import type Database from 'better-sqlite3'; import type { FastifyInstance } from 'fastify'; import { buildApp, type BuildAppOptions } from './app.js'; import { ConnectionProbe, type ConnectionTransport, } from './application/connections/connection-probe.js'; import { InstanceCredentialResolver } from './application/connections/instance-credential-resolver.js'; import { InstanceLoginService } from './application/connections/instance-login-service.js'; import { InstanceSessionStore, UpstreamSessionClient, type UpstreamSessionClientOptions, } from './application/connections/upstream-session-client.js'; import { InstanceService } from './application/instances/instance-service.js'; import { DeleteInstanceOperation } from './application/operations/delete-instance-operation.js'; import type { SecretStore } from './infrastructure/secrets/secret-store.js'; import { registerInstanceRoutes } from './interface/http/instance-routes.js'; export interface SafeControlPlaneUpstream extends ConnectionTransport { request: UpstreamSessionClientOptions['request']; } export interface ControlPlaneOptions { readonly db: Database.Database; readonly store: SecretStore; readonly upstream: SafeControlPlaneUpstream; readonly now?: () => Date; readonly app?: Omit; } export interface ControlPlaneApp extends FastifyInstance { retryPendingSecretCleanup(): Promise; } export function buildControlPlaneApp(options: ControlPlaneOptions): ControlPlaneApp { const instances = options.now ? new InstanceService({ db: options.db, store: options.store, now: options.now }) : new InstanceService({ db: options.db, store: options.store }); const connections = options.now ? new ConnectionProbe({ db: options.db, instances, transport: options.upstream, now: options.now, }) : new ConnectionProbe({ db: options.db, instances, transport: options.upstream }); const sessions = new InstanceSessionStore(); const client = new UpstreamSessionClient({ sessions, request: options.upstream.request }); const resolver = new InstanceCredentialResolver({ db: options.db, store: options.store }); const login = options.now ? new InstanceLoginService({ db: options.db, client, resolver, now: options.now }) : new InstanceLoginService({ db: options.db, client, resolver }); const deletion = options.now ? new DeleteInstanceOperation({ db: options.db, instances, now: options.now }) : new DeleteInstanceOperation({ db: options.db, instances }); deletion.reconcileInterruptedJobs(); const app = buildApp({ ...options.app, registerRoutes: (app) => registerInstanceRoutes(app, { instances, connections, login, deletion }), }); Object.assign(app, { retryPendingSecretCleanup: () => instances.retryPendingSecretCleanup(), }); return app as ControlPlaneApp; }