@powerhousedao/switchboard 6.1.0-dev.4 โ†’ 6.1.0-dev.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,45 @@
1
+ ## 6.1.0-dev.6 (2026-05-28)
2
+
3
+ ### ๐Ÿš€ Features
4
+
5
+ - adding attachment service to the switchboard options ([a88e4cdc6](https://github.com/powerhouse-inc/powerhouse/commit/a88e4cdc6))
6
+ - mount OpenPanel with consent gating and user identification ([aa7c77b07](https://github.com/powerhouse-inc/powerhouse/commit/aa7c77b07))
7
+ - feat(openpanel): add event mappings, validation, and naming helpers ([556688064](https://github.com/powerhouse-inc/powerhouse/commit/556688064))
8
+ - **connect:** add OpenPanel Analytics configuration ([b39d072b2](https://github.com/powerhouse-inc/powerhouse/commit/b39d072b2))
9
+
10
+ ### ๐Ÿฉน Fixes
11
+
12
+ - **connect:** resolve @powerhousedao/connect/* aliases in vitest ([f7de787c6](https://github.com/powerhouse-inc/powerhouse/commit/f7de787c6))
13
+ - pnpm-lock file ([d5019827c](https://github.com/powerhouse-inc/powerhouse/commit/d5019827c))
14
+
15
+ ### โค๏ธ Thank You
16
+
17
+ - Benjamin Jordan
18
+ - Claude Opus 4.7
19
+
20
+ ## 6.1.0-dev.5 (2026-05-27)
21
+
22
+ ### ๐Ÿš€ Features
23
+
24
+ - **reactor:** benchmark for workers ([8ff51da2f](https://github.com/powerhouse-inc/powerhouse/commit/8ff51da2f))
25
+ - **reactor:** multi-worker integration test -- eep ([b8b9566cf](https://github.com/powerhouse-inc/powerhouse/commit/b8b9566cf))
26
+ - **reactor:** added workerhandle, parent-side ipc wrapper ([b1955e3a6](https://github.com/powerhouse-inc/powerhouse/commit/b1955e3a6))
27
+ - **reactor:** worker protocol ([c1bb0bd30](https://github.com/powerhouse-inc/powerhouse/commit/c1bb0bd30))
28
+
29
+ ### ๐Ÿฉน Fixes
30
+
31
+ - ci needs to run integration tests serially, not in parallel ([d97b73622](https://github.com/powerhouse-inc/powerhouse/commit/d97b73622))
32
+ - do not double run hub-spoke and make sure postgres is running in integration tests ([6a26a7377](https://github.com/powerhouse-inc/powerhouse/commit/6a26a7377))
33
+ - swap never bundle for reactor-api so that subgraphs are properly detected ([7ee0fdb5b](https://github.com/powerhouse-inc/powerhouse/commit/7ee0fdb5b))
34
+ - **reactor:** fix linting issues ([7e0a4af8d](https://github.com/powerhouse-inc/powerhouse/commit/7e0a4af8d))
35
+ - add hub-spoke test in test:integration ([5ab8c0b36](https://github.com/powerhouse-inc/powerhouse/commit/5ab8c0b36))
36
+ - **reactor:** a number of linter and import errors needed fixed due to node imports ([060babc30](https://github.com/powerhouse-inc/powerhouse/commit/060babc30))
37
+ - **reactor:** grr path resolution fixes, tailwind fixes, linter fixes ([0eb6ad89f](https://github.com/powerhouse-inc/powerhouse/commit/0eb6ad89f))
38
+
39
+ ### โค๏ธ Thank You
40
+
41
+ - Benjamin Jordan
42
+
1
43
  ## 6.1.0-dev.4 (2026-05-26)
2
44
 
3
45
  This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="5f3b2177-d6bb-533b-b87e-e4eb03b10c87")}catch(e){}}();
4
- import { i as parseForcePgVersion, n as startSwitchboard } from "./server-bMFA4VKj.mjs";
4
+ import { a as parseForcePgVersion, r as startSwitchboard } from "./server-DcQv9aK4.mjs";
5
5
  import "./utils-BVNg1DRI.mjs";
6
6
  import { metrics } from "@opentelemetry/api";
7
7
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="15b2a451-3254-5fb0-8fe9-4fa3c769e05c")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="751da8f8-c846-5e8d-9e67-a73d4fbbba42")}catch(e){}}();
3
3
  import { n as addDefaultReactorDrive, r as isPostgresUrl, t as addDefaultDrive } from "./utils-BVNg1DRI.mjs";
4
4
  import { register } from "node:module";
5
5
  import * as Sentry from "@sentry/node";
@@ -11,6 +11,7 @@ import path from "node:path";
11
11
  import { ReactorInstrumentation } from "@powerhousedao/opentelemetry-instrumentation-reactor";
12
12
  import { AtomicNodeFs } from "@powerhousedao/pglite-fs";
13
13
  import { ChannelScheme, EventBus, REACTOR_SCHEMA, ReactorBuilder, ReactorClientBuilder, driveCollectionId, parseDriveUrl } from "@powerhousedao/reactor";
14
+ import { AttachmentNotFound, InvalidAttachmentRef, ReservationNotFound, createRemoteAttachmentService } from "@powerhousedao/reactor-attachments";
14
15
  import { HttpPackageLoader, ImportPackageLoader, PackageManagementService, PackagesSubgraph, getUniqueDocumentModels, initializeAndStartAPI } from "@powerhousedao/reactor-api";
15
16
  import { httpsHooksPath } from "@powerhousedao/reactor-api/https-hooks";
16
17
  import { VitePackageLoader, createViteLogger, startViteServer } from "@powerhousedao/reactor-api/vite";
@@ -22,7 +23,6 @@ import { Kysely, PostgresDialect } from "kysely";
22
23
  import net from "node:net";
23
24
  import path$1 from "path";
24
25
  import { Pool } from "pg";
25
- import { AttachmentNotFound, InvalidAttachmentRef, ReservationNotFound } from "@powerhousedao/reactor-attachments";
26
26
  import { Readable } from "node:stream";
27
27
  import { EnvVarProvider } from "@openfeature/env-var-provider";
28
28
  import { OpenFeature } from "@openfeature/server-sdk";
@@ -574,6 +574,17 @@ async function createReactorKysely(opts) {
574
574
  logger.info(inMemory ? `Using in-memory PGlite (PG${reactorPgliteMajor}) for reactor storage [PH_PGLITE_IN_MEMORY=1]` : `Using PGlite (PG${reactorPgliteMajor}) for reactor storage at ${reactorPgliteDir}`);
575
575
  return new Kysely({ dialect: new ClosablePGliteDialect(pglite) });
576
576
  }
577
+ /** Derive the remote attachment service config for switchboard's own `/attachments/*` API. */
578
+ function deriveAttachmentServiceConfig(options, serverPort, renown) {
579
+ const protocol = options.https ? "https" : "http";
580
+ return {
581
+ remoteUrl: options.attachmentServiceUrl ?? process.env.PH_SWITCHBOARD_PUBLIC_URL ?? `${protocol}://localhost:${serverPort}`,
582
+ jwtHandler: renown ? async (url) => renown.user ? renown.getBearerToken({
583
+ expiresIn: 10,
584
+ aud: url
585
+ }) : void 0 : void 0
586
+ };
587
+ }
577
588
  async function initServer(serverPort, options, renown) {
578
589
  const { dev, packages = [], remoteDrives = [], logger = defaultLogger } = options;
579
590
  logger.level = LogLevel;
@@ -726,6 +737,7 @@ async function initServer(serverPort, options, renown) {
726
737
  }, "switchboard");
727
738
  apiRef.current = api;
728
739
  registerAttachmentRoutes(api);
740
+ const attachmentService = createRemoteAttachmentService(deriveAttachmentServiceConfig(options, serverPort, renown));
729
741
  if (process.env.SENTRY_DSN) api.httpAdapter.setupSentryErrorHandler(Sentry);
730
742
  const { client, graphqlManager, documentModelRegistry } = api;
731
743
  if (httpLoader) {
@@ -795,6 +807,7 @@ async function initServer(serverPort, options, renown) {
795
807
  defaultDriveUrl,
796
808
  api,
797
809
  reactor: client,
810
+ attachmentService,
798
811
  renown,
799
812
  port: serverPort,
800
813
  shutdown: () => api.dispose()
@@ -850,7 +863,7 @@ const startSwitchboard = async (options = {}) => {
850
863
  };
851
864
  if (import.meta.main) await startSwitchboard();
852
865
  //#endregion
853
- export { parseForcePgVersion as i, startSwitchboard as n, applySwitchboardReactorDefaults as r, isPortAvailable as t };
866
+ export { parseForcePgVersion as a, applySwitchboardReactorDefaults as i, isPortAvailable as n, startSwitchboard as r, deriveAttachmentServiceConfig as t };
854
867
 
855
- //# sourceMappingURL=server-bMFA4VKj.mjs.map
856
- //# debugId=15b2a451-3254-5fb0-8fe9-4fa3c769e05c
868
+ //# sourceMappingURL=server-DcQv9aK4.mjs.map
869
+ //# debugId=751da8f8-c846-5e8d-9e67-a73d4fbbba42
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-DcQv9aK4.mjs","sources":["../src/pglite-version.ts","../src/builder-defaults.mts","../src/attachments/auth.ts","../src/attachments/mount-auth.ts","../src/attachments/routes.ts","../src/attachments/index.ts","../src/feature-flags.ts","../src/pglite-dialect.ts","../src/pglite-migration.ts","../src/renown.ts","../src/server.mts"],"sourcesContent":["import type * as CurrentPGliteModuleNs from \"@electric-sql/pglite\";\nimport { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\n\nexport const CURRENT_PG_MAJOR = 17;\nexport const SUPPORTED_PG_MAJORS = [16, 17] as const;\nexport type SupportedPgMajor = (typeof SUPPORTED_PG_MAJORS)[number];\n\ntype CurrentPGliteModule = typeof CurrentPGliteModuleNs;\n\nexport async function readPgVersionFile(\n dataDir: string,\n): Promise<number | null> {\n try {\n const raw = await fs.readFile(path.join(dataDir, \"PG_VERSION\"), \"utf8\");\n const major = parseInt(raw.trim(), 10);\n return Number.isFinite(major) ? major : null;\n } catch {\n return null;\n }\n}\n\nexport function isSupportedMajor(major: number): major is SupportedPgMajor {\n return (SUPPORTED_PG_MAJORS as readonly number[]).includes(major);\n}\n\n/**\n * Parses the `PH_FORCE_PG_VERSION` env var. Returns the validated major, or\n * `null` when the var is unset/empty. Throws on any value that is not a\n * supported major โ€” invalid configuration must fail before the server starts\n * touching disk.\n */\nexport function parseForcePgVersion(\n raw: string | undefined,\n): SupportedPgMajor | null {\n if (raw === undefined || raw.trim() === \"\") return null;\n const parsed = Number(raw);\n if (Number.isInteger(parsed) && isSupportedMajor(parsed)) return parsed;\n throw new Error(\n `PH_FORCE_PG_VERSION must be one of: ${SUPPORTED_PG_MAJORS.join(\", \")} (got: ${raw})`,\n );\n}\n\nexport async function loadPGliteModule(\n major: SupportedPgMajor,\n): Promise<CurrentPGliteModule> {\n if (major === 16) {\n return (await import(\"pglite-legacy-02\")) as unknown as CurrentPGliteModule;\n }\n return import(\"@electric-sql/pglite\");\n}\n\ntype PgDumpFn = (options: {\n pg: unknown;\n}) => Promise<{ text(): Promise<string> }>;\n\nexport async function loadPgDump(major: SupportedPgMajor): Promise<PgDumpFn> {\n if (major === 16) {\n const mod = (await import(\"pglite-tools-legacy-02/pg_dump\")) as {\n pgDump: PgDumpFn;\n };\n return mod.pgDump;\n }\n const mod = (await import(\"@electric-sql/pglite-tools/pg_dump\")) as {\n pgDump: PgDumpFn;\n };\n return mod.pgDump;\n}\n","import type { ReactorBuilder } from \"@powerhousedao/reactor\";\nimport {\n ChannelScheme,\n type IDocumentModelLoader,\n type ReactorClientBuilder,\n type SignerConfig,\n} from \"@powerhousedao/reactor\";\nimport { reactorDriveDocumentModelModule } from \"@powerhousedao/reactor-drive\";\nimport { getUniqueDocumentModels } from \"@powerhousedao/reactor-api\";\nimport { driveDocumentModelModule } from \"@powerhousedao/shared/document-drive\";\nimport type { DocumentModelModule } from \"@powerhousedao/shared/document-model\";\nimport { documentModels as vetraDocumentModels } from \"@powerhousedao/vetra\";\nimport { documentModelDocumentModelModule, type ILogger } from \"document-model\";\n\nexport type SwitchboardReactorDefaultsOptions = {\n /**\n * Additional document models to register alongside switchboard's baseline.\n * The baseline (document-model, drive, reactor-drive) is included unless\n * `includeBaseModels` is `false`; vetra models are included unless\n * `includeVetraModels` is `false`. Duplicates by id are removed via\n * `getUniqueDocumentModels`.\n */\n documentModels?: DocumentModelModule[];\n /** Default true. */\n includeBaseModels?: boolean;\n /** Default true. */\n includeVetraModels?: boolean;\n /**\n * Channel scheme. Defaults to `ChannelScheme.SWITCHBOARD`, which populates\n * `reactorModule.syncModule.syncManager` โ€” required by reactor-api. Set\n * to `false` only if the caller will configure a scheme themselves.\n */\n channelScheme?: ChannelScheme | false;\n /** Defaults to true. Set false when the caller owns SIGINT handling. */\n signalHandlers?: boolean;\n /** Executor tuning. Omit to use the reactor's own defaults. */\n executorConfig?: { maxSkipThreshold?: number };\n /** Wire dynamic document-model loading via HTTP. */\n documentModelLoader?: IDocumentModelLoader;\n logger?: ILogger;\n /**\n * Identity signer (typically from `getRenownSignerConfig`). Applied to the\n * `ReactorClientBuilder`; omit for unsigned operation.\n */\n signer?: SignerConfig;\n};\n\n/**\n * Apply switchboard's standard configuration to a reactor + client builder\n * pair. Each piece is opt-out via the options object; defaults mirror what\n * `startSwitchboard` does when building a reactor itself. Mutates both\n * builders in place.\n *\n * Does NOT touch kysely or read models โ€” callers wire those themselves\n * (see `withKysely` / `withReadModelFactory` on the reactor builder).\n */\nexport function applySwitchboardReactorDefaults(\n reactorBuilder: ReactorBuilder,\n clientBuilder: ReactorClientBuilder,\n options: SwitchboardReactorDefaultsOptions = {},\n): void {\n const baseModels =\n options.includeBaseModels !== false\n ? [\n documentModelDocumentModelModule,\n driveDocumentModelModule,\n reactorDriveDocumentModelModule,\n ]\n : [];\n const vetraModels =\n options.includeVetraModels !== false ? vetraDocumentModels : [];\n const extra = options.documentModels ?? [];\n if (baseModels.length || vetraModels.length || extra.length) {\n reactorBuilder.withDocumentModels(\n getUniqueDocumentModels(baseModels, vetraModels, extra),\n );\n }\n\n const scheme =\n options.channelScheme === undefined\n ? ChannelScheme.SWITCHBOARD\n : options.channelScheme;\n if (scheme !== false) {\n reactorBuilder.withChannelScheme(scheme);\n }\n\n if (options.signalHandlers !== false) {\n reactorBuilder.withSignalHandlers();\n }\n\n if (options.executorConfig?.maxSkipThreshold !== undefined) {\n reactorBuilder.withExecutorConfig({\n maxSkipThreshold: options.executorConfig.maxSkipThreshold,\n });\n }\n\n if (options.documentModelLoader) {\n reactorBuilder.withDocumentModelLoader(options.documentModelLoader);\n }\n\n if (options.logger) {\n reactorBuilder.withLogger(options.logger);\n }\n\n if (options.signer) {\n clientBuilder.withSigner(options.signer);\n }\n}\n","import type { AuthService } from \"@powerhousedao/reactor-api\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\n\nexport type NodeHandler = (\n req: IncomingMessage,\n res: ServerResponse,\n) => Promise<void> | void;\n\n/**\n * Wrap a Node-style handler so that, when `authService` is provided and auth is\n * enabled, the request must carry a verifiable Bearer token.\n */\nexport function requireAuth(\n authService: AuthService | undefined,\n handler: NodeHandler,\n): NodeHandler {\n if (!authService) return handler;\n\n return async (req, res) => {\n let result;\n try {\n result = await authService.verifyBearer(req.headers.authorization);\n } catch {\n res.statusCode = 500;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify({ error: \"Internal authentication error\" }));\n return;\n }\n\n if (result instanceof Response) {\n const body = await result.text();\n res.statusCode = result.status;\n const contentType = result.headers.get(\"content-type\");\n if (contentType) res.setHeader(\"Content-Type\", contentType);\n res.end(body);\n return;\n }\n\n if (result.auth_enabled && !result.user) {\n res.statusCode = 401;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify({ error: \"Authentication required\" }));\n return;\n }\n\n await handler(req, res);\n };\n}\n","import type { API } from \"@powerhousedao/reactor-api\";\nimport { requireAuth, type NodeHandler } from \"./auth.js\";\n\nexport type HttpMethod = \"DELETE\" | \"GET\" | \"HEAD\" | \"POST\" | \"PUT\";\n\n/**\n * Mount a Node-style attachment route with `requireAuth` applied unconditionally.\n * When `api.authService` is undefined (auth disabled), `requireAuth` returns the\n * handler unchanged โ€” that is the only way to opt out. To register a route\n * without auth wrapping you must call `api.httpAdapter.mountNodeRoute` directly.\n */\nexport function mountAuthenticatedNodeRoute(\n api: Pick<API, \"httpAdapter\" | \"authService\">,\n method: HttpMethod,\n path: string,\n handler: NodeHandler,\n): void {\n api.httpAdapter.mountNodeRoute(\n method,\n path,\n requireAuth(api.authService, handler),\n );\n}\n","import {\n AttachmentNotFound,\n InvalidAttachmentRef,\n ReservationNotFound,\n type AttachmentBuildResult,\n type ReserveAttachmentOptions,\n} from \"@powerhousedao/reactor-attachments\";\nimport type { AttachmentHash } from \"@powerhousedao/reactor\";\nimport { childLogger } from \"document-model\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { Readable } from \"node:stream\";\nimport type { ReadableStream as NodeReadableStream } from \"node:stream/web\";\n\nconst logger = childLogger([\"switchboard\", \"attachments\"]);\n\n// Canonical form is lowercase hex (the SHA-256 hasher emits lowercase), but\n// accept either case from the wire and normalise before lookup. This keeps\n// the API forgiving for hand-typed URLs without changing storage semantics.\nconst HASH_PATTERN = /^[a-f0-9]{64}$/i;\n// eslint-disable-next-line no-control-regex\nconst CONTROL_CHARS = /[\\x00-\\x1f\\x7f]/;\n// RFC 6838 token chars; allows optional `; param=value` pairs (token or quoted-string).\nconst MIME_TYPE_PATTERN =\n /^[!#$%&'*+\\-.^_`|~\\w]+\\/[!#$%&'*+\\-.^_`|~\\w]+(?:\\s*;\\s*[!#$%&'*+\\-.^_`|~\\w]+=(?:[!#$%&'*+\\-.^_`|~\\w]+|\"(?:[^\"\\\\\\r\\n]|\\\\[^\\r\\n])*\"))*$/;\nconst MAX_FILENAME_LEN = 255;\nconst MAX_MIMETYPE_LEN = 255;\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n res.statusCode = status;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify(body));\n}\n\nfunction sendError(res: ServerResponse, status: number, message: string): void {\n sendJson(res, status, { error: message });\n}\n\nfunction statusForError(err: unknown): number {\n if (err instanceof AttachmentNotFound) return 404;\n if (err instanceof ReservationNotFound) return 404;\n if (err instanceof InvalidAttachmentRef) return 400;\n return 500;\n}\n\nfunction sendErrorFromException(res: ServerResponse, err: unknown): void {\n const status = statusForError(err);\n if (status >= 500) {\n logger.error(\"Attachment route error: @error\", err);\n sendError(res, status, \"Internal error\");\n return;\n }\n sendError(res, status, err instanceof Error ? err.message : String(err));\n}\n\nasync function readJsonBody(\n req: IncomingMessage,\n body: unknown,\n): Promise<unknown> {\n // The Express body-parser may have already populated `body`. When that\n // happens we trust it; otherwise read the raw stream ourselves so this\n // module is independent of upstream middleware ordering.\n if (body !== undefined && body !== null && typeof body === \"object\") {\n return body;\n }\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(chunk as Buffer);\n }\n if (chunks.length === 0) return undefined;\n const text = Buffer.concat(chunks).toString(\"utf8\");\n if (text.length === 0) return undefined;\n return JSON.parse(text);\n}\n\nexport function parseReserveOptions(\n input: unknown,\n): ReserveAttachmentOptions | null {\n if (input === null || typeof input !== \"object\") return null;\n const obj = input as Record<string, unknown>;\n if (\n typeof obj.mimeType !== \"string\" ||\n obj.mimeType.length === 0 ||\n obj.mimeType.length > MAX_MIMETYPE_LEN ||\n !MIME_TYPE_PATTERN.test(obj.mimeType)\n ) {\n return null;\n }\n if (\n typeof obj.fileName !== \"string\" ||\n obj.fileName.length === 0 ||\n obj.fileName.length > MAX_FILENAME_LEN ||\n CONTROL_CHARS.test(obj.fileName)\n ) {\n return null;\n }\n let extension: string | null = null;\n if (typeof obj.extension === \"string\") {\n if (obj.extension.length === 0 || /[\\\\/]/.test(obj.extension)) return null;\n extension = obj.extension;\n } else if (obj.extension !== undefined && obj.extension !== null) {\n return null;\n }\n return {\n mimeType: obj.mimeType,\n fileName: obj.fileName,\n extension,\n };\n}\n\nexport function quoteFilename(name: string): string {\n // RFC 6266: quoted-string with internal \" and \\ escaped.\n return `\"${name.replace(/[\\\\\"]/g, \"\\\\$&\")}\"`;\n}\n\nexport function buildContentDisposition(fileName: string): string {\n // ASCII fallback: replace any byte outside printable ASCII (0x20-0x7e),\n // plus `\"` and `\\`, with `_`. Browsers fall back to this when they don't\n // grok `filename*=`; the modern parameter carries the real name.\n const ascii = fileName.replace(/[^\\x20-\\x21\\x23-\\x5b\\x5d-\\x7e]/g, \"_\");\n // RFC 5987: percent-encode UTF-8 bytes. encodeURIComponent leaves a few\n // chars that 5987 disallows in token; re-encode them.\n const encoded = encodeURIComponent(fileName).replace(\n /['()*!]/g,\n (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n return `attachment; filename=${quoteFilename(ascii)}; filename*=UTF-8''${encoded}`;\n}\n\nexport function makeReserveHandler(attachments: AttachmentBuildResult) {\n return async (\n req: IncomingMessage,\n res: ServerResponse,\n body?: unknown,\n ): Promise<void> => {\n let parsed: unknown;\n try {\n parsed = await readJsonBody(req, body);\n } catch {\n sendError(res, 400, \"Invalid JSON body\");\n return;\n }\n const opts = parseReserveOptions(parsed);\n if (!opts) {\n sendError(\n res,\n 400,\n \"Body must be { mimeType: string (type/subtype), fileName: string (no control characters, max 255 chars), extension?: string|null }\",\n );\n return;\n }\n try {\n const upload = await attachments.service.reserve(opts);\n sendJson(res, 201, { reservationId: upload.reservationId });\n } catch (err) {\n sendErrorFromException(res, err);\n }\n };\n}\n\nexport function makeUploadHandler(attachments: AttachmentBuildResult) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const reservationId = extractParam(req, \"reservationId\");\n if (!reservationId) {\n sendError(res, 400, \"Missing reservationId\");\n return;\n }\n\n let reservation;\n try {\n reservation = await attachments.reservations.get(reservationId);\n } catch (err) {\n sendErrorFromException(res, err);\n return;\n }\n\n const upload = attachments.uploadFactory.createUpload(\n reservation.reservationId,\n {\n mimeType: reservation.mimeType,\n fileName: reservation.fileName,\n extension: reservation.extension,\n },\n );\n\n const webStream = Readable.toWeb(\n req as Readable,\n ) as ReadableStream<Uint8Array>;\n\n try {\n const result = await upload.send(webStream);\n sendJson(res, 200, result);\n } catch (err) {\n sendErrorFromException(res, err);\n }\n };\n}\n\nexport function makeDownloadHandler(attachments: AttachmentBuildResult) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const hash = extractParam(req, \"hash\");\n if (!hash || !HASH_PATTERN.test(hash)) {\n sendError(res, 400, \"Invalid attachment hash\");\n return;\n }\n\n const controller = new AbortController();\n req.once(\"close\", () => controller.abort());\n\n const canonicalHash = hash.toLowerCase() as AttachmentHash;\n let response;\n try {\n response = await attachments.store.get(canonicalHash, controller.signal);\n } catch (err) {\n sendErrorFromException(res, err);\n return;\n }\n\n const { header, body } = response;\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", header.mimeType);\n res.setHeader(\"Content-Length\", String(header.sizeBytes));\n res.setHeader(\n \"Content-Disposition\",\n buildContentDisposition(header.fileName),\n );\n res.setHeader(\"Attachment-Metadata\", buildMetadataHeader(header));\n\n Readable.fromWeb(body as unknown as NodeReadableStream<Uint8Array>).pipe(\n res,\n );\n };\n}\n\nfunction buildMetadataHeader(header: {\n mimeType: string;\n fileName: string;\n sizeBytes: number;\n extension: string | null;\n createdAtUtc: string;\n lastAccessedAtUtc: string;\n}): string {\n return JSON.stringify({\n mimeType: header.mimeType,\n fileName: header.fileName,\n sizeBytes: header.sizeBytes,\n extension: header.extension,\n createdAtUtc: header.createdAtUtc,\n lastAccessedAtUtc: header.lastAccessedAtUtc,\n });\n}\n\nexport function makeStatHandler(attachments: AttachmentBuildResult) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const hash = extractParam(req, \"hash\");\n if (!hash || !HASH_PATTERN.test(hash)) {\n sendError(res, 400, \"Invalid attachment hash\");\n return;\n }\n\n const canonicalHash = hash.toLowerCase() as AttachmentHash;\n let header;\n try {\n header = await attachments.store.stat(canonicalHash);\n } catch (err) {\n sendErrorFromException(res, err);\n return;\n }\n\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", header.mimeType);\n res.setHeader(\"Content-Length\", String(header.sizeBytes));\n res.setHeader(\n \"Content-Disposition\",\n buildContentDisposition(header.fileName),\n );\n res.setHeader(\"Attachment-Metadata\", buildMetadataHeader(header));\n res.end();\n };\n}\n\nexport function makeGetReservationHandler(attachments: AttachmentBuildResult) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const reservationId = extractParam(req, \"reservationId\");\n if (!reservationId) {\n sendError(res, 400, \"Missing reservationId\");\n return;\n }\n try {\n const reservation = await attachments.reservations.get(reservationId);\n sendJson(res, 200, reservation);\n } catch (err) {\n sendErrorFromException(res, err);\n }\n };\n}\n\nexport function makeDeleteReservationHandler(\n attachments: AttachmentBuildResult,\n) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const reservationId = extractParam(req, \"reservationId\");\n if (!reservationId) {\n sendError(res, 400, \"Missing reservationId\");\n return;\n }\n try {\n await attachments.reservations.delete(reservationId);\n res.statusCode = 204;\n res.end();\n } catch (err) {\n sendErrorFromException(res, err);\n }\n };\n}\n\nfunction extractParam(req: IncomingMessage, name: string): string | undefined {\n const expressParams = (\n req as IncomingMessage & {\n params?: Record<string, string>;\n }\n ).params;\n return expressParams?.[name];\n}\n","import type { API } from \"@powerhousedao/reactor-api\";\nimport { mountAuthenticatedNodeRoute } from \"./mount-auth.js\";\nimport {\n makeDeleteReservationHandler,\n makeDownloadHandler,\n makeGetReservationHandler,\n makeReserveHandler,\n makeStatHandler,\n makeUploadHandler,\n} from \"./routes.js\";\n\nexport function registerAttachmentRoutes(api: API): void {\n const { attachments } = api;\n\n mountAuthenticatedNodeRoute(\n api,\n \"POST\",\n \"/attachments/reservations\",\n makeReserveHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"GET\",\n \"/attachments/reservations/:reservationId\",\n makeGetReservationHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"DELETE\",\n \"/attachments/reservations/:reservationId\",\n makeDeleteReservationHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"PUT\",\n \"/attachments/reservations/:reservationId\",\n makeUploadHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"HEAD\",\n \"/attachments/:hash\",\n makeStatHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"GET\",\n \"/attachments/:hash\",\n makeDownloadHandler(attachments),\n );\n}\n","import { EnvVarProvider } from \"@openfeature/env-var-provider\";\nimport { OpenFeature } from \"@openfeature/server-sdk\";\n\nexport async function initFeatureFlags() {\n // for now, we're only using env vars for feature flags\n const provider = new EnvVarProvider();\n\n await OpenFeature.setProviderAndWait(provider);\n\n return OpenFeature.getClient();\n}\n","import type { PGlite } from \"@electric-sql/pglite\";\nimport type { Driver } from \"kysely\";\nimport { PGliteDialect } from \"kysely-pglite-dialect\";\n\n// kysely-pglite-dialect's driver.destroy() only nulls its reference to the\n// PGlite client โ€” it never calls pglite.close(). Without close(), WAL is not\n// flushed and the data dir is left in a state that aborts the wasm on the\n// next open. This wrapper closes the dialect's PGlite as part of the\n// reactor's database.destroy() chain.\nexport class ClosablePGliteDialect extends PGliteDialect {\n readonly #pglite: PGlite;\n\n constructor(pglite: PGlite) {\n super(pglite);\n this.#pglite = pglite;\n }\n\n createDriver(): Driver {\n const driver = super.createDriver();\n const pglite = this.#pglite;\n const innerDestroy = driver.destroy.bind(driver);\n driver.destroy = async () => {\n await innerDestroy();\n if (!pglite.closed) {\n await pglite.close();\n }\n };\n return driver;\n }\n}\n","import type { ILogger } from \"document-model\";\nimport { promises as fs } from \"node:fs\";\nimport {\n CURRENT_PG_MAJOR,\n isSupportedMajor,\n loadPGliteModule,\n loadPgDump,\n readPgVersionFile,\n type SupportedPgMajor,\n} from \"./pglite-version.js\";\n\ntype PGliteCtor = new (\n dataDir: string,\n options?: Record<string, unknown>,\n) => {\n waitReady: Promise<void>;\n exec: (sql: string) => Promise<unknown>;\n close: () => Promise<void>;\n};\n\nfunction backupPath(dataDir: string, major: number): string {\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n return `${dataDir}.backup-pg${major}-${stamp}`;\n}\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await fs.stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction logRestoreFailure(\n dataDir: string,\n sql: string,\n err: unknown,\n logger: ILogger,\n): void {\n const errObj = err as {\n message?: string;\n position?: string | number;\n severity?: string;\n code?: string;\n detail?: string;\n where?: string;\n };\n const position =\n typeof errObj.position === \"string\"\n ? parseInt(errObj.position, 10)\n : typeof errObj.position === \"number\"\n ? errObj.position\n : NaN;\n\n logger.error(\n `[pglite-migration] Restore failed for ${dataDir}: code=${errObj.code ?? \"\"} severity=${errObj.severity ?? \"\"} message=${errObj.message ?? \"\"} sqlLength=${sql.length}`,\n );\n\n if (Number.isFinite(position) && position > 0) {\n const zeroBased = position - 1;\n const start = Math.max(0, zeroBased - 200);\n const end = Math.min(sql.length, zeroBased + 200);\n const before = sql.slice(start, zeroBased);\n const at = sql.slice(zeroBased, zeroBased + 1);\n const after = sql.slice(zeroBased + 1, end);\n logger.error(\n `[pglite-migration] SQL context around position ${position}:\\n${before}ยป${at}ยซ${after}`,\n );\n } else {\n logger.error(\n `[pglite-migration] No position info. First 2000 chars of dump:\\n${sql.slice(0, 2000)}`,\n );\n }\n}\n\n/**\n * Migrate a filesystem PGLite data directory from a legacy PG major to the\n * current one. Renames the existing dir to a timestamped backup, dumps via the\n * matching legacy `pg_dump`, restores into a fresh current-version PGLite at\n * the original path. On failure, the original dir is restored from the backup.\n *\n * No-op when the dir is missing or already at the current major.\n */\nexport async function migratePgliteDir(\n dataDir: string,\n logger: ILogger,\n): Promise<void> {\n const major = await readPgVersionFile(dataDir);\n if (major === null) {\n logger.info(\n `[pglite-migration] No PG_VERSION at ${dataDir}; skipping migration`,\n );\n return;\n }\n if (major === CURRENT_PG_MAJOR) return;\n\n if (!isSupportedMajor(major)) {\n throw new Error(\n `Unsupported legacy PGlite data dir: PG_VERSION=${major} for ${dataDir}`,\n );\n }\n\n const backupDir = backupPath(dataDir, major);\n logger.info(\n `[pglite-migration] Migrating ${dataDir} from PG${major} to PG${CURRENT_PG_MAJOR}; backup: ${backupDir}`,\n );\n\n await fs.rename(dataDir, backupDir);\n\n let sql: string;\n try {\n const [legacyMod, pgDump] = await Promise.all([\n loadPGliteModule(major as SupportedPgMajor),\n loadPgDump(major as SupportedPgMajor),\n ]);\n const LegacyPGlite = (legacyMod as unknown as { PGlite: PGliteCtor })\n .PGlite;\n const pg = new LegacyPGlite(backupDir);\n try {\n await pg.waitReady;\n const file = await pgDump({ pg });\n sql = await file.text();\n } finally {\n await pg.close();\n }\n } catch (err) {\n await rollback(dataDir, backupDir, err, logger);\n throw err;\n }\n\n try {\n const currentMod = await loadPGliteModule(CURRENT_PG_MAJOR);\n const CurrentPGlite = (currentMod as unknown as { PGlite: PGliteCtor })\n .PGlite;\n const pg = new CurrentPGlite(dataDir, { relaxedDurability: false });\n try {\n await pg.waitReady;\n try {\n await pg.exec(\"SET standard_conforming_strings = off;\");\n } catch (gucErr) {\n logger.warn(\n `[pglite-migration] Could not force standard_conforming_strings=off: ${String(gucErr)}`,\n );\n }\n try {\n await pg.exec(sql);\n } catch (execErr) {\n logRestoreFailure(dataDir, sql, execErr, logger);\n throw execErr;\n }\n } finally {\n await pg.close();\n }\n } catch (err) {\n await rollback(dataDir, backupDir, err, logger);\n throw err;\n }\n\n logger.info(\n `[pglite-migration] Migration of ${dataDir} complete. Backup retained at ${backupDir}; remove it manually once you have verified the upgrade.`,\n );\n}\n\nasync function rollback(\n dataDir: string,\n backupDir: string,\n originalError: unknown,\n logger: ILogger,\n): Promise<void> {\n try {\n if (await pathExists(dataDir)) {\n await fs.rm(dataDir, { recursive: true, force: true });\n }\n if (await pathExists(backupDir)) {\n await fs.rename(backupDir, dataDir);\n }\n } catch (rollbackErr) {\n logger.error(\n `[pglite-migration] Migration AND rollback failed for ${dataDir}. Original error: ${String(originalError)}; rollback error: ${String(rollbackErr)}; backup may still exist at ${backupDir}.`,\n );\n return;\n }\n logger.error(\n `[pglite-migration] Migration failed for ${dataDir}; rolled back from ${backupDir}. Original error: ${String(originalError)}`,\n );\n}\n","import type { SignerConfig } from \"@powerhousedao/reactor\";\nimport {\n createSignatureVerifier,\n DEFAULT_RENOWN_URL,\n NodeKeyStorage,\n RenownBuilder,\n RenownCryptoBuilder,\n type IRenown,\n} from \"@renown/sdk/node\";\nimport { childLogger } from \"document-model\";\n\nconst logger = childLogger([\"switchboard\", \"renown\"]);\n\nexport interface RenownOptions {\n /** Path to the keypair file. Defaults to .ph/.keypair.json in cwd */\n keypairPath?: string;\n /** If true, won't generate a new keypair if none exists */\n requireExisting?: boolean;\n /** Base url of the Renown instance to use */\n baseUrl?: string;\n}\n\n/**\n * Initialize Renown for the Switchboard instance.\n * This allows Switchboard to authenticate with remote services\n * using the same identity established during `ph login`.\n */\nexport async function initRenown(\n options: RenownOptions = {},\n): Promise<IRenown | null> {\n const {\n keypairPath,\n requireExisting = false,\n baseUrl = DEFAULT_RENOWN_URL,\n } = options;\n\n const keyStorage = new NodeKeyStorage(keypairPath, {\n logger,\n });\n\n // Check if we have an existing keypair\n const existingKeyPair = await keyStorage.loadKeyPair();\n\n if (!existingKeyPair && requireExisting) {\n throw new Error(\n \"No existing keypair found and requireExisting is true. \" +\n 'Run \"ph login\" to create one.',\n );\n }\n\n if (!existingKeyPair) {\n logger.info(\"No existing keypair found. A new one will be generated.\");\n }\n\n const renownCrypto = await new RenownCryptoBuilder()\n .withKeyPairStorage(keyStorage)\n .build();\n\n const renown = await new RenownBuilder(\"switchboard\", {})\n .withCrypto(renownCrypto)\n .withBaseUrl(baseUrl)\n .build();\n\n logger.info(\"Switchboard identity initialized: @did\", renownCrypto.did);\n\n return renown;\n}\n\n/**\n * Get the signer config for the given renown instance.\n *\n * @param renown - The renown instance\n * @param requireSignature - If true, unsigned actions are rejected\n */\nexport function getRenownSignerConfig(\n renown: IRenown,\n requireSignature?: boolean,\n): SignerConfig {\n return {\n signer: renown.signer,\n verifier: createSignatureVerifier(requireSignature),\n };\n}\n","#!/usr/bin/env node\nimport type { PGlite } from \"@electric-sql/pglite\";\nimport { getConfig } from \"@powerhousedao/config/node\";\nimport { ReactorInstrumentation } from \"@powerhousedao/opentelemetry-instrumentation-reactor\";\nimport { AtomicNodeFs } from \"@powerhousedao/pglite-fs\";\nimport {\n EventBus,\n REACTOR_SCHEMA,\n ReactorBuilder,\n ReactorClientBuilder,\n driveCollectionId,\n parseDriveUrl,\n type Database,\n type JwtHandler,\n} from \"@powerhousedao/reactor\";\nimport { createRemoteAttachmentService } from \"@powerhousedao/reactor-attachments\";\nimport {\n HttpPackageLoader,\n ImportPackageLoader,\n PackageManagementService,\n PackagesSubgraph,\n initializeAndStartAPI,\n type IPackageLoader,\n} from \"@powerhousedao/reactor-api\";\nimport { httpsHooksPath } from \"@powerhousedao/reactor-api/https-hooks\";\nimport {\n VitePackageLoader,\n createViteLogger,\n startViteServer,\n} from \"@powerhousedao/reactor-api/vite\";\nimport {\n DriveNodeView,\n NodeProcessor,\n ReactorDriveClient,\n createReactorDriveResolvers,\n reactorDriveSubgraphTypeDefs,\n type ReactorDriveDatabase,\n} from \"@powerhousedao/reactor-drive\";\nimport type { DocumentModelModule } from \"@powerhousedao/shared/document-model\";\nimport { processorFactory as vetraProcessorFactory } from \"@powerhousedao/vetra/processors\";\nimport { applySwitchboardReactorDefaults } from \"./builder-defaults.mjs\";\nimport type { IRenown } from \"@renown/sdk/node\";\nimport * as Sentry from \"@sentry/node\";\nimport { childLogger, setLogLevel, type ILogger } from \"document-model\";\nimport dotenv from \"dotenv\";\nimport { Kysely, PostgresDialect } from \"kysely\";\nimport { promises as fs } from \"node:fs\";\nimport { register } from \"node:module\";\nimport net from \"node:net\";\nimport path from \"path\";\nimport { Pool } from \"pg\";\nimport { registerAttachmentRoutes } from \"./attachments/index.js\";\nimport { initFeatureFlags } from \"./feature-flags.js\";\nimport { ClosablePGliteDialect } from \"./pglite-dialect.js\";\nimport { migratePgliteDir } from \"./pglite-migration.js\";\nimport {\n CURRENT_PG_MAJOR,\n isSupportedMajor,\n loadPGliteModule,\n readPgVersionFile,\n type SupportedPgMajor,\n} from \"./pglite-version.js\";\nimport { getRenownSignerConfig, initRenown } from \"./renown.js\";\nimport type { StartServerOptions, SwitchboardReactor } from \"./types.js\";\nimport {\n addDefaultDrive,\n addDefaultReactorDrive,\n isPostgresUrl,\n} from \"./utils.mjs\";\n\nconst defaultLogger = childLogger([\"switchboard\"]);\n\nconst LogLevel = (process.env.LOG_LEVEL as ILogger[\"level\"] | \"\") || \"info\";\nsetLogLevel(LogLevel);\n\ndotenv.config();\n\n// Feature flag constants\nconst DOCUMENT_MODEL_SUBGRAPHS_ENABLED = \"DOCUMENT_MODEL_SUBGRAPHS_ENABLED\";\nconst DOCUMENT_MODEL_SUBGRAPHS_ENABLED_DEFAULT = true;\nconst REQUIRE_SIGNATURES = \"REQUIRE_SIGNATURES\";\nconst REQUIRE_SIGNATURES_DEFAULT = false;\n\nconst DEFAULT_PORT = process.env.PORT ? Number(process.env.PORT) : 4001;\n\n// How many ports forward from the requested one we will try before giving up.\nconst PORT_FALLBACK_ATTEMPTS = 20;\n\n// AtomicNodeFs needs a flush interval to coalesce writes into a single disk write (only used locally)\nconst PGLITE_FLUSH_INTERVAL_MS = (() => {\n const raw = process.env.PGLITE_FLUSH_INTERVAL_MS;\n if (raw === undefined) return 100;\n const parsed = Number(raw);\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : 100;\n})();\n\n// When set, runs both reactor and read-model PGLite instances purely in-memory.\nconst PGLITE_IN_MEMORY = process.env.PH_PGLITE_IN_MEMORY === \"1\";\n\n/**\n * Attempt to bind a throwaway TCP server to the given port. Resolves true if\n * the port is free, false if the OS reports it in use. Any other error is\n * surfaced so we don't silently mask real issues (permissions, bad host, โ€ฆ).\n */\nexport function isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve, reject) => {\n const tester = net.createServer();\n tester.once(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\" || err.code === \"EACCES\") {\n resolve(false);\n } else {\n reject(err);\n }\n });\n tester.once(\"listening\", () => {\n tester.close(() => resolve(true));\n });\n // Bind on the unspecified IPv6 address so we detect collisions with both\n // IPv6 and IPv4 listeners (Node maps `::` to dual-stack on most systems).\n tester.listen({ port, host: \"::\" });\n });\n}\n\nasync function resolveServerPort(\n requested: number,\n strictPort: boolean,\n logger: ILogger,\n): Promise<number> {\n if (strictPort) return requested;\n for (let i = 0; i < PORT_FALLBACK_ATTEMPTS; i++) {\n const candidate = requested + i;\n if (await isPortAvailable(candidate)) {\n if (candidate !== requested) {\n logger.info(\n `Port ${requested} is in use. Falling back to port ${candidate}.`,\n );\n }\n return candidate;\n }\n }\n // Couldn't find a free port in the window; let the caller surface the\n // original EADDRINUSE when the real bind attempts runs.\n return requested;\n}\n\nasync function createReactorKysely(opts: {\n reactorDbUrl: string | undefined;\n reactorPgliteDir: string | null;\n reactorPgliteMajor: SupportedPgMajor | null;\n inMemory: boolean;\n flushIntervalMs: number;\n logger: ILogger;\n}): Promise<Kysely<Database>> {\n const {\n reactorDbUrl,\n reactorPgliteDir,\n reactorPgliteMajor,\n inMemory,\n flushIntervalMs,\n logger,\n } = opts;\n\n if (reactorDbUrl && isPostgresUrl(reactorDbUrl)) {\n const connectionString = reactorDbUrl.includes(\"?\")\n ? reactorDbUrl\n : `${reactorDbUrl}?sslmode=disable`;\n const pool = new Pool({ connectionString });\n logger.info(\"Using PostgreSQL for reactor storage\");\n return new Kysely<Database>({ dialect: new PostgresDialect({ pool }) });\n }\n\n if (!reactorPgliteDir || reactorPgliteMajor === null) {\n throw new Error(\"Reactor PGLite directory not resolved\");\n }\n const { PGlite } = await loadPGliteModule(reactorPgliteMajor);\n const pglite = inMemory\n ? new PGlite()\n : new PGlite({\n fs: new AtomicNodeFs(reactorPgliteDir, { logger, flushIntervalMs }),\n });\n logger.info(\n inMemory\n ? `Using in-memory PGlite (PG${reactorPgliteMajor}) for reactor storage [PH_PGLITE_IN_MEMORY=1]`\n : `Using PGlite (PG${reactorPgliteMajor}) for reactor storage at ${reactorPgliteDir}`,\n );\n return new Kysely<Database>({ dialect: new ClosablePGliteDialect(pglite) });\n}\n\n/** Derive the remote attachment service config for switchboard's own `/attachments/*` API. */\nexport function deriveAttachmentServiceConfig(\n options: Pick<StartServerOptions, \"attachmentServiceUrl\" | \"https\">,\n serverPort: number,\n renown: IRenown | null,\n): { remoteUrl: string; jwtHandler: JwtHandler | undefined } {\n const protocol = options.https ? \"https\" : \"http\";\n const remoteUrl =\n options.attachmentServiceUrl ??\n process.env.PH_SWITCHBOARD_PUBLIC_URL ??\n `${protocol}://localhost:${serverPort}`;\n const jwtHandler: JwtHandler | undefined = renown\n ? async (url: string) =>\n renown.user\n ? renown.getBearerToken({ expiresIn: 10, aud: url })\n : undefined\n : undefined;\n return { remoteUrl, jwtHandler };\n}\n\nasync function initServer(\n serverPort: number,\n options: StartServerOptions,\n renown: IRenown | null,\n) {\n const {\n dev,\n packages = [],\n remoteDrives = [],\n logger = defaultLogger,\n } = options;\n logger.level = LogLevel;\n const dbPath =\n options.dbPath ??\n process.env.DATABASE_URL ??\n process.env.PH_SWITCHBOARD_DATABASE_URL;\n\n // use postgres url for read model storage if available, otherwise use local PGlite path\n const readModelPath = dbPath || \".ph/read-storage\";\n\n const reactorDbUrl =\n options.dbPath ??\n process.env.PH_REACTOR_DATABASE_URL ??\n process.env.PH_SWITCHBOARD_DATABASE_URL;\n // When the caller passes in a reactor, the reactor-side PGLite dir is\n // unused โ€” the caller owns its own storage. Only the read-model dir is\n // still needed by reactor-api itself.\n const reactorPath = reactorDbUrl || \"./.ph/reactor-storage\";\n const reactorPgliteDir = options.reactor\n ? null\n : !reactorDbUrl || !isPostgresUrl(reactorDbUrl)\n ? reactorPath\n : null;\n const readModelPgliteDir =\n !dbPath || !isPostgresUrl(dbPath) ? readModelPath : null;\n\n // PGLite version pre-flight: when PH_FORCE_PG_VERSION is set, wipe local\n // data dirs and re-initdb at the chosen version. Otherwise detect on-disk\n // PG_VERSION and either migrate (when --migrate-pglite is set) or warn and\n // fall through to the matching legacy PGLite at runtime.\n const pgliteDirs = [reactorPgliteDir, readModelPgliteDir].filter(\n (d): d is string => d !== null,\n );\n const detectedMajors = new Map<string, number>();\n\n if (options.forcePgVersion !== undefined && pgliteDirs.length > 0) {\n if (options.migratePglite) {\n logger.warn(\n \"PH_FORCE_PG_VERSION is set; ignoring --migrate-pglite/PH_MIGRATE_PGLITE because the data dirs will be wiped.\",\n );\n }\n logger.warn(\n `PH_FORCE_PG_VERSION=${options.forcePgVersion} set; wiping PGLite data dirs and re-initializing at PG${options.forcePgVersion}.`,\n );\n for (const dir of pgliteDirs) {\n await fs.rm(dir, { recursive: true, force: true });\n logger.info(`Wiped PGLite data dir ${dir}`);\n }\n } else if (options.forcePgVersion === undefined) {\n for (const dir of pgliteDirs) {\n const major = await readPgVersionFile(dir);\n if (major !== null) detectedMajors.set(dir, major);\n }\n\n if (options.migratePglite) {\n for (const [dir, major] of detectedMajors) {\n if (major === CURRENT_PG_MAJOR) continue;\n await migratePgliteDir(dir, logger);\n // refresh detected major after a successful migration\n const after = await readPgVersionFile(dir);\n if (after !== null) detectedMajors.set(dir, after);\n }\n } else {\n for (const [dir, major] of detectedMajors) {\n if (major === CURRENT_PG_MAJOR) continue;\n logger.warn(\n `PGLite data dir at ${dir} was created with PG${major} but Switchboard ships PG${CURRENT_PG_MAJOR}. Running on legacy PGLite. Re-start with --migrate-pglite (or PH_MIGRATE_PGLITE=true) to upgrade.`,\n );\n }\n }\n }\n\n function resolvePgliteMajorForDir(dir: string): SupportedPgMajor {\n if (options.forcePgVersion !== undefined) return options.forcePgVersion;\n const detected = detectedMajors.get(dir);\n if (detected === undefined) return CURRENT_PG_MAJOR;\n if (!isSupportedMajor(detected)) {\n throw new Error(\n `Unsupported PGLite data dir at ${dir}: PG_VERSION=${detected}`,\n );\n }\n return detected;\n }\n\n const reactorPgliteMajor = reactorPgliteDir\n ? resolvePgliteMajorForDir(reactorPgliteDir)\n : null;\n const readModelPgliteMajor = readModelPgliteDir\n ? resolvePgliteMajorForDir(readModelPgliteDir)\n : null;\n\n // The reactor-api owns its own PGlite/HTTP/WS resources but has no shutdown\n // path of its own; we register `api.dispose` as a reactor shutdown hook so\n // those resources drain inside the reactor's SIGINT chain. The reference\n // is forward โ€” `initializeClient` runs (and registers the hook) before\n // `initializeAndStartAPI` returns the api โ€” so the closure reads `apiRef`\n // at hook-fire time, not at registration time.\n const apiRef: { current: { dispose: () => Promise<void> } | undefined } = {\n current: undefined,\n };\n let driveNodeView: DriveNodeView | undefined;\n\n // HTTP registry package loading\n const configPath =\n options.configFile ?? path.join(process.cwd(), \"powerhouse.config.json\");\n const config = getConfig(configPath);\n const registryUrl =\n options.registryUrl ??\n process.env.PH_REGISTRY_URL ??\n config.packageRegistryUrl;\n const registryPackages = process.env.PH_REGISTRY_PACKAGES;\n const dynamicModelLoading =\n options.dynamicModelLoading ?? process.env.DYNAMIC_MODEL_LOADING === \"true\";\n let httpLoader: HttpPackageLoader | undefined;\n\n if (registryUrl) {\n // Register HTTP/HTTPS module loader hooks for dynamic package imports\n register(httpsHooksPath, import.meta.url);\n httpLoader = new HttpPackageLoader({ registryUrl });\n registryPackages?.split(\",\").forEach((p) => {\n const name = p.trim();\n if (!packages.includes(name)) {\n packages.push(name);\n }\n });\n }\n\n const reactorLogger = logger.child([\"reactor\"]);\n const initializeClient = async (documentModels: DocumentModelModule[]) => {\n // When the caller hands us a pre-built reactor module, reuse it\n // instead of constructing one. The caller owns the reactor lifecycle\n // and must call `switchboard.shutdown()` from their own teardown to\n // drain /graphql, MCP, attachments, etc.\n if (options.reactor) {\n if (options.reactor.reactorModule) {\n const instrumentation = new ReactorInstrumentation(\n options.reactor.reactorModule,\n );\n instrumentation.start();\n reactorLogger.info(\n \"Reactor metrics instrumentation started (using caller-provided reactor)\",\n );\n }\n return { module: options.reactor };\n }\n\n const baseKysely = await createReactorKysely({\n reactorDbUrl,\n reactorPgliteDir,\n reactorPgliteMajor,\n inMemory: PGLITE_IN_MEMORY,\n flushIntervalMs: PGLITE_FLUSH_INTERVAL_MS,\n logger,\n });\n\n const maxSkipThreshold = parseInt(process.env.MAX_SKIP_THRESHOLD ?? \"\", 10);\n const hasSkipThreshold = !isNaN(maxSkipThreshold) && maxSkipThreshold > 0;\n if (hasSkipThreshold) {\n logger.info(`Reactor maxSkipThreshold set to ${maxSkipThreshold}`);\n }\n\n const reactorBuilder = new ReactorBuilder()\n .withEventBus(new EventBus())\n .withKysely(baseKysely);\n\n const clientBuilder = new ReactorClientBuilder().withReactorBuilder(\n reactorBuilder,\n );\n\n applySwitchboardReactorDefaults(reactorBuilder, clientBuilder, {\n documentModels,\n executorConfig: hasSkipThreshold ? { maxSkipThreshold } : undefined,\n documentModelLoader:\n httpLoader && dynamicModelLoading\n ? httpLoader.documentModelLoader\n : undefined,\n logger: reactorLogger,\n signer: renown\n ? getRenownSignerConfig(renown, options.identity?.requireSignatures)\n : undefined,\n });\n\n reactorBuilder.withReadModelFactory(\n async ({\n operationIndex,\n writeCache,\n processorManagerConsistencyTracker,\n }) => {\n const nodeProcessor = new NodeProcessor(\n baseKysely as unknown as Kysely<unknown>,\n REACTOR_SCHEMA,\n operationIndex,\n writeCache,\n processorManagerConsistencyTracker,\n );\n await nodeProcessor.init();\n return nodeProcessor;\n },\n );\n\n reactorBuilder.withShutdownHook(async () => {\n if (apiRef.current) await apiRef.current.dispose();\n });\n\n const module = await clientBuilder.buildModule();\n\n if (module.reactorModule) {\n const instrumentation = new ReactorInstrumentation(module.reactorModule);\n instrumentation.start();\n reactorLogger.info(\"Reactor metrics instrumentation started\");\n }\n\n const reactorDriveSchemaDb = baseKysely.withSchema(\n REACTOR_SCHEMA,\n ) as unknown as Kysely<ReactorDriveDatabase>;\n driveNodeView = new DriveNodeView(reactorDriveSchemaDb);\n const reactorDriveClient = new ReactorDriveClient({\n reactor: module.client,\n readModel: driveNodeView,\n });\n\n return { module, reactorDriveClient };\n };\n\n let defaultDriveUrl: undefined | string = undefined;\n\n // TODO get path from powerhouse config\n // start vite server if dev mode is enabled\n const basePath = process.cwd();\n const viteLogger = createViteLogger(logger);\n const vite = dev\n ? await startViteServer(process.cwd(), viteLogger)\n : undefined;\n\n // get paths to local document models\n if (!options.disableLocalPackages) {\n packages.push(basePath);\n }\n\n // create loaders\n const packageLoaders: IPackageLoader[] = [];\n if (vite) {\n packageLoaders.push(VitePackageLoader.build(vite));\n } else {\n packageLoaders.push(new ImportPackageLoader());\n }\n if (httpLoader) {\n packageLoaders.push(httpLoader);\n registryPackages?.split(\",\").forEach((p) => {\n const name = p.trim();\n if (!packages.includes(name)) {\n packages.push(name);\n }\n });\n }\n\n const apiLogger = logger.child([\"reactor-api\"]);\n // When the read-model store is on disk, hand reactor-api a factory that\n // constructs the matching PGLite (current or legacy) for the detected\n // PG_VERSION. reactor-api calls the factory synchronously, so the legacy\n // module is preloaded above.\n let pgliteFactory:\n | ((connectionString: string | undefined) => PGlite)\n | undefined;\n if (readModelPgliteDir && readModelPgliteMajor !== null) {\n const { PGlite: ReadModelPGlite } =\n await loadPGliteModule(readModelPgliteMajor);\n pgliteFactory = PGLITE_IN_MEMORY\n ? () => new ReadModelPGlite()\n : (connectionString) =>\n new ReadModelPGlite({\n fs: new AtomicNodeFs(\n connectionString ?? (readModelPgliteDir as string),\n { logger, flushIntervalMs: PGLITE_FLUSH_INTERVAL_MS },\n ),\n });\n }\n\n const api = await initializeAndStartAPI(\n initializeClient,\n {\n port: serverPort,\n dbPath: readModelPath,\n pgliteFactory,\n https: options.https,\n packageLoaders: packageLoaders.length > 0 ? packageLoaders : undefined,\n packages: packages,\n processorConfig: options.processorConfig,\n processors: {\n \"@powerhousedao/vetra\": [vetraProcessorFactory],\n },\n configFile:\n options.configFile ??\n path.join(process.cwd(), \"powerhouse.config.json\"),\n mcp: options.mcp ?? true,\n logger: apiLogger,\n enableDocumentModelSubgraphs: options.enableDocumentModelSubgraphs,\n },\n \"switchboard\",\n );\n apiRef.current = api;\n\n registerAttachmentRoutes(api);\n\n const attachmentService = createRemoteAttachmentService(\n deriveAttachmentServiceConfig(options, serverPort, renown),\n );\n\n if (process.env.SENTRY_DSN) {\n // Register Sentry error handler after all routes are established.\n // The adapter calls the framework-specific Sentry setup internally.\n api.httpAdapter.setupSentryErrorHandler(Sentry);\n }\n\n const { client, graphqlManager, documentModelRegistry } = api;\n\n // Wire up dynamic package management if HTTP loader is configured\n if (httpLoader) {\n const packageManagementService = new PackageManagementService({\n defaultRegistryUrl: registryUrl,\n httpLoader,\n documentModelRegistry,\n });\n\n packageManagementService.setOnModelsChanged(() => {\n graphqlManager.regenerateDocumentModelSubgraphs().catch(logger.error);\n });\n\n const packagesSubgraph = new PackagesSubgraph({\n relationalDb: undefined as never,\n analyticsStore: undefined as never,\n reactorClient: client,\n graphqlManager,\n syncManager: api.syncManager,\n path: graphqlManager.getBasePath(),\n packageManagementService,\n });\n\n void graphqlManager\n .registerSubgraphInstance(packagesSubgraph, \"graphql\", false)\n .then(() => graphqlManager.updateRouter())\n .catch((error: unknown) => {\n logger.error(\"Failed to register packages subgraph: @error\", error);\n });\n }\n\n if (driveNodeView) {\n graphqlManager.setAdditionalContextFields({\n readModel: driveNodeView,\n });\n\n const reactorDriveSubgraph = {\n name: \"reactor-drive\",\n path: graphqlManager.getBasePath(),\n resolvers: createReactorDriveResolvers(),\n typeDefs: reactorDriveSubgraphTypeDefs,\n reactorClient: client,\n relationalDb: undefined as never,\n };\n\n void graphqlManager\n .registerSubgraphInstance(reactorDriveSubgraph, \"graphql\", false)\n .then(() => graphqlManager.updateRouter())\n .catch((error: unknown) => {\n logger.error(\n \"Failed to register reactor-drive subgraph: @error\",\n error,\n );\n });\n }\n\n // Create default drive if provided\n if (options.drive) {\n if (!renown) {\n throw new Error(\"Cannot create default drive without Renown identity\");\n }\n\n const driveType = options.drive.documentType ?? \"powerhouse/document-drive\";\n if (driveType === \"powerhouse/reactor-drive\") {\n defaultDriveUrl = await addDefaultReactorDrive(\n client,\n options.drive,\n serverPort,\n );\n } else {\n defaultDriveUrl = await addDefaultDrive(\n client,\n options.drive,\n serverPort,\n );\n }\n }\n\n // add vite middleware after express app is initialized if applicable\n if (vite) {\n api.httpAdapter.mountRawMiddleware(vite.middlewares);\n }\n\n // Connect to remote drives AFTER packages are loaded\n if (remoteDrives.length > 0) {\n for (const remoteDriveUrl of remoteDrives) {\n let driveId: string | undefined;\n\n try {\n const { syncManager } = api;\n const parsed = parseDriveUrl(remoteDriveUrl);\n driveId = parsed.driveId;\n const remoteName = `remote-drive-${driveId}-${crypto.randomUUID()}`;\n await syncManager.add(remoteName, driveCollectionId(\"main\", driveId), {\n type: \"gql\",\n parameters: { url: parsed.graphqlEndpoint },\n });\n logger.debug(\"Remote drive @remoteDriveUrl synced\", remoteDriveUrl);\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes(\"already exists\")\n ) {\n logger.debug(\n \"Remote drive already added: @remoteDriveUrl\",\n remoteDriveUrl,\n );\n driveId = remoteDriveUrl.split(\"/\").pop();\n } else {\n logger.error(\n \"Failed to connect to remote drive @remoteDriveUrl: @error\",\n remoteDriveUrl,\n error,\n );\n }\n } finally {\n // Construct local URL once in finally block\n if (!defaultDriveUrl && driveId) {\n const protocol = options.https ? \"https\" : \"http\";\n defaultDriveUrl = `${protocol}://localhost:${serverPort}/d/${driveId}`;\n }\n }\n }\n }\n\n return {\n defaultDriveUrl,\n api,\n reactor: client,\n attachmentService,\n renown,\n port: serverPort,\n shutdown: () => api.dispose(),\n };\n}\n\n/**\n * Boot the switchboard HTTP/GraphQL/MCP stack on top of a reactor.\n *\n * If `options.reactor` is provided, the switchboard reuses it instead of\n * building its own โ€” the caller then owns the reactor's lifecycle and is\n * responsible for invoking `SwitchboardReactor.shutdown()` from their own\n * teardown / SIGINT path. The switchboard will not reach into the caller's\n * reactor; killing the reactor alone leaves the api/GraphQL/MCP resources\n * dangling until the process exits.\n *\n * When `options.reactor` is omitted, the switchboard builds and owns the\n * reactor. `shutdown()` on the returned handle only drains the api (HTTP\n * server, GraphQL, MCP, attachments); the reactor itself is torn down by\n * its own signal handlers (`withSignalHandlers`), which call `kill()` and\n * trigger the `withShutdownHook` chain that disposes the api. Programmatic\n * full teardown isn't currently exposed โ€” wire it via SIGINT/SIGTERM.\n */\nexport const startSwitchboard = async (\n options: StartServerOptions = {},\n): Promise<SwitchboardReactor> => {\n const requestedPort = options.port ?? DEFAULT_PORT;\n const logger = options.logger ?? defaultLogger;\n const serverPort = await resolveServerPort(\n requestedPort,\n options.strictPort ?? false,\n logger,\n );\n\n // Initialize feature flags\n const featureFlags = await initFeatureFlags();\n\n const enableDocumentModelSubgraphs = await featureFlags.getBooleanValue(\n DOCUMENT_MODEL_SUBGRAPHS_ENABLED,\n options.enableDocumentModelSubgraphs ??\n DOCUMENT_MODEL_SUBGRAPHS_ENABLED_DEFAULT,\n );\n\n options.enableDocumentModelSubgraphs = enableDocumentModelSubgraphs;\n\n const requireSignatures =\n options.identity?.requireSignatures ??\n (await featureFlags.getBooleanValue(\n REQUIRE_SIGNATURES,\n REQUIRE_SIGNATURES_DEFAULT,\n ));\n options.identity = { ...options.identity, requireSignatures };\n\n logger.info(\n \"Feature flags: @flags\",\n JSON.stringify(\n {\n DOCUMENT_MODEL_SUBGRAPHS_ENABLED: enableDocumentModelSubgraphs,\n REQUIRE_SIGNATURES: requireSignatures,\n },\n null,\n 2,\n ),\n );\n\n // Initialize Renown if identity options are provided or keypair exists\n let renown: IRenown | null = null;\n try {\n renown = await initRenown(options.identity);\n } catch (e) {\n logger.warn(\"Failed to initialize ConnectCrypto: @error\", e);\n if (options.identity.requireExisting) {\n throw new Error(\n 'Identity required but failed to initialize. Run \"ph login\" first.',\n { cause: e },\n );\n }\n }\n\n try {\n return await initServer(serverPort, options, renown);\n } catch (e) {\n Sentry.captureException(e);\n logger.error(\"App crashed: @error\", e);\n throw e;\n }\n};\n\nexport * from \"./types.js\";\nexport {\n applySwitchboardReactorDefaults,\n type SwitchboardReactorDefaultsOptions,\n} from \"./builder-defaults.mjs\";\n\nif (import.meta.main) {\n await startSwitchboard();\n}\n"],"names":["fs","vetraDocumentModels","logger","#pglite","fs","fs","path","vetraProcessorFactory"],"mappings":"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","debug_id":"751da8f8-c846-5e8d-9e67-a73d4fbbba42"}
package/dist/server.d.mts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { ILogger } from "document-model";
2
- import { ChannelScheme, IDocumentModelLoader, IReactorClient, ReactorBuilder, ReactorClientBuilder, ReactorClientModule, SignerConfig } from "@powerhousedao/reactor";
2
+ import { ChannelScheme, IDocumentModelLoader, IReactorClient, JwtHandler, ReactorBuilder, ReactorClientBuilder, ReactorClientModule, SignerConfig } from "@powerhousedao/reactor";
3
+ import { IAttachmentService } from "@powerhousedao/reactor-attachments";
3
4
  import { DriveInput } from "@powerhousedao/shared/document-drive";
5
+ import { IRenown } from "@renown/sdk/node";
4
6
  import { DocumentModelModule } from "@powerhousedao/shared/document-model";
5
- import { IRenown } from "@renown/sdk";
7
+ import { IRenown as IRenown$1 } from "@renown/sdk";
6
8
 
7
9
  //#region src/types.d.ts
8
10
  /**
@@ -88,7 +90,8 @@ type StartServerOptions = {
88
90
  * When configured, the switchboard will load the keypair from `ph login`
89
91
  * and can authenticate with remote services on behalf of the user.
90
92
  */
91
- identity?: IdentityOptions;
93
+ identity?: IdentityOptions; /** Base URL for the attachment service; defaults to `PH_SWITCHBOARD_PUBLIC_URL` then `http(s)://localhost:${port}`. */
94
+ attachmentServiceUrl?: string;
92
95
  mcp?: boolean;
93
96
  processorConfig?: Map<string, unknown>;
94
97
  disableLocalPackages?: boolean;
@@ -123,8 +126,9 @@ type StartServerOptions = {
123
126
  };
124
127
  type SwitchboardReactor = {
125
128
  defaultDriveUrl: string | undefined;
126
- reactor: IReactorClient; /** The Renown instance if identity was initialized */
127
- renown: IRenown | null;
129
+ reactor: IReactorClient; /** Switchboard-backed remote attachment service over its own `/attachments/*` API, for downstream consumers. */
130
+ attachmentService: IAttachmentService; /** The Renown instance if identity was initialized */
131
+ renown: IRenown$1 | null;
128
132
  /**
129
133
  * Port the HTTP server actually bound to. May differ from the requested
130
134
  * port when the requested port was in use and fallback kicked in.
@@ -193,6 +197,11 @@ declare function applySwitchboardReactorDefaults(reactorBuilder: ReactorBuilder,
193
197
  * surfaced so we don't silently mask real issues (permissions, bad host, โ€ฆ).
194
198
  */
195
199
  declare function isPortAvailable(port: number): Promise<boolean>;
200
+ /** Derive the remote attachment service config for switchboard's own `/attachments/*` API. */
201
+ declare function deriveAttachmentServiceConfig(options: Pick<StartServerOptions, "attachmentServiceUrl" | "https">, serverPort: number, renown: IRenown | null): {
202
+ remoteUrl: string;
203
+ jwtHandler: JwtHandler | undefined;
204
+ };
196
205
  /**
197
206
  * Boot the switchboard HTTP/GraphQL/MCP stack on top of a reactor.
198
207
  *
@@ -212,5 +221,5 @@ declare function isPortAvailable(port: number): Promise<boolean>;
212
221
  */
213
222
  declare const startSwitchboard: (options?: StartServerOptions) => Promise<SwitchboardReactor>;
214
223
  //#endregion
215
- export { IdentityOptions, StartServerOptions, StorageOptions, SwitchboardDriveDocumentType, SwitchboardDriveInput, SwitchboardReactor, type SwitchboardReactorDefaultsOptions, applySwitchboardReactorDefaults, isPortAvailable, startSwitchboard };
224
+ export { IdentityOptions, StartServerOptions, StorageOptions, SwitchboardDriveDocumentType, SwitchboardDriveInput, SwitchboardReactor, type SwitchboardReactorDefaultsOptions, applySwitchboardReactorDefaults, deriveAttachmentServiceConfig, isPortAvailable, startSwitchboard };
216
225
  //# sourceMappingURL=server.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.mts","names":[],"sources":["../src/types.ts","../src/builder-defaults.mts","../src/server.mts"],"mappings":";;;;;;;;;;AAcA;;;KAAY,4BAAA;;KAKA,qBAAA,GAAwB,UAAA;EAClC,YAAA,GAAe,4BAAA;AAAA;AAAA,KAGL,cAAA;EACV,IAAA;EACA,cAAA;EACA,WAAA;AAAA;AAAA,KAGU,eAAA;EANA,+DAQV,WAAA;;;;;EAKA,eAAA,YAVW;EAaX,OAAA,WAVU;EAaV,iBAAA;AAAA;AAAA,KAGU,kBAAA;EAdV;;;;;;AAcF;;;;;;;;;;;;;;EAqBE,OAAA,GAAU,mBAAA;EAeV;;;;;;EARA,WAAA;EACA,UAAA;EACA,IAAA;EAeM;;;;;EATN,UAAA;EACA,GAAA;EACA,MAAA;EACA,KAAA,GAAQ,qBAAA;EACR,QAAA;EACA,YAAA;EACA,KAAA;IAEM,OAAA;IACA,QAAA;EAAA;EAIN,IAAA;IACE,OAAA;IACA,MAAA;IACA,KAAA;IACA,MAAA;EAAA;EAyC0B;;;;;EAlC5B,QAAA,GAAW,eAAA;EACX,GAAA;EACA,eAAA,GAAkB,GAAA;EAClB,oBAAA;EACA,4BAAA;EAkCA;;;;;EA5BA,mBAAA;EACA,MAAA,GAAS,OAAA;;;;ACtGX;;;;;;EDgHE,aAAA;EClFS;;;;;;;;;ED4FT,cAAA;AAAA;AAAA,KAGU,kBAAA;EACV,eAAA;EACA,OAAA,EAAS,cAAA,ECtGT;EDwGA,MAAA,EAAQ,OAAA;ECnGR;;;;EDwGA,IAAA;EC5F6C;;;;;;;;;;;;EDyG7C,QAAA,QAAgB,OAAA;AAAA;;;KCnJN,iCAAA;;;ADAZ;;;;;ECQE,cAAA,GAAiB,mBAAA,IDHc;ECK/B,iBAAA,YDJ2C;ECM3C,kBAAA;EDNA;;;;AAGF;ECSE,aAAA,GAAgB,aAAA;EAEhB,cAAA,YDVA;ECYA,cAAA;IAAmB,gBAAA;EAAA,GDVR;ECYX,mBAAA,GAAsB,oBAAA;EACtB,MAAA,GAAS,OAAA;;;;;EAKT,MAAA,GAAS,YAAA;AAAA;;;ADCX;;;;;;;iBCWgB,+BAAA,CACd,cAAA,EAAgB,cAAA,EAChB,aAAA,EAAe,oBAAA,EACf,OAAA,GAAS,iCAAA;;;;;;;;iBC2CK,eAAA,CAAgB,IAAA,WAAe,OAAA;;;;;AFnF/C;;;;;;;;;AAIA;;;;cE4nBa,gBAAA,GACX,OAAA,GAAS,kBAAA,KACR,OAAA,CAAQ,kBAAA"}
1
+ {"version":3,"file":"server.d.mts","names":[],"sources":["../src/types.ts","../src/builder-defaults.mts","../src/server.mts"],"mappings":";;;;;;;;;;;;;AAeA;;KAAY,4BAAA;;KAKA,qBAAA,GAAwB,UAAA;EAClC,YAAA,GAAe,4BAAA;AAAA;AAAA,KAGL,cAAA;EACV,IAAA;EACA,cAAA;EACA,WAAA;AAAA;AAAA,KAGU,eAAA;EATiC,+DAW3C,WAAA;EARwB;;;;EAaxB,eAAA,YAVA;EAaA,OAAA,WAbW;EAgBX,iBAAA;AAAA;AAAA,KAGU,kBAAA;EAhBe;;;;;;;AAgB3B;;;;;;;;;;;;;EAqBE,OAAA,GAAU,mBAAA;EASV;;;;;;EAFA,WAAA;EACA,UAAA;EACA,IAAA;EAcM;;;;;EARN,UAAA;EACA,GAAA;EACA,MAAA;EACA,KAAA,GAAQ,qBAAA;EACR,QAAA;EACA,YAAA;EACA,KAAA;IAEM,OAAA;IACA,QAAA;EAAA;EAIN,IAAA;IACE,OAAA;IACA,MAAA;IACA,KAAA;IACA,MAAA;EAAA;EAwCY;AAGhB;;;;EApCE,QAAA,GAAW,eAAA,EA0CH;EAxCR,oBAAA;EACA,GAAA;EACA,eAAA,GAAkB,GAAA;EAClB,oBAAA;EACA,4BAAA;EAgCS;;;;;EA1BT,mBAAA;EACA,MAAA,GAAS,OAAA;EA+CO;;;;;;ACxJlB;;;EDmHE,aAAA;ECjGgB;;;;;;;;;ED2GhB,cAAA;AAAA;AAAA,KAGU,kBAAA;EACV,eAAA;EACA,OAAA,EAAS,cAAA,EC5GU;ED8GnB,iBAAA,EAAmB,kBAAA,EC5GG;ED8GtB,MAAA,EAAQ,SAAA;EC7GC;;;;EDkHT,IAAA;ECjGc;;;;;;;;;;;;ED8Gd,QAAA,QAAgB,OAAA;AAAA;;;KCxJN,iCAAA;;;;;ADCZ;;;ECOE,cAAA,GAAiB,mBAAA,IDPqB;ECStC,iBAAA,YDJ+B;ECM/B,kBAAA;EDL2C;;;;;ECW3C,aAAA,GAAgB,aAAA,UDRN;ECUV,cAAA;EAEA,cAAA;IAAmB,gBAAA;EAAA,GDTnB;ECWA,mBAAA,GAAsB,oBAAA;EACtB,MAAA,GAAS,OAAA;EDTC;;;;ECcV,MAAA,GAAS,YAAA;AAAA;;;;;ADEX;;;;;iBCUgB,+BAAA,CACd,cAAA,EAAgB,cAAA,EAChB,aAAA,EAAe,oBAAA,EACf,OAAA,GAAS,iCAAA;;;;;;;;iBC6CK,eAAA,CAAgB,IAAA,WAAe,OAAA;;iBAqF/B,6BAAA,CACd,OAAA,EAAS,IAAA,CAAK,kBAAA,qCACd,UAAA,UACA,MAAA,EAAQ,OAAA;EACL,SAAA;EAAmB,UAAA,EAAY,UAAA;AAAA;;;;;;;;;AFzKpC;;;;;;;;;cEspBa,gBAAA,GACX,OAAA,GAAS,kBAAA,KACR,OAAA,CAAQ,kBAAA"}
package/dist/server.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { n as startSwitchboard, r as applySwitchboardReactorDefaults, t as isPortAvailable } from "./server-bMFA4VKj.mjs";
2
+ import { i as applySwitchboardReactorDefaults, n as isPortAvailable, r as startSwitchboard, t as deriveAttachmentServiceConfig } from "./server-DcQv9aK4.mjs";
3
3
  import "./utils-BVNg1DRI.mjs";
4
- export { applySwitchboardReactorDefaults, isPortAvailable, startSwitchboard };
5
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="bc45ef41-a766-58da-8c6a-e6dd9d6d0010")}catch(e){}}();
6
- //# debugId=bc45ef41-a766-58da-8c6a-e6dd9d6d0010
4
+ export { applySwitchboardReactorDefaults, deriveAttachmentServiceConfig, isPortAvailable, startSwitchboard };
5
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="30be1b56-bcdd-5ccf-9a81-706a389e7ff5")}catch(e){}}();
6
+ //# debugId=30be1b56-bcdd-5ccf-9a81-706a389e7ff5
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@powerhousedao/switchboard",
3
3
  "type": "module",
4
- "version": "6.1.0-dev.4",
4
+ "version": "6.1.0-dev.6",
5
5
  "main": "dist/index.mjs",
6
6
  "exports": {
7
7
  ".": {
@@ -62,17 +62,17 @@
62
62
  "kysely-pglite-dialect": "1.2.0",
63
63
  "pg": "8.18.0",
64
64
  "vite": "8.0.8",
65
- "@powerhousedao/config": "6.1.0-dev.4",
66
- "@powerhousedao/opentelemetry-instrumentation-reactor": "6.1.0-dev.4",
67
- "@powerhousedao/reactor": "6.1.0-dev.4",
68
- "@powerhousedao/vetra": "6.1.0-dev.4",
69
- "@powerhousedao/reactor-api": "6.1.0-dev.4",
70
- "@powerhousedao/shared": "6.1.0-dev.4",
71
- "@powerhousedao/reactor-attachments": "6.1.0-dev.4",
72
- "@powerhousedao/reactor-drive": "6.1.0-dev.4",
73
- "@renown/sdk": "6.1.0-dev.4",
74
- "document-model": "6.1.0-dev.4",
75
- "@powerhousedao/pglite-fs": "6.1.0-dev.4"
65
+ "@powerhousedao/opentelemetry-instrumentation-reactor": "6.1.0-dev.6",
66
+ "@powerhousedao/config": "6.1.0-dev.6",
67
+ "@powerhousedao/reactor": "6.1.0-dev.6",
68
+ "@powerhousedao/vetra": "6.1.0-dev.6",
69
+ "@powerhousedao/shared": "6.1.0-dev.6",
70
+ "@powerhousedao/reactor-api": "6.1.0-dev.6",
71
+ "@powerhousedao/reactor-attachments": "6.1.0-dev.6",
72
+ "@renown/sdk": "6.1.0-dev.6",
73
+ "document-model": "6.1.0-dev.6",
74
+ "@powerhousedao/reactor-drive": "6.1.0-dev.6",
75
+ "@powerhousedao/pglite-fs": "6.1.0-dev.6"
76
76
  },
77
77
  "devDependencies": {
78
78
  "@types/express": "^4.17.25",
@@ -0,0 +1,170 @@
1
+ import { PGlite } from "@electric-sql/pglite";
2
+ import type { API } from "@powerhousedao/reactor-api";
3
+ import { createHttpAdapter } from "@powerhousedao/reactor-api";
4
+ import {
5
+ AttachmentBuilder,
6
+ type AttachmentBuildResult,
7
+ createRemoteAttachmentService,
8
+ } from "@powerhousedao/reactor-attachments";
9
+ import type { IRenown } from "@renown/sdk/node";
10
+ import { Kysely } from "kysely";
11
+ import { PGliteDialect } from "kysely-pglite-dialect";
12
+ import { mkdtemp, rm } from "node:fs/promises";
13
+ import type { Server } from "node:http";
14
+ import { tmpdir } from "node:os";
15
+ import { join } from "node:path";
16
+ import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
17
+ import { registerAttachmentRoutes } from "../../src/attachments/index.js";
18
+ import { deriveAttachmentServiceConfig } from "../../src/server.mjs";
19
+
20
+ describe("deriveAttachmentServiceConfig", () => {
21
+ const ORIGINAL_PUBLIC_URL = process.env.PH_SWITCHBOARD_PUBLIC_URL;
22
+
23
+ afterEach(() => {
24
+ if (ORIGINAL_PUBLIC_URL === undefined) {
25
+ delete process.env.PH_SWITCHBOARD_PUBLIC_URL;
26
+ } else {
27
+ process.env.PH_SWITCHBOARD_PUBLIC_URL = ORIGINAL_PUBLIC_URL;
28
+ }
29
+ });
30
+
31
+ it("defaults to http://localhost:${port} with no auth when renown is null", () => {
32
+ delete process.env.PH_SWITCHBOARD_PUBLIC_URL;
33
+ const config = deriveAttachmentServiceConfig({}, 4001, null);
34
+ expect(config.remoteUrl).toBe("http://localhost:4001");
35
+ expect(config.jwtHandler).toBeUndefined();
36
+ });
37
+
38
+ it("uses https when options.https is set", () => {
39
+ delete process.env.PH_SWITCHBOARD_PUBLIC_URL;
40
+ const config = deriveAttachmentServiceConfig({ https: true }, 4443, null);
41
+ expect(config.remoteUrl).toBe("https://localhost:4443");
42
+ });
43
+
44
+ it("prefers PH_SWITCHBOARD_PUBLIC_URL over the localhost default", () => {
45
+ process.env.PH_SWITCHBOARD_PUBLIC_URL = "https://sb.example.com";
46
+ const config = deriveAttachmentServiceConfig({}, 4001, null);
47
+ expect(config.remoteUrl).toBe("https://sb.example.com");
48
+ });
49
+
50
+ it("prefers the explicit attachmentServiceUrl option above all else", () => {
51
+ process.env.PH_SWITCHBOARD_PUBLIC_URL = "https://sb.example.com";
52
+ const config = deriveAttachmentServiceConfig(
53
+ { attachmentServiceUrl: "https://override.example.com" },
54
+ 4001,
55
+ null,
56
+ );
57
+ expect(config.remoteUrl).toBe("https://override.example.com");
58
+ });
59
+
60
+ it("builds a jwtHandler from renown that scopes the token to the request url", async () => {
61
+ const calls: Array<{ expiresIn: number; aud: string }> = [];
62
+ const renown = {
63
+ user: { address: "0xabc" },
64
+ getBearerToken: async (opts: { expiresIn: number; aud: string }) => {
65
+ calls.push(opts);
66
+ return "tok-for-" + opts.aud;
67
+ },
68
+ } as unknown as IRenown;
69
+
70
+ const { jwtHandler } = deriveAttachmentServiceConfig({}, 4001, renown);
71
+ expect(jwtHandler).toBeDefined();
72
+ const token = await jwtHandler!("http://localhost:4001/attachments/x");
73
+ expect(token).toBe("tok-for-http://localhost:4001/attachments/x");
74
+ expect(calls[0]).toEqual({
75
+ expiresIn: 10,
76
+ aud: "http://localhost:4001/attachments/x",
77
+ });
78
+ });
79
+
80
+ it("returns undefined token when renown has no user identity", async () => {
81
+ const renown = {
82
+ user: null,
83
+ getBearerToken: async () => "should-not-be-called",
84
+ } as unknown as IRenown;
85
+
86
+ const { jwtHandler } = deriveAttachmentServiceConfig({}, 4001, renown);
87
+ expect(jwtHandler).toBeDefined();
88
+ await expect(
89
+ jwtHandler!("http://localhost:4001/x"),
90
+ ).resolves.toBeUndefined();
91
+ });
92
+ });
93
+
94
+ describe("attachment service built from deriveAttachmentServiceConfig round-trips", () => {
95
+ let attachments: AttachmentBuildResult;
96
+ let kysely: Kysely<unknown>;
97
+ let storagePath: string;
98
+ let server: Server;
99
+ let port: number;
100
+
101
+ beforeAll(async () => {
102
+ const pglite = new PGlite();
103
+ kysely = new Kysely<unknown>({ dialect: new PGliteDialect(pglite) });
104
+ storagePath = await mkdtemp(join(tmpdir(), "switchboard-attach-cfg-"));
105
+ attachments = await new AttachmentBuilder(kysely, storagePath).build();
106
+
107
+ const { adapter } = await createHttpAdapter("express");
108
+ adapter.setupMiddleware({});
109
+ registerAttachmentRoutes({
110
+ httpAdapter: adapter,
111
+ attachments,
112
+ authService: undefined,
113
+ } as unknown as API);
114
+
115
+ server = await adapter.listen(0);
116
+ const addr = server.address();
117
+ if (!addr || typeof addr === "string") throw new Error("no addr");
118
+ port = addr.port;
119
+ });
120
+
121
+ afterAll(async () => {
122
+ await new Promise<void>((resolve) => server.close(() => resolve()));
123
+ await kysely.destroy();
124
+ await rm(storagePath, { recursive: true, force: true });
125
+ });
126
+
127
+ it("reserve -> upload -> get works against the live routes", async () => {
128
+ const config = deriveAttachmentServiceConfig(
129
+ { attachmentServiceUrl: `http://127.0.0.1:${port}` },
130
+ port,
131
+ null,
132
+ );
133
+ const service = createRemoteAttachmentService(config);
134
+
135
+ const upload = await service.reserve({
136
+ mimeType: "text/plain",
137
+ fileName: "hello.txt",
138
+ extension: "txt",
139
+ });
140
+
141
+ const payload = "switchboard attachment service";
142
+ const bytes = new TextEncoder().encode(payload);
143
+ const stream = new ReadableStream<Uint8Array>({
144
+ start(controller) {
145
+ controller.enqueue(bytes);
146
+ controller.close();
147
+ },
148
+ });
149
+ const result = await upload.send(stream);
150
+ expect(result.header.sizeBytes).toBe(bytes.byteLength);
151
+
152
+ const got = await service.get(result.ref);
153
+ expect(got.header.mimeType).toBe("text/plain");
154
+ const reader = got.body.getReader();
155
+ const chunks: Uint8Array[] = [];
156
+ for (;;) {
157
+ const { done, value } = await reader.read();
158
+ if (done) break;
159
+ chunks.push(value);
160
+ }
161
+ const total = chunks.reduce((n, c) => n + c.byteLength, 0);
162
+ const merged = new Uint8Array(total);
163
+ let off = 0;
164
+ for (const c of chunks) {
165
+ merged.set(c, off);
166
+ off += c.byteLength;
167
+ }
168
+ expect(new TextDecoder().decode(merged)).toBe(payload);
169
+ });
170
+ });
@@ -1 +0,0 @@
1
- {"version":3,"file":"server-bMFA4VKj.mjs","sources":["../src/pglite-version.ts","../src/builder-defaults.mts","../src/attachments/auth.ts","../src/attachments/mount-auth.ts","../src/attachments/routes.ts","../src/attachments/index.ts","../src/feature-flags.ts","../src/pglite-dialect.ts","../src/pglite-migration.ts","../src/renown.ts","../src/server.mts"],"sourcesContent":["import type * as CurrentPGliteModuleNs from \"@electric-sql/pglite\";\nimport { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\n\nexport const CURRENT_PG_MAJOR = 17;\nexport const SUPPORTED_PG_MAJORS = [16, 17] as const;\nexport type SupportedPgMajor = (typeof SUPPORTED_PG_MAJORS)[number];\n\ntype CurrentPGliteModule = typeof CurrentPGliteModuleNs;\n\nexport async function readPgVersionFile(\n dataDir: string,\n): Promise<number | null> {\n try {\n const raw = await fs.readFile(path.join(dataDir, \"PG_VERSION\"), \"utf8\");\n const major = parseInt(raw.trim(), 10);\n return Number.isFinite(major) ? major : null;\n } catch {\n return null;\n }\n}\n\nexport function isSupportedMajor(major: number): major is SupportedPgMajor {\n return (SUPPORTED_PG_MAJORS as readonly number[]).includes(major);\n}\n\n/**\n * Parses the `PH_FORCE_PG_VERSION` env var. Returns the validated major, or\n * `null` when the var is unset/empty. Throws on any value that is not a\n * supported major โ€” invalid configuration must fail before the server starts\n * touching disk.\n */\nexport function parseForcePgVersion(\n raw: string | undefined,\n): SupportedPgMajor | null {\n if (raw === undefined || raw.trim() === \"\") return null;\n const parsed = Number(raw);\n if (Number.isInteger(parsed) && isSupportedMajor(parsed)) return parsed;\n throw new Error(\n `PH_FORCE_PG_VERSION must be one of: ${SUPPORTED_PG_MAJORS.join(\", \")} (got: ${raw})`,\n );\n}\n\nexport async function loadPGliteModule(\n major: SupportedPgMajor,\n): Promise<CurrentPGliteModule> {\n if (major === 16) {\n return (await import(\"pglite-legacy-02\")) as unknown as CurrentPGliteModule;\n }\n return import(\"@electric-sql/pglite\");\n}\n\ntype PgDumpFn = (options: {\n pg: unknown;\n}) => Promise<{ text(): Promise<string> }>;\n\nexport async function loadPgDump(major: SupportedPgMajor): Promise<PgDumpFn> {\n if (major === 16) {\n const mod = (await import(\"pglite-tools-legacy-02/pg_dump\")) as {\n pgDump: PgDumpFn;\n };\n return mod.pgDump;\n }\n const mod = (await import(\"@electric-sql/pglite-tools/pg_dump\")) as {\n pgDump: PgDumpFn;\n };\n return mod.pgDump;\n}\n","import type { ReactorBuilder } from \"@powerhousedao/reactor\";\nimport {\n ChannelScheme,\n type IDocumentModelLoader,\n type ReactorClientBuilder,\n type SignerConfig,\n} from \"@powerhousedao/reactor\";\nimport { reactorDriveDocumentModelModule } from \"@powerhousedao/reactor-drive\";\nimport { getUniqueDocumentModels } from \"@powerhousedao/reactor-api\";\nimport { driveDocumentModelModule } from \"@powerhousedao/shared/document-drive\";\nimport type { DocumentModelModule } from \"@powerhousedao/shared/document-model\";\nimport { documentModels as vetraDocumentModels } from \"@powerhousedao/vetra\";\nimport { documentModelDocumentModelModule, type ILogger } from \"document-model\";\n\nexport type SwitchboardReactorDefaultsOptions = {\n /**\n * Additional document models to register alongside switchboard's baseline.\n * The baseline (document-model, drive, reactor-drive) is included unless\n * `includeBaseModels` is `false`; vetra models are included unless\n * `includeVetraModels` is `false`. Duplicates by id are removed via\n * `getUniqueDocumentModels`.\n */\n documentModels?: DocumentModelModule[];\n /** Default true. */\n includeBaseModels?: boolean;\n /** Default true. */\n includeVetraModels?: boolean;\n /**\n * Channel scheme. Defaults to `ChannelScheme.SWITCHBOARD`, which populates\n * `reactorModule.syncModule.syncManager` โ€” required by reactor-api. Set\n * to `false` only if the caller will configure a scheme themselves.\n */\n channelScheme?: ChannelScheme | false;\n /** Defaults to true. Set false when the caller owns SIGINT handling. */\n signalHandlers?: boolean;\n /** Executor tuning. Omit to use the reactor's own defaults. */\n executorConfig?: { maxSkipThreshold?: number };\n /** Wire dynamic document-model loading via HTTP. */\n documentModelLoader?: IDocumentModelLoader;\n logger?: ILogger;\n /**\n * Identity signer (typically from `getRenownSignerConfig`). Applied to the\n * `ReactorClientBuilder`; omit for unsigned operation.\n */\n signer?: SignerConfig;\n};\n\n/**\n * Apply switchboard's standard configuration to a reactor + client builder\n * pair. Each piece is opt-out via the options object; defaults mirror what\n * `startSwitchboard` does when building a reactor itself. Mutates both\n * builders in place.\n *\n * Does NOT touch kysely or read models โ€” callers wire those themselves\n * (see `withKysely` / `withReadModelFactory` on the reactor builder).\n */\nexport function applySwitchboardReactorDefaults(\n reactorBuilder: ReactorBuilder,\n clientBuilder: ReactorClientBuilder,\n options: SwitchboardReactorDefaultsOptions = {},\n): void {\n const baseModels =\n options.includeBaseModels !== false\n ? [\n documentModelDocumentModelModule,\n driveDocumentModelModule,\n reactorDriveDocumentModelModule,\n ]\n : [];\n const vetraModels =\n options.includeVetraModels !== false ? vetraDocumentModels : [];\n const extra = options.documentModels ?? [];\n if (baseModels.length || vetraModels.length || extra.length) {\n reactorBuilder.withDocumentModels(\n getUniqueDocumentModels(baseModels, vetraModels, extra),\n );\n }\n\n const scheme =\n options.channelScheme === undefined\n ? ChannelScheme.SWITCHBOARD\n : options.channelScheme;\n if (scheme !== false) {\n reactorBuilder.withChannelScheme(scheme);\n }\n\n if (options.signalHandlers !== false) {\n reactorBuilder.withSignalHandlers();\n }\n\n if (options.executorConfig?.maxSkipThreshold !== undefined) {\n reactorBuilder.withExecutorConfig({\n maxSkipThreshold: options.executorConfig.maxSkipThreshold,\n });\n }\n\n if (options.documentModelLoader) {\n reactorBuilder.withDocumentModelLoader(options.documentModelLoader);\n }\n\n if (options.logger) {\n reactorBuilder.withLogger(options.logger);\n }\n\n if (options.signer) {\n clientBuilder.withSigner(options.signer);\n }\n}\n","import type { AuthService } from \"@powerhousedao/reactor-api\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\n\nexport type NodeHandler = (\n req: IncomingMessage,\n res: ServerResponse,\n) => Promise<void> | void;\n\n/**\n * Wrap a Node-style handler so that, when `authService` is provided and auth is\n * enabled, the request must carry a verifiable Bearer token.\n */\nexport function requireAuth(\n authService: AuthService | undefined,\n handler: NodeHandler,\n): NodeHandler {\n if (!authService) return handler;\n\n return async (req, res) => {\n let result;\n try {\n result = await authService.verifyBearer(req.headers.authorization);\n } catch {\n res.statusCode = 500;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify({ error: \"Internal authentication error\" }));\n return;\n }\n\n if (result instanceof Response) {\n const body = await result.text();\n res.statusCode = result.status;\n const contentType = result.headers.get(\"content-type\");\n if (contentType) res.setHeader(\"Content-Type\", contentType);\n res.end(body);\n return;\n }\n\n if (result.auth_enabled && !result.user) {\n res.statusCode = 401;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify({ error: \"Authentication required\" }));\n return;\n }\n\n await handler(req, res);\n };\n}\n","import type { API } from \"@powerhousedao/reactor-api\";\nimport { requireAuth, type NodeHandler } from \"./auth.js\";\n\nexport type HttpMethod = \"DELETE\" | \"GET\" | \"HEAD\" | \"POST\" | \"PUT\";\n\n/**\n * Mount a Node-style attachment route with `requireAuth` applied unconditionally.\n * When `api.authService` is undefined (auth disabled), `requireAuth` returns the\n * handler unchanged โ€” that is the only way to opt out. To register a route\n * without auth wrapping you must call `api.httpAdapter.mountNodeRoute` directly.\n */\nexport function mountAuthenticatedNodeRoute(\n api: Pick<API, \"httpAdapter\" | \"authService\">,\n method: HttpMethod,\n path: string,\n handler: NodeHandler,\n): void {\n api.httpAdapter.mountNodeRoute(\n method,\n path,\n requireAuth(api.authService, handler),\n );\n}\n","import {\n AttachmentNotFound,\n InvalidAttachmentRef,\n ReservationNotFound,\n type AttachmentBuildResult,\n type ReserveAttachmentOptions,\n} from \"@powerhousedao/reactor-attachments\";\nimport type { AttachmentHash } from \"@powerhousedao/reactor\";\nimport { childLogger } from \"document-model\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { Readable } from \"node:stream\";\nimport type { ReadableStream as NodeReadableStream } from \"node:stream/web\";\n\nconst logger = childLogger([\"switchboard\", \"attachments\"]);\n\n// Canonical form is lowercase hex (the SHA-256 hasher emits lowercase), but\n// accept either case from the wire and normalise before lookup. This keeps\n// the API forgiving for hand-typed URLs without changing storage semantics.\nconst HASH_PATTERN = /^[a-f0-9]{64}$/i;\n// eslint-disable-next-line no-control-regex\nconst CONTROL_CHARS = /[\\x00-\\x1f\\x7f]/;\n// RFC 6838 token chars; allows optional `; param=value` pairs (token or quoted-string).\nconst MIME_TYPE_PATTERN =\n /^[!#$%&'*+\\-.^_`|~\\w]+\\/[!#$%&'*+\\-.^_`|~\\w]+(?:\\s*;\\s*[!#$%&'*+\\-.^_`|~\\w]+=(?:[!#$%&'*+\\-.^_`|~\\w]+|\"(?:[^\"\\\\\\r\\n]|\\\\[^\\r\\n])*\"))*$/;\nconst MAX_FILENAME_LEN = 255;\nconst MAX_MIMETYPE_LEN = 255;\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n res.statusCode = status;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify(body));\n}\n\nfunction sendError(res: ServerResponse, status: number, message: string): void {\n sendJson(res, status, { error: message });\n}\n\nfunction statusForError(err: unknown): number {\n if (err instanceof AttachmentNotFound) return 404;\n if (err instanceof ReservationNotFound) return 404;\n if (err instanceof InvalidAttachmentRef) return 400;\n return 500;\n}\n\nfunction sendErrorFromException(res: ServerResponse, err: unknown): void {\n const status = statusForError(err);\n if (status >= 500) {\n logger.error(\"Attachment route error: @error\", err);\n sendError(res, status, \"Internal error\");\n return;\n }\n sendError(res, status, err instanceof Error ? err.message : String(err));\n}\n\nasync function readJsonBody(\n req: IncomingMessage,\n body: unknown,\n): Promise<unknown> {\n // The Express body-parser may have already populated `body`. When that\n // happens we trust it; otherwise read the raw stream ourselves so this\n // module is independent of upstream middleware ordering.\n if (body !== undefined && body !== null && typeof body === \"object\") {\n return body;\n }\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(chunk as Buffer);\n }\n if (chunks.length === 0) return undefined;\n const text = Buffer.concat(chunks).toString(\"utf8\");\n if (text.length === 0) return undefined;\n return JSON.parse(text);\n}\n\nexport function parseReserveOptions(\n input: unknown,\n): ReserveAttachmentOptions | null {\n if (input === null || typeof input !== \"object\") return null;\n const obj = input as Record<string, unknown>;\n if (\n typeof obj.mimeType !== \"string\" ||\n obj.mimeType.length === 0 ||\n obj.mimeType.length > MAX_MIMETYPE_LEN ||\n !MIME_TYPE_PATTERN.test(obj.mimeType)\n ) {\n return null;\n }\n if (\n typeof obj.fileName !== \"string\" ||\n obj.fileName.length === 0 ||\n obj.fileName.length > MAX_FILENAME_LEN ||\n CONTROL_CHARS.test(obj.fileName)\n ) {\n return null;\n }\n let extension: string | null = null;\n if (typeof obj.extension === \"string\") {\n if (obj.extension.length === 0 || /[\\\\/]/.test(obj.extension)) return null;\n extension = obj.extension;\n } else if (obj.extension !== undefined && obj.extension !== null) {\n return null;\n }\n return {\n mimeType: obj.mimeType,\n fileName: obj.fileName,\n extension,\n };\n}\n\nexport function quoteFilename(name: string): string {\n // RFC 6266: quoted-string with internal \" and \\ escaped.\n return `\"${name.replace(/[\\\\\"]/g, \"\\\\$&\")}\"`;\n}\n\nexport function buildContentDisposition(fileName: string): string {\n // ASCII fallback: replace any byte outside printable ASCII (0x20-0x7e),\n // plus `\"` and `\\`, with `_`. Browsers fall back to this when they don't\n // grok `filename*=`; the modern parameter carries the real name.\n const ascii = fileName.replace(/[^\\x20-\\x21\\x23-\\x5b\\x5d-\\x7e]/g, \"_\");\n // RFC 5987: percent-encode UTF-8 bytes. encodeURIComponent leaves a few\n // chars that 5987 disallows in token; re-encode them.\n const encoded = encodeURIComponent(fileName).replace(\n /['()*!]/g,\n (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n return `attachment; filename=${quoteFilename(ascii)}; filename*=UTF-8''${encoded}`;\n}\n\nexport function makeReserveHandler(attachments: AttachmentBuildResult) {\n return async (\n req: IncomingMessage,\n res: ServerResponse,\n body?: unknown,\n ): Promise<void> => {\n let parsed: unknown;\n try {\n parsed = await readJsonBody(req, body);\n } catch {\n sendError(res, 400, \"Invalid JSON body\");\n return;\n }\n const opts = parseReserveOptions(parsed);\n if (!opts) {\n sendError(\n res,\n 400,\n \"Body must be { mimeType: string (type/subtype), fileName: string (no control characters, max 255 chars), extension?: string|null }\",\n );\n return;\n }\n try {\n const upload = await attachments.service.reserve(opts);\n sendJson(res, 201, { reservationId: upload.reservationId });\n } catch (err) {\n sendErrorFromException(res, err);\n }\n };\n}\n\nexport function makeUploadHandler(attachments: AttachmentBuildResult) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const reservationId = extractParam(req, \"reservationId\");\n if (!reservationId) {\n sendError(res, 400, \"Missing reservationId\");\n return;\n }\n\n let reservation;\n try {\n reservation = await attachments.reservations.get(reservationId);\n } catch (err) {\n sendErrorFromException(res, err);\n return;\n }\n\n const upload = attachments.uploadFactory.createUpload(\n reservation.reservationId,\n {\n mimeType: reservation.mimeType,\n fileName: reservation.fileName,\n extension: reservation.extension,\n },\n );\n\n const webStream = Readable.toWeb(\n req as Readable,\n ) as ReadableStream<Uint8Array>;\n\n try {\n const result = await upload.send(webStream);\n sendJson(res, 200, result);\n } catch (err) {\n sendErrorFromException(res, err);\n }\n };\n}\n\nexport function makeDownloadHandler(attachments: AttachmentBuildResult) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const hash = extractParam(req, \"hash\");\n if (!hash || !HASH_PATTERN.test(hash)) {\n sendError(res, 400, \"Invalid attachment hash\");\n return;\n }\n\n const controller = new AbortController();\n req.once(\"close\", () => controller.abort());\n\n const canonicalHash = hash.toLowerCase() as AttachmentHash;\n let response;\n try {\n response = await attachments.store.get(canonicalHash, controller.signal);\n } catch (err) {\n sendErrorFromException(res, err);\n return;\n }\n\n const { header, body } = response;\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", header.mimeType);\n res.setHeader(\"Content-Length\", String(header.sizeBytes));\n res.setHeader(\n \"Content-Disposition\",\n buildContentDisposition(header.fileName),\n );\n res.setHeader(\"Attachment-Metadata\", buildMetadataHeader(header));\n\n Readable.fromWeb(body as unknown as NodeReadableStream<Uint8Array>).pipe(\n res,\n );\n };\n}\n\nfunction buildMetadataHeader(header: {\n mimeType: string;\n fileName: string;\n sizeBytes: number;\n extension: string | null;\n createdAtUtc: string;\n lastAccessedAtUtc: string;\n}): string {\n return JSON.stringify({\n mimeType: header.mimeType,\n fileName: header.fileName,\n sizeBytes: header.sizeBytes,\n extension: header.extension,\n createdAtUtc: header.createdAtUtc,\n lastAccessedAtUtc: header.lastAccessedAtUtc,\n });\n}\n\nexport function makeStatHandler(attachments: AttachmentBuildResult) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const hash = extractParam(req, \"hash\");\n if (!hash || !HASH_PATTERN.test(hash)) {\n sendError(res, 400, \"Invalid attachment hash\");\n return;\n }\n\n const canonicalHash = hash.toLowerCase() as AttachmentHash;\n let header;\n try {\n header = await attachments.store.stat(canonicalHash);\n } catch (err) {\n sendErrorFromException(res, err);\n return;\n }\n\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", header.mimeType);\n res.setHeader(\"Content-Length\", String(header.sizeBytes));\n res.setHeader(\n \"Content-Disposition\",\n buildContentDisposition(header.fileName),\n );\n res.setHeader(\"Attachment-Metadata\", buildMetadataHeader(header));\n res.end();\n };\n}\n\nexport function makeGetReservationHandler(attachments: AttachmentBuildResult) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const reservationId = extractParam(req, \"reservationId\");\n if (!reservationId) {\n sendError(res, 400, \"Missing reservationId\");\n return;\n }\n try {\n const reservation = await attachments.reservations.get(reservationId);\n sendJson(res, 200, reservation);\n } catch (err) {\n sendErrorFromException(res, err);\n }\n };\n}\n\nexport function makeDeleteReservationHandler(\n attachments: AttachmentBuildResult,\n) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const reservationId = extractParam(req, \"reservationId\");\n if (!reservationId) {\n sendError(res, 400, \"Missing reservationId\");\n return;\n }\n try {\n await attachments.reservations.delete(reservationId);\n res.statusCode = 204;\n res.end();\n } catch (err) {\n sendErrorFromException(res, err);\n }\n };\n}\n\nfunction extractParam(req: IncomingMessage, name: string): string | undefined {\n const expressParams = (\n req as IncomingMessage & {\n params?: Record<string, string>;\n }\n ).params;\n return expressParams?.[name];\n}\n","import type { API } from \"@powerhousedao/reactor-api\";\nimport { mountAuthenticatedNodeRoute } from \"./mount-auth.js\";\nimport {\n makeDeleteReservationHandler,\n makeDownloadHandler,\n makeGetReservationHandler,\n makeReserveHandler,\n makeStatHandler,\n makeUploadHandler,\n} from \"./routes.js\";\n\nexport function registerAttachmentRoutes(api: API): void {\n const { attachments } = api;\n\n mountAuthenticatedNodeRoute(\n api,\n \"POST\",\n \"/attachments/reservations\",\n makeReserveHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"GET\",\n \"/attachments/reservations/:reservationId\",\n makeGetReservationHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"DELETE\",\n \"/attachments/reservations/:reservationId\",\n makeDeleteReservationHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"PUT\",\n \"/attachments/reservations/:reservationId\",\n makeUploadHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"HEAD\",\n \"/attachments/:hash\",\n makeStatHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"GET\",\n \"/attachments/:hash\",\n makeDownloadHandler(attachments),\n );\n}\n","import { EnvVarProvider } from \"@openfeature/env-var-provider\";\nimport { OpenFeature } from \"@openfeature/server-sdk\";\n\nexport async function initFeatureFlags() {\n // for now, we're only using env vars for feature flags\n const provider = new EnvVarProvider();\n\n await OpenFeature.setProviderAndWait(provider);\n\n return OpenFeature.getClient();\n}\n","import type { PGlite } from \"@electric-sql/pglite\";\nimport type { Driver } from \"kysely\";\nimport { PGliteDialect } from \"kysely-pglite-dialect\";\n\n// kysely-pglite-dialect's driver.destroy() only nulls its reference to the\n// PGlite client โ€” it never calls pglite.close(). Without close(), WAL is not\n// flushed and the data dir is left in a state that aborts the wasm on the\n// next open. This wrapper closes the dialect's PGlite as part of the\n// reactor's database.destroy() chain.\nexport class ClosablePGliteDialect extends PGliteDialect {\n readonly #pglite: PGlite;\n\n constructor(pglite: PGlite) {\n super(pglite);\n this.#pglite = pglite;\n }\n\n createDriver(): Driver {\n const driver = super.createDriver();\n const pglite = this.#pglite;\n const innerDestroy = driver.destroy.bind(driver);\n driver.destroy = async () => {\n await innerDestroy();\n if (!pglite.closed) {\n await pglite.close();\n }\n };\n return driver;\n }\n}\n","import type { ILogger } from \"document-model\";\nimport { promises as fs } from \"node:fs\";\nimport {\n CURRENT_PG_MAJOR,\n isSupportedMajor,\n loadPGliteModule,\n loadPgDump,\n readPgVersionFile,\n type SupportedPgMajor,\n} from \"./pglite-version.js\";\n\ntype PGliteCtor = new (\n dataDir: string,\n options?: Record<string, unknown>,\n) => {\n waitReady: Promise<void>;\n exec: (sql: string) => Promise<unknown>;\n close: () => Promise<void>;\n};\n\nfunction backupPath(dataDir: string, major: number): string {\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n return `${dataDir}.backup-pg${major}-${stamp}`;\n}\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await fs.stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction logRestoreFailure(\n dataDir: string,\n sql: string,\n err: unknown,\n logger: ILogger,\n): void {\n const errObj = err as {\n message?: string;\n position?: string | number;\n severity?: string;\n code?: string;\n detail?: string;\n where?: string;\n };\n const position =\n typeof errObj.position === \"string\"\n ? parseInt(errObj.position, 10)\n : typeof errObj.position === \"number\"\n ? errObj.position\n : NaN;\n\n logger.error(\n `[pglite-migration] Restore failed for ${dataDir}: code=${errObj.code ?? \"\"} severity=${errObj.severity ?? \"\"} message=${errObj.message ?? \"\"} sqlLength=${sql.length}`,\n );\n\n if (Number.isFinite(position) && position > 0) {\n const zeroBased = position - 1;\n const start = Math.max(0, zeroBased - 200);\n const end = Math.min(sql.length, zeroBased + 200);\n const before = sql.slice(start, zeroBased);\n const at = sql.slice(zeroBased, zeroBased + 1);\n const after = sql.slice(zeroBased + 1, end);\n logger.error(\n `[pglite-migration] SQL context around position ${position}:\\n${before}ยป${at}ยซ${after}`,\n );\n } else {\n logger.error(\n `[pglite-migration] No position info. First 2000 chars of dump:\\n${sql.slice(0, 2000)}`,\n );\n }\n}\n\n/**\n * Migrate a filesystem PGLite data directory from a legacy PG major to the\n * current one. Renames the existing dir to a timestamped backup, dumps via the\n * matching legacy `pg_dump`, restores into a fresh current-version PGLite at\n * the original path. On failure, the original dir is restored from the backup.\n *\n * No-op when the dir is missing or already at the current major.\n */\nexport async function migratePgliteDir(\n dataDir: string,\n logger: ILogger,\n): Promise<void> {\n const major = await readPgVersionFile(dataDir);\n if (major === null) {\n logger.info(\n `[pglite-migration] No PG_VERSION at ${dataDir}; skipping migration`,\n );\n return;\n }\n if (major === CURRENT_PG_MAJOR) return;\n\n if (!isSupportedMajor(major)) {\n throw new Error(\n `Unsupported legacy PGlite data dir: PG_VERSION=${major} for ${dataDir}`,\n );\n }\n\n const backupDir = backupPath(dataDir, major);\n logger.info(\n `[pglite-migration] Migrating ${dataDir} from PG${major} to PG${CURRENT_PG_MAJOR}; backup: ${backupDir}`,\n );\n\n await fs.rename(dataDir, backupDir);\n\n let sql: string;\n try {\n const [legacyMod, pgDump] = await Promise.all([\n loadPGliteModule(major as SupportedPgMajor),\n loadPgDump(major as SupportedPgMajor),\n ]);\n const LegacyPGlite = (legacyMod as unknown as { PGlite: PGliteCtor })\n .PGlite;\n const pg = new LegacyPGlite(backupDir);\n try {\n await pg.waitReady;\n const file = await pgDump({ pg });\n sql = await file.text();\n } finally {\n await pg.close();\n }\n } catch (err) {\n await rollback(dataDir, backupDir, err, logger);\n throw err;\n }\n\n try {\n const currentMod = await loadPGliteModule(CURRENT_PG_MAJOR);\n const CurrentPGlite = (currentMod as unknown as { PGlite: PGliteCtor })\n .PGlite;\n const pg = new CurrentPGlite(dataDir, { relaxedDurability: false });\n try {\n await pg.waitReady;\n try {\n await pg.exec(\"SET standard_conforming_strings = off;\");\n } catch (gucErr) {\n logger.warn(\n `[pglite-migration] Could not force standard_conforming_strings=off: ${String(gucErr)}`,\n );\n }\n try {\n await pg.exec(sql);\n } catch (execErr) {\n logRestoreFailure(dataDir, sql, execErr, logger);\n throw execErr;\n }\n } finally {\n await pg.close();\n }\n } catch (err) {\n await rollback(dataDir, backupDir, err, logger);\n throw err;\n }\n\n logger.info(\n `[pglite-migration] Migration of ${dataDir} complete. Backup retained at ${backupDir}; remove it manually once you have verified the upgrade.`,\n );\n}\n\nasync function rollback(\n dataDir: string,\n backupDir: string,\n originalError: unknown,\n logger: ILogger,\n): Promise<void> {\n try {\n if (await pathExists(dataDir)) {\n await fs.rm(dataDir, { recursive: true, force: true });\n }\n if (await pathExists(backupDir)) {\n await fs.rename(backupDir, dataDir);\n }\n } catch (rollbackErr) {\n logger.error(\n `[pglite-migration] Migration AND rollback failed for ${dataDir}. Original error: ${String(originalError)}; rollback error: ${String(rollbackErr)}; backup may still exist at ${backupDir}.`,\n );\n return;\n }\n logger.error(\n `[pglite-migration] Migration failed for ${dataDir}; rolled back from ${backupDir}. Original error: ${String(originalError)}`,\n );\n}\n","import type { SignerConfig } from \"@powerhousedao/reactor\";\nimport {\n createSignatureVerifier,\n DEFAULT_RENOWN_URL,\n NodeKeyStorage,\n RenownBuilder,\n RenownCryptoBuilder,\n type IRenown,\n} from \"@renown/sdk/node\";\nimport { childLogger } from \"document-model\";\n\nconst logger = childLogger([\"switchboard\", \"renown\"]);\n\nexport interface RenownOptions {\n /** Path to the keypair file. Defaults to .ph/.keypair.json in cwd */\n keypairPath?: string;\n /** If true, won't generate a new keypair if none exists */\n requireExisting?: boolean;\n /** Base url of the Renown instance to use */\n baseUrl?: string;\n}\n\n/**\n * Initialize Renown for the Switchboard instance.\n * This allows Switchboard to authenticate with remote services\n * using the same identity established during `ph login`.\n */\nexport async function initRenown(\n options: RenownOptions = {},\n): Promise<IRenown | null> {\n const {\n keypairPath,\n requireExisting = false,\n baseUrl = DEFAULT_RENOWN_URL,\n } = options;\n\n const keyStorage = new NodeKeyStorage(keypairPath, {\n logger,\n });\n\n // Check if we have an existing keypair\n const existingKeyPair = await keyStorage.loadKeyPair();\n\n if (!existingKeyPair && requireExisting) {\n throw new Error(\n \"No existing keypair found and requireExisting is true. \" +\n 'Run \"ph login\" to create one.',\n );\n }\n\n if (!existingKeyPair) {\n logger.info(\"No existing keypair found. A new one will be generated.\");\n }\n\n const renownCrypto = await new RenownCryptoBuilder()\n .withKeyPairStorage(keyStorage)\n .build();\n\n const renown = await new RenownBuilder(\"switchboard\", {})\n .withCrypto(renownCrypto)\n .withBaseUrl(baseUrl)\n .build();\n\n logger.info(\"Switchboard identity initialized: @did\", renownCrypto.did);\n\n return renown;\n}\n\n/**\n * Get the signer config for the given renown instance.\n *\n * @param renown - The renown instance\n * @param requireSignature - If true, unsigned actions are rejected\n */\nexport function getRenownSignerConfig(\n renown: IRenown,\n requireSignature?: boolean,\n): SignerConfig {\n return {\n signer: renown.signer,\n verifier: createSignatureVerifier(requireSignature),\n };\n}\n","#!/usr/bin/env node\nimport type { PGlite } from \"@electric-sql/pglite\";\nimport { getConfig } from \"@powerhousedao/config/node\";\nimport { ReactorInstrumentation } from \"@powerhousedao/opentelemetry-instrumentation-reactor\";\nimport { AtomicNodeFs } from \"@powerhousedao/pglite-fs\";\nimport {\n EventBus,\n REACTOR_SCHEMA,\n ReactorBuilder,\n ReactorClientBuilder,\n driveCollectionId,\n parseDriveUrl,\n type Database,\n} from \"@powerhousedao/reactor\";\nimport {\n HttpPackageLoader,\n ImportPackageLoader,\n PackageManagementService,\n PackagesSubgraph,\n initializeAndStartAPI,\n type IPackageLoader,\n} from \"@powerhousedao/reactor-api\";\nimport { httpsHooksPath } from \"@powerhousedao/reactor-api/https-hooks\";\nimport {\n VitePackageLoader,\n createViteLogger,\n startViteServer,\n} from \"@powerhousedao/reactor-api/vite\";\nimport {\n DriveNodeView,\n NodeProcessor,\n ReactorDriveClient,\n createReactorDriveResolvers,\n reactorDriveSubgraphTypeDefs,\n type ReactorDriveDatabase,\n} from \"@powerhousedao/reactor-drive\";\nimport type { DocumentModelModule } from \"@powerhousedao/shared/document-model\";\nimport { processorFactory as vetraProcessorFactory } from \"@powerhousedao/vetra/processors\";\nimport { applySwitchboardReactorDefaults } from \"./builder-defaults.mjs\";\nimport type { IRenown } from \"@renown/sdk/node\";\nimport * as Sentry from \"@sentry/node\";\nimport { childLogger, setLogLevel, type ILogger } from \"document-model\";\nimport dotenv from \"dotenv\";\nimport { Kysely, PostgresDialect } from \"kysely\";\nimport { promises as fs } from \"node:fs\";\nimport { register } from \"node:module\";\nimport net from \"node:net\";\nimport path from \"path\";\nimport { Pool } from \"pg\";\nimport { registerAttachmentRoutes } from \"./attachments/index.js\";\nimport { initFeatureFlags } from \"./feature-flags.js\";\nimport { ClosablePGliteDialect } from \"./pglite-dialect.js\";\nimport { migratePgliteDir } from \"./pglite-migration.js\";\nimport {\n CURRENT_PG_MAJOR,\n isSupportedMajor,\n loadPGliteModule,\n readPgVersionFile,\n type SupportedPgMajor,\n} from \"./pglite-version.js\";\nimport { getRenownSignerConfig, initRenown } from \"./renown.js\";\nimport type { StartServerOptions, SwitchboardReactor } from \"./types.js\";\nimport {\n addDefaultDrive,\n addDefaultReactorDrive,\n isPostgresUrl,\n} from \"./utils.mjs\";\n\nconst defaultLogger = childLogger([\"switchboard\"]);\n\nconst LogLevel = (process.env.LOG_LEVEL as ILogger[\"level\"] | \"\") || \"info\";\nsetLogLevel(LogLevel);\n\ndotenv.config();\n\n// Feature flag constants\nconst DOCUMENT_MODEL_SUBGRAPHS_ENABLED = \"DOCUMENT_MODEL_SUBGRAPHS_ENABLED\";\nconst DOCUMENT_MODEL_SUBGRAPHS_ENABLED_DEFAULT = true;\nconst REQUIRE_SIGNATURES = \"REQUIRE_SIGNATURES\";\nconst REQUIRE_SIGNATURES_DEFAULT = false;\n\nconst DEFAULT_PORT = process.env.PORT ? Number(process.env.PORT) : 4001;\n\n// How many ports forward from the requested one we will try before giving up.\nconst PORT_FALLBACK_ATTEMPTS = 20;\n\n// AtomicNodeFs needs a flush interval to coalesce writes into a single disk write (only used locally)\nconst PGLITE_FLUSH_INTERVAL_MS = (() => {\n const raw = process.env.PGLITE_FLUSH_INTERVAL_MS;\n if (raw === undefined) return 100;\n const parsed = Number(raw);\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : 100;\n})();\n\n// When set, runs both reactor and read-model PGLite instances purely in-memory.\nconst PGLITE_IN_MEMORY = process.env.PH_PGLITE_IN_MEMORY === \"1\";\n\n/**\n * Attempt to bind a throwaway TCP server to the given port. Resolves true if\n * the port is free, false if the OS reports it in use. Any other error is\n * surfaced so we don't silently mask real issues (permissions, bad host, โ€ฆ).\n */\nexport function isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve, reject) => {\n const tester = net.createServer();\n tester.once(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\" || err.code === \"EACCES\") {\n resolve(false);\n } else {\n reject(err);\n }\n });\n tester.once(\"listening\", () => {\n tester.close(() => resolve(true));\n });\n // Bind on the unspecified IPv6 address so we detect collisions with both\n // IPv6 and IPv4 listeners (Node maps `::` to dual-stack on most systems).\n tester.listen({ port, host: \"::\" });\n });\n}\n\nasync function resolveServerPort(\n requested: number,\n strictPort: boolean,\n logger: ILogger,\n): Promise<number> {\n if (strictPort) return requested;\n for (let i = 0; i < PORT_FALLBACK_ATTEMPTS; i++) {\n const candidate = requested + i;\n if (await isPortAvailable(candidate)) {\n if (candidate !== requested) {\n logger.info(\n `Port ${requested} is in use. Falling back to port ${candidate}.`,\n );\n }\n return candidate;\n }\n }\n // Couldn't find a free port in the window; let the caller surface the\n // original EADDRINUSE when the real bind attempts runs.\n return requested;\n}\n\nasync function createReactorKysely(opts: {\n reactorDbUrl: string | undefined;\n reactorPgliteDir: string | null;\n reactorPgliteMajor: SupportedPgMajor | null;\n inMemory: boolean;\n flushIntervalMs: number;\n logger: ILogger;\n}): Promise<Kysely<Database>> {\n const {\n reactorDbUrl,\n reactorPgliteDir,\n reactorPgliteMajor,\n inMemory,\n flushIntervalMs,\n logger,\n } = opts;\n\n if (reactorDbUrl && isPostgresUrl(reactorDbUrl)) {\n const connectionString = reactorDbUrl.includes(\"?\")\n ? reactorDbUrl\n : `${reactorDbUrl}?sslmode=disable`;\n const pool = new Pool({ connectionString });\n logger.info(\"Using PostgreSQL for reactor storage\");\n return new Kysely<Database>({ dialect: new PostgresDialect({ pool }) });\n }\n\n if (!reactorPgliteDir || reactorPgliteMajor === null) {\n throw new Error(\"Reactor PGLite directory not resolved\");\n }\n const { PGlite } = await loadPGliteModule(reactorPgliteMajor);\n const pglite = inMemory\n ? new PGlite()\n : new PGlite({\n fs: new AtomicNodeFs(reactorPgliteDir, { logger, flushIntervalMs }),\n });\n logger.info(\n inMemory\n ? `Using in-memory PGlite (PG${reactorPgliteMajor}) for reactor storage [PH_PGLITE_IN_MEMORY=1]`\n : `Using PGlite (PG${reactorPgliteMajor}) for reactor storage at ${reactorPgliteDir}`,\n );\n return new Kysely<Database>({ dialect: new ClosablePGliteDialect(pglite) });\n}\n\nasync function initServer(\n serverPort: number,\n options: StartServerOptions,\n renown: IRenown | null,\n) {\n const {\n dev,\n packages = [],\n remoteDrives = [],\n logger = defaultLogger,\n } = options;\n logger.level = LogLevel;\n const dbPath =\n options.dbPath ??\n process.env.DATABASE_URL ??\n process.env.PH_SWITCHBOARD_DATABASE_URL;\n\n // use postgres url for read model storage if available, otherwise use local PGlite path\n const readModelPath = dbPath || \".ph/read-storage\";\n\n const reactorDbUrl =\n options.dbPath ??\n process.env.PH_REACTOR_DATABASE_URL ??\n process.env.PH_SWITCHBOARD_DATABASE_URL;\n // When the caller passes in a reactor, the reactor-side PGLite dir is\n // unused โ€” the caller owns its own storage. Only the read-model dir is\n // still needed by reactor-api itself.\n const reactorPath = reactorDbUrl || \"./.ph/reactor-storage\";\n const reactorPgliteDir = options.reactor\n ? null\n : !reactorDbUrl || !isPostgresUrl(reactorDbUrl)\n ? reactorPath\n : null;\n const readModelPgliteDir =\n !dbPath || !isPostgresUrl(dbPath) ? readModelPath : null;\n\n // PGLite version pre-flight: when PH_FORCE_PG_VERSION is set, wipe local\n // data dirs and re-initdb at the chosen version. Otherwise detect on-disk\n // PG_VERSION and either migrate (when --migrate-pglite is set) or warn and\n // fall through to the matching legacy PGLite at runtime.\n const pgliteDirs = [reactorPgliteDir, readModelPgliteDir].filter(\n (d): d is string => d !== null,\n );\n const detectedMajors = new Map<string, number>();\n\n if (options.forcePgVersion !== undefined && pgliteDirs.length > 0) {\n if (options.migratePglite) {\n logger.warn(\n \"PH_FORCE_PG_VERSION is set; ignoring --migrate-pglite/PH_MIGRATE_PGLITE because the data dirs will be wiped.\",\n );\n }\n logger.warn(\n `PH_FORCE_PG_VERSION=${options.forcePgVersion} set; wiping PGLite data dirs and re-initializing at PG${options.forcePgVersion}.`,\n );\n for (const dir of pgliteDirs) {\n await fs.rm(dir, { recursive: true, force: true });\n logger.info(`Wiped PGLite data dir ${dir}`);\n }\n } else if (options.forcePgVersion === undefined) {\n for (const dir of pgliteDirs) {\n const major = await readPgVersionFile(dir);\n if (major !== null) detectedMajors.set(dir, major);\n }\n\n if (options.migratePglite) {\n for (const [dir, major] of detectedMajors) {\n if (major === CURRENT_PG_MAJOR) continue;\n await migratePgliteDir(dir, logger);\n // refresh detected major after a successful migration\n const after = await readPgVersionFile(dir);\n if (after !== null) detectedMajors.set(dir, after);\n }\n } else {\n for (const [dir, major] of detectedMajors) {\n if (major === CURRENT_PG_MAJOR) continue;\n logger.warn(\n `PGLite data dir at ${dir} was created with PG${major} but Switchboard ships PG${CURRENT_PG_MAJOR}. Running on legacy PGLite. Re-start with --migrate-pglite (or PH_MIGRATE_PGLITE=true) to upgrade.`,\n );\n }\n }\n }\n\n function resolvePgliteMajorForDir(dir: string): SupportedPgMajor {\n if (options.forcePgVersion !== undefined) return options.forcePgVersion;\n const detected = detectedMajors.get(dir);\n if (detected === undefined) return CURRENT_PG_MAJOR;\n if (!isSupportedMajor(detected)) {\n throw new Error(\n `Unsupported PGLite data dir at ${dir}: PG_VERSION=${detected}`,\n );\n }\n return detected;\n }\n\n const reactorPgliteMajor = reactorPgliteDir\n ? resolvePgliteMajorForDir(reactorPgliteDir)\n : null;\n const readModelPgliteMajor = readModelPgliteDir\n ? resolvePgliteMajorForDir(readModelPgliteDir)\n : null;\n\n // The reactor-api owns its own PGlite/HTTP/WS resources but has no shutdown\n // path of its own; we register `api.dispose` as a reactor shutdown hook so\n // those resources drain inside the reactor's SIGINT chain. The reference\n // is forward โ€” `initializeClient` runs (and registers the hook) before\n // `initializeAndStartAPI` returns the api โ€” so the closure reads `apiRef`\n // at hook-fire time, not at registration time.\n const apiRef: { current: { dispose: () => Promise<void> } | undefined } = {\n current: undefined,\n };\n let driveNodeView: DriveNodeView | undefined;\n\n // HTTP registry package loading\n const configPath =\n options.configFile ?? path.join(process.cwd(), \"powerhouse.config.json\");\n const config = getConfig(configPath);\n const registryUrl =\n options.registryUrl ??\n process.env.PH_REGISTRY_URL ??\n config.packageRegistryUrl;\n const registryPackages = process.env.PH_REGISTRY_PACKAGES;\n const dynamicModelLoading =\n options.dynamicModelLoading ?? process.env.DYNAMIC_MODEL_LOADING === \"true\";\n let httpLoader: HttpPackageLoader | undefined;\n\n if (registryUrl) {\n // Register HTTP/HTTPS module loader hooks for dynamic package imports\n register(httpsHooksPath, import.meta.url);\n httpLoader = new HttpPackageLoader({ registryUrl });\n registryPackages?.split(\",\").forEach((p) => {\n const name = p.trim();\n if (!packages.includes(name)) {\n packages.push(name);\n }\n });\n }\n\n const reactorLogger = logger.child([\"reactor\"]);\n const initializeClient = async (documentModels: DocumentModelModule[]) => {\n // When the caller hands us a pre-built reactor module, reuse it\n // instead of constructing one. The caller owns the reactor lifecycle\n // and must call `switchboard.shutdown()` from their own teardown to\n // drain /graphql, MCP, attachments, etc.\n if (options.reactor) {\n if (options.reactor.reactorModule) {\n const instrumentation = new ReactorInstrumentation(\n options.reactor.reactorModule,\n );\n instrumentation.start();\n reactorLogger.info(\n \"Reactor metrics instrumentation started (using caller-provided reactor)\",\n );\n }\n return { module: options.reactor };\n }\n\n const baseKysely = await createReactorKysely({\n reactorDbUrl,\n reactorPgliteDir,\n reactorPgliteMajor,\n inMemory: PGLITE_IN_MEMORY,\n flushIntervalMs: PGLITE_FLUSH_INTERVAL_MS,\n logger,\n });\n\n const maxSkipThreshold = parseInt(process.env.MAX_SKIP_THRESHOLD ?? \"\", 10);\n const hasSkipThreshold = !isNaN(maxSkipThreshold) && maxSkipThreshold > 0;\n if (hasSkipThreshold) {\n logger.info(`Reactor maxSkipThreshold set to ${maxSkipThreshold}`);\n }\n\n const reactorBuilder = new ReactorBuilder()\n .withEventBus(new EventBus())\n .withKysely(baseKysely);\n\n const clientBuilder = new ReactorClientBuilder().withReactorBuilder(\n reactorBuilder,\n );\n\n applySwitchboardReactorDefaults(reactorBuilder, clientBuilder, {\n documentModels,\n executorConfig: hasSkipThreshold ? { maxSkipThreshold } : undefined,\n documentModelLoader:\n httpLoader && dynamicModelLoading\n ? httpLoader.documentModelLoader\n : undefined,\n logger: reactorLogger,\n signer: renown\n ? getRenownSignerConfig(renown, options.identity?.requireSignatures)\n : undefined,\n });\n\n reactorBuilder.withReadModelFactory(\n async ({\n operationIndex,\n writeCache,\n processorManagerConsistencyTracker,\n }) => {\n const nodeProcessor = new NodeProcessor(\n baseKysely as unknown as Kysely<unknown>,\n REACTOR_SCHEMA,\n operationIndex,\n writeCache,\n processorManagerConsistencyTracker,\n );\n await nodeProcessor.init();\n return nodeProcessor;\n },\n );\n\n reactorBuilder.withShutdownHook(async () => {\n if (apiRef.current) await apiRef.current.dispose();\n });\n\n const module = await clientBuilder.buildModule();\n\n if (module.reactorModule) {\n const instrumentation = new ReactorInstrumentation(module.reactorModule);\n instrumentation.start();\n reactorLogger.info(\"Reactor metrics instrumentation started\");\n }\n\n const reactorDriveSchemaDb = baseKysely.withSchema(\n REACTOR_SCHEMA,\n ) as unknown as Kysely<ReactorDriveDatabase>;\n driveNodeView = new DriveNodeView(reactorDriveSchemaDb);\n const reactorDriveClient = new ReactorDriveClient({\n reactor: module.client,\n readModel: driveNodeView,\n });\n\n return { module, reactorDriveClient };\n };\n\n let defaultDriveUrl: undefined | string = undefined;\n\n // TODO get path from powerhouse config\n // start vite server if dev mode is enabled\n const basePath = process.cwd();\n const viteLogger = createViteLogger(logger);\n const vite = dev\n ? await startViteServer(process.cwd(), viteLogger)\n : undefined;\n\n // get paths to local document models\n if (!options.disableLocalPackages) {\n packages.push(basePath);\n }\n\n // create loaders\n const packageLoaders: IPackageLoader[] = [];\n if (vite) {\n packageLoaders.push(VitePackageLoader.build(vite));\n } else {\n packageLoaders.push(new ImportPackageLoader());\n }\n if (httpLoader) {\n packageLoaders.push(httpLoader);\n registryPackages?.split(\",\").forEach((p) => {\n const name = p.trim();\n if (!packages.includes(name)) {\n packages.push(name);\n }\n });\n }\n\n const apiLogger = logger.child([\"reactor-api\"]);\n // When the read-model store is on disk, hand reactor-api a factory that\n // constructs the matching PGLite (current or legacy) for the detected\n // PG_VERSION. reactor-api calls the factory synchronously, so the legacy\n // module is preloaded above.\n let pgliteFactory:\n | ((connectionString: string | undefined) => PGlite)\n | undefined;\n if (readModelPgliteDir && readModelPgliteMajor !== null) {\n const { PGlite: ReadModelPGlite } =\n await loadPGliteModule(readModelPgliteMajor);\n pgliteFactory = PGLITE_IN_MEMORY\n ? () => new ReadModelPGlite()\n : (connectionString) =>\n new ReadModelPGlite({\n fs: new AtomicNodeFs(\n connectionString ?? (readModelPgliteDir as string),\n { logger, flushIntervalMs: PGLITE_FLUSH_INTERVAL_MS },\n ),\n });\n }\n\n const api = await initializeAndStartAPI(\n initializeClient,\n {\n port: serverPort,\n dbPath: readModelPath,\n pgliteFactory,\n https: options.https,\n packageLoaders: packageLoaders.length > 0 ? packageLoaders : undefined,\n packages: packages,\n processorConfig: options.processorConfig,\n processors: {\n \"@powerhousedao/vetra\": [vetraProcessorFactory],\n },\n configFile:\n options.configFile ??\n path.join(process.cwd(), \"powerhouse.config.json\"),\n mcp: options.mcp ?? true,\n logger: apiLogger,\n enableDocumentModelSubgraphs: options.enableDocumentModelSubgraphs,\n },\n \"switchboard\",\n );\n apiRef.current = api;\n\n registerAttachmentRoutes(api);\n\n if (process.env.SENTRY_DSN) {\n // Register Sentry error handler after all routes are established.\n // The adapter calls the framework-specific Sentry setup internally.\n api.httpAdapter.setupSentryErrorHandler(Sentry);\n }\n\n const { client, graphqlManager, documentModelRegistry } = api;\n\n // Wire up dynamic package management if HTTP loader is configured\n if (httpLoader) {\n const packageManagementService = new PackageManagementService({\n defaultRegistryUrl: registryUrl,\n httpLoader,\n documentModelRegistry,\n });\n\n packageManagementService.setOnModelsChanged(() => {\n graphqlManager.regenerateDocumentModelSubgraphs().catch(logger.error);\n });\n\n const packagesSubgraph = new PackagesSubgraph({\n relationalDb: undefined as never,\n analyticsStore: undefined as never,\n reactorClient: client,\n graphqlManager,\n syncManager: api.syncManager,\n path: graphqlManager.getBasePath(),\n packageManagementService,\n });\n\n void graphqlManager\n .registerSubgraphInstance(packagesSubgraph, \"graphql\", false)\n .then(() => graphqlManager.updateRouter())\n .catch((error: unknown) => {\n logger.error(\"Failed to register packages subgraph: @error\", error);\n });\n }\n\n if (driveNodeView) {\n graphqlManager.setAdditionalContextFields({\n readModel: driveNodeView,\n });\n\n const reactorDriveSubgraph = {\n name: \"reactor-drive\",\n path: graphqlManager.getBasePath(),\n resolvers: createReactorDriveResolvers(),\n typeDefs: reactorDriveSubgraphTypeDefs,\n reactorClient: client,\n relationalDb: undefined as never,\n };\n\n void graphqlManager\n .registerSubgraphInstance(reactorDriveSubgraph, \"graphql\", false)\n .then(() => graphqlManager.updateRouter())\n .catch((error: unknown) => {\n logger.error(\n \"Failed to register reactor-drive subgraph: @error\",\n error,\n );\n });\n }\n\n // Create default drive if provided\n if (options.drive) {\n if (!renown) {\n throw new Error(\"Cannot create default drive without Renown identity\");\n }\n\n const driveType = options.drive.documentType ?? \"powerhouse/document-drive\";\n if (driveType === \"powerhouse/reactor-drive\") {\n defaultDriveUrl = await addDefaultReactorDrive(\n client,\n options.drive,\n serverPort,\n );\n } else {\n defaultDriveUrl = await addDefaultDrive(\n client,\n options.drive,\n serverPort,\n );\n }\n }\n\n // add vite middleware after express app is initialized if applicable\n if (vite) {\n api.httpAdapter.mountRawMiddleware(vite.middlewares);\n }\n\n // Connect to remote drives AFTER packages are loaded\n if (remoteDrives.length > 0) {\n for (const remoteDriveUrl of remoteDrives) {\n let driveId: string | undefined;\n\n try {\n const { syncManager } = api;\n const parsed = parseDriveUrl(remoteDriveUrl);\n driveId = parsed.driveId;\n const remoteName = `remote-drive-${driveId}-${crypto.randomUUID()}`;\n await syncManager.add(remoteName, driveCollectionId(\"main\", driveId), {\n type: \"gql\",\n parameters: { url: parsed.graphqlEndpoint },\n });\n logger.debug(\"Remote drive @remoteDriveUrl synced\", remoteDriveUrl);\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes(\"already exists\")\n ) {\n logger.debug(\n \"Remote drive already added: @remoteDriveUrl\",\n remoteDriveUrl,\n );\n driveId = remoteDriveUrl.split(\"/\").pop();\n } else {\n logger.error(\n \"Failed to connect to remote drive @remoteDriveUrl: @error\",\n remoteDriveUrl,\n error,\n );\n }\n } finally {\n // Construct local URL once in finally block\n if (!defaultDriveUrl && driveId) {\n const protocol = options.https ? \"https\" : \"http\";\n defaultDriveUrl = `${protocol}://localhost:${serverPort}/d/${driveId}`;\n }\n }\n }\n }\n\n return {\n defaultDriveUrl,\n api,\n reactor: client,\n renown,\n port: serverPort,\n shutdown: () => api.dispose(),\n };\n}\n\n/**\n * Boot the switchboard HTTP/GraphQL/MCP stack on top of a reactor.\n *\n * If `options.reactor` is provided, the switchboard reuses it instead of\n * building its own โ€” the caller then owns the reactor's lifecycle and is\n * responsible for invoking `SwitchboardReactor.shutdown()` from their own\n * teardown / SIGINT path. The switchboard will not reach into the caller's\n * reactor; killing the reactor alone leaves the api/GraphQL/MCP resources\n * dangling until the process exits.\n *\n * When `options.reactor` is omitted, the switchboard builds and owns the\n * reactor. `shutdown()` on the returned handle only drains the api (HTTP\n * server, GraphQL, MCP, attachments); the reactor itself is torn down by\n * its own signal handlers (`withSignalHandlers`), which call `kill()` and\n * trigger the `withShutdownHook` chain that disposes the api. Programmatic\n * full teardown isn't currently exposed โ€” wire it via SIGINT/SIGTERM.\n */\nexport const startSwitchboard = async (\n options: StartServerOptions = {},\n): Promise<SwitchboardReactor> => {\n const requestedPort = options.port ?? DEFAULT_PORT;\n const logger = options.logger ?? defaultLogger;\n const serverPort = await resolveServerPort(\n requestedPort,\n options.strictPort ?? false,\n logger,\n );\n\n // Initialize feature flags\n const featureFlags = await initFeatureFlags();\n\n const enableDocumentModelSubgraphs = await featureFlags.getBooleanValue(\n DOCUMENT_MODEL_SUBGRAPHS_ENABLED,\n options.enableDocumentModelSubgraphs ??\n DOCUMENT_MODEL_SUBGRAPHS_ENABLED_DEFAULT,\n );\n\n options.enableDocumentModelSubgraphs = enableDocumentModelSubgraphs;\n\n const requireSignatures =\n options.identity?.requireSignatures ??\n (await featureFlags.getBooleanValue(\n REQUIRE_SIGNATURES,\n REQUIRE_SIGNATURES_DEFAULT,\n ));\n options.identity = { ...options.identity, requireSignatures };\n\n logger.info(\n \"Feature flags: @flags\",\n JSON.stringify(\n {\n DOCUMENT_MODEL_SUBGRAPHS_ENABLED: enableDocumentModelSubgraphs,\n REQUIRE_SIGNATURES: requireSignatures,\n },\n null,\n 2,\n ),\n );\n\n // Initialize Renown if identity options are provided or keypair exists\n let renown: IRenown | null = null;\n try {\n renown = await initRenown(options.identity);\n } catch (e) {\n logger.warn(\"Failed to initialize ConnectCrypto: @error\", e);\n if (options.identity.requireExisting) {\n throw new Error(\n 'Identity required but failed to initialize. Run \"ph login\" first.',\n { cause: e },\n );\n }\n }\n\n try {\n return await initServer(serverPort, options, renown);\n } catch (e) {\n Sentry.captureException(e);\n logger.error(\"App crashed: @error\", e);\n throw e;\n }\n};\n\nexport * from \"./types.js\";\nexport {\n applySwitchboardReactorDefaults,\n type SwitchboardReactorDefaultsOptions,\n} from \"./builder-defaults.mjs\";\n\nif (import.meta.main) {\n await startSwitchboard();\n}\n"],"names":["fs","vetraDocumentModels","logger","#pglite","fs","fs","path","vetraProcessorFactory"],"mappings":"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","debug_id":"15b2a451-3254-5fb0-8fe9-4fa3c769e05c"}