@powerhousedao/switchboard 6.0.2-staging.8 → 6.1.0-dev.0

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/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
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]="5ffe851c-e77f-513a-a99b-e5f1ba344644")}catch(e){}}();
4
- import { n as startSwitchboard, r as parseForcePgVersion } from "./server-DPxV64Dh.mjs";
5
- import "./utils-DFl0ezBT.mjs";
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";
5
+ import "./utils-BVNg1DRI.mjs";
6
6
  import { metrics } from "@opentelemetry/api";
7
7
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
8
8
  import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
@@ -129,6 +129,11 @@ process.on("SIGTERM", () => {
129
129
  //#region src/config.ts
130
130
  dotenv.config();
131
131
  const { switchboard } = getConfig();
132
+ function parseDriveType(raw) {
133
+ if (!raw) return void 0;
134
+ if (raw === "powerhouse/document-drive" || raw === "powerhouse/reactor-drive") return raw;
135
+ throw new Error(`Invalid PH_DEFAULT_DRIVE_TYPE: ${raw}. Expected "powerhouse/document-drive" or "powerhouse/reactor-drive".`);
136
+ }
132
137
  const config = {
133
138
  database: { url: process.env.PH_SWITCHBOARD_DATABASE_URL ?? switchboard?.database?.url ?? "dev.db" },
134
139
  port: process.env.PH_SWITCHBOARD_PORT && !isNaN(Number(process.env.PH_SWITCHBOARD_PORT)) ? Number(process.env.PH_SWITCHBOARD_PORT) : switchboard?.port ?? 4001,
@@ -138,6 +143,7 @@ const config = {
138
143
  drive: {
139
144
  id: "powerhouse",
140
145
  slug: "powerhouse",
146
+ documentType: parseDriveType(process.env.PH_DEFAULT_DRIVE_TYPE),
141
147
  global: {
142
148
  name: "Powerhouse",
143
149
  icon: "https://ipfs.io/ipfs/QmcaTDBYn8X2psGaXe7iQ6qd8q6oqHLgxvMX9yXf7f9uP7"
@@ -215,4 +221,4 @@ startSwitchboard({
215
221
  export {};
216
222
 
217
223
  //# sourceMappingURL=index.mjs.map
218
- //# debugId=5ffe851c-e77f-513a-a99b-e5f1ba344644
224
+ //# debugId=5f3b2177-d6bb-533b-b87e-e4eb03b10c87
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../src/metrics.ts","../src/observability.mts","../src/config.ts","../src/profiler.ts","../src/index.mts"],"sourcesContent":["import { OTLPMetricExporter } from \"@opentelemetry/exporter-metrics-otlp-http\";\nimport { Resource } from \"@opentelemetry/resources\";\nimport {\n MeterProvider,\n PeriodicExportingMetricReader,\n} from \"@opentelemetry/sdk-metrics\";\nimport { childLogger } from \"document-model\";\n\nconst logger = childLogger([\"switchboard\", \"metrics\"]);\n\nexport function createMeterProviderFromEnv(env: {\n OTEL_EXPORTER_OTLP_ENDPOINT?: string;\n OTEL_METRIC_EXPORT_INTERVAL?: string;\n OTEL_SERVICE_NAME?: string;\n}): MeterProvider | undefined {\n const endpoint = env.OTEL_EXPORTER_OTLP_ENDPOINT;\n if (!endpoint) return undefined;\n\n const parsed = parseInt(env.OTEL_METRIC_EXPORT_INTERVAL ?? \"\", 10);\n const exportIntervalMillis =\n Number.isFinite(parsed) && parsed > 0 ? parsed : 5_000;\n\n const base = endpoint.replace(/\\/$/, \"\");\n const exporterUrl = base.endsWith(\"/v1/metrics\")\n ? base\n : `${base}/v1/metrics`;\n\n logger.info(`Initializing OpenTelemetry metrics exporter at: ${endpoint}`);\n const meterProvider = new MeterProvider({\n resource: new Resource({\n \"service.name\": env.OTEL_SERVICE_NAME ?? \"switchboard\",\n }),\n readers: [\n new PeriodicExportingMetricReader({\n exporter: new OTLPMetricExporter({\n url: exporterUrl,\n }),\n exportIntervalMillis,\n exportTimeoutMillis: Math.max(exportIntervalMillis - 250, 1),\n }),\n ],\n });\n logger.info(`Metrics export enabled (interval: ${exportIntervalMillis}ms)`);\n return meterProvider;\n}\n","// Single observability bootstrap: Sentry + OpenTelemetry (tracing + metrics).\n//\n// MUST be imported as the very first module in apps/switchboard/src/index.mts.\n// OpenTelemetry instrumentations register require-time hooks at module load,\n// so http/express/pg/graphql must not be imported (transitively) before this\n// file runs.\n//\n// Replaces three legacy bootstrap sites:\n// - apps/switchboard/src/server.mts top-level Sentry.init\n// - apps/switchboard/src/metrics.ts standalone MeterProvider (still exported\n// here via createMeterProviderFromEnv so its tests keep passing)\n// - packages/reactor-api/src/tracing.ts side-effect NodeSDK\nimport { metrics } from \"@opentelemetry/api\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport { ExpressInstrumentation } from \"@opentelemetry/instrumentation-express\";\nimport { GraphQLInstrumentation } from \"@opentelemetry/instrumentation-graphql\";\nimport { HttpInstrumentation } from \"@opentelemetry/instrumentation-http\";\nimport { PgInstrumentation } from \"@opentelemetry/instrumentation-pg\";\nimport { Resource } from \"@opentelemetry/resources\";\nimport type { MeterProvider } from \"@opentelemetry/sdk-metrics\";\nimport { NodeSDK } from \"@opentelemetry/sdk-node\";\nimport {\n BatchSpanProcessor,\n type SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\nimport {\n ATTR_SERVICE_NAME,\n ATTR_SERVICE_VERSION,\n} from \"@opentelemetry/semantic-conventions\";\nimport * as Sentry from \"@sentry/node\";\nimport { SentryPropagator, SentrySpanProcessor } from \"@sentry/opentelemetry\";\nimport { childLogger } from \"document-model\";\nimport type { IncomingMessage } from \"node:http\";\nimport { createMeterProviderFromEnv } from \"./metrics.js\";\n\nconst logger = childLogger([\"switchboard\", \"observability\"]);\n\nconst SERVICE_NAME = process.env.OTEL_SERVICE_NAME || \"switchboard\";\nconst SERVICE_VERSION = process.env.npm_package_version || \"unknown\";\nconst TENANT_ID = process.env.TENANT_ID || \"default\";\nconst DEPLOY_ENV = process.env.NODE_ENV || \"development\";\n\nconst TEMPO_ENDPOINT = process.env.TEMPO_ENDPOINT;\nconst SENTRY_DSN = process.env.SENTRY_DSN;\n\nconst TRACING_REQUESTED =\n process.env.ENABLE_TRACING === \"true\" ||\n process.env.NODE_ENV === \"production\";\nconst HAS_TRACE_DESTINATION = Boolean(TEMPO_ENDPOINT) || Boolean(SENTRY_DSN);\nconst TRACING_ENABLED = TRACING_REQUESTED && HAS_TRACE_DESTINATION;\n\nif (TRACING_REQUESTED && !HAS_TRACE_DESTINATION) {\n logger.warn(\n \"Tracing was requested (NODE_ENV=production or ENABLE_TRACING=true) but \" +\n \"no destination is configured — instrumentation will not run. Set \" +\n \"TEMPO_ENDPOINT (e.g. http://tempo.monitoring.svc.cluster.local:4318/v1/traces) \" +\n \"to export OTLP spans, and/or SENTRY_DSN to forward spans to Sentry.\",\n );\n}\n\n// Default 10% APM sampling — Sentry's own production guidance; overridable\n// per-deploy. Only kicks in once tracesSampleRate * (sampler decision) lands.\nconst SENTRY_TRACES_SAMPLE_RATE = parseFloat(\n process.env.SENTRY_TRACES_SAMPLE_RATE ?? \"0.1\",\n);\n\nif (SENTRY_DSN) {\n logger.info(\"Initialized Sentry with env: @env\", process.env.SENTRY_ENV);\n Sentry.init({\n dsn: SENTRY_DSN,\n environment: process.env.SENTRY_ENV,\n // Match the version tag uploaded by release-branch.yml so source maps\n // resolve. Populated by the CI (WORKSPACE_VERSION) or npm at runtime.\n release:\n process.env.SENTRY_RELEASE ||\n (process.env.npm_package_version\n ? `v${process.env.npm_package_version}`\n : undefined),\n tracesSampleRate: SENTRY_TRACES_SAMPLE_RATE,\n // When tracing is on, our NodeSDK below owns the OTel globals and Sentry\n // receives spans via SentrySpanProcessor. Skipping Sentry's bundled OTel\n // setup avoids two TracerProviders fighting over setGlobalTracerProvider.\n // When tracing is off, leave the flag unset so @sentry/node's default\n // auto-OTel still records HTTP transactions for the APM dashboard.\n skipOpenTelemetrySetup: TRACING_ENABLED,\n });\n}\n\nconst meterProvider: MeterProvider | undefined = createMeterProviderFromEnv({\n OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,\n OTEL_METRIC_EXPORT_INTERVAL: process.env.OTEL_METRIC_EXPORT_INTERVAL,\n OTEL_SERVICE_NAME: process.env.OTEL_SERVICE_NAME,\n});\nif (meterProvider) {\n // One-way door: must register before any code calls metrics.getMeter() —\n // most notably ReactorInstrumentation inside the reactor module.\n metrics.setGlobalMeterProvider(meterProvider);\n}\n\nlet sdk: NodeSDK | undefined;\n\nif (TRACING_ENABLED) {\n logger.info(`Initializing OpenTelemetry tracing for ${SERVICE_NAME}`);\n if (TEMPO_ENDPOINT) logger.info(` Tempo endpoint: ${TEMPO_ENDPOINT}`);\n if (SENTRY_DSN) logger.info(` Sentry span forwarding: enabled`);\n logger.info(` Tenant: ${TENANT_ID}`);\n\n const resource = new Resource({\n [ATTR_SERVICE_NAME]: SERVICE_NAME,\n [ATTR_SERVICE_VERSION]: SERVICE_VERSION,\n \"tenant.id\": TENANT_ID,\n \"deployment.environment\": DEPLOY_ENV,\n });\n\n const spanProcessors: SpanProcessor[] = [];\n if (TEMPO_ENDPOINT) {\n spanProcessors.push(\n new BatchSpanProcessor(new OTLPTraceExporter({ url: TEMPO_ENDPOINT })),\n );\n }\n if (SENTRY_DSN) {\n // Fan the same OTel spans into Sentry — same trace IDs as Tempo, so\n // Sentry transactions cross-link to traces in Grafana.\n spanProcessors.push(new SentrySpanProcessor());\n }\n\n sdk = new NodeSDK({\n resource,\n spanProcessors,\n textMapPropagator: SENTRY_DSN ? new SentryPropagator() : undefined,\n instrumentations: [\n new HttpInstrumentation({\n ignoreIncomingRequestHook: (req) =>\n req.url === \"/health\" || req.url === \"/ready\",\n requireParentforIncomingSpans: false,\n requireParentforOutgoingSpans: false,\n requestHook: (span, request) => {\n span.setAttribute(\n \"http.route\",\n (request as IncomingMessage).url || \"\",\n );\n },\n responseHook: (span, response) => {\n if (response.statusCode) {\n span.setAttribute(\"http.status_code\", response.statusCode);\n }\n },\n }),\n new ExpressInstrumentation({\n requestHook: (span, info) => {\n if (info.route) span.setAttribute(\"http.route\", info.route);\n },\n }),\n new GraphQLInstrumentation({ mergeItems: true, allowValues: true }),\n new PgInstrumentation({ enhancedDatabaseReporting: true }),\n ],\n });\n sdk.start();\n if (SENTRY_DSN && typeof Sentry.validateOpenTelemetrySetup === \"function\") {\n Sentry.validateOpenTelemetrySetup();\n }\n logger.info(\"OpenTelemetry tracing initialized\");\n}\n\nasync function shutdown() {\n await Promise.race([\n Promise.all([\n meterProvider?.shutdown().catch(() => undefined),\n sdk?.shutdown().catch(() => undefined),\n ]),\n new Promise<void>((resolve) => setTimeout(resolve, 5_000)),\n ]);\n}\n\nprocess.on(\"SIGINT\", () => {\n void shutdown().finally(() => process.exit(0));\n});\nprocess.on(\"SIGTERM\", () => {\n void shutdown().finally(() => process.exit(0));\n});\n\nexport { meterProvider, sdk };\n","import dotenv from \"dotenv\";\ndotenv.config();\n\nimport { getConfig } from \"@powerhousedao/config/node\";\nimport type { DriveInput } from \"@powerhousedao/shared/document-drive\";\nimport { parseForcePgVersion } from \"./pglite-version.js\";\nconst phConfig = getConfig();\nconst { switchboard } = phConfig;\ninterface Config {\n database: {\n url: string;\n };\n port: number;\n mcp: boolean;\n migratePglite: boolean;\n forcePgVersion: 16 | 17 | null;\n drive: DriveInput;\n}\nexport const config: Config = {\n database: {\n // url: process.env.PH_SWITCHBOARD_DATABASE_URL ?? switchboard?.database?.url ?? \"dev.db\",\n url:\n process.env.PH_SWITCHBOARD_DATABASE_URL ??\n switchboard?.database?.url ??\n \"dev.db\",\n },\n port:\n process.env.PH_SWITCHBOARD_PORT &&\n !isNaN(Number(process.env.PH_SWITCHBOARD_PORT))\n ? Number(process.env.PH_SWITCHBOARD_PORT)\n : (switchboard?.port ?? 4001),\n mcp: true,\n migratePglite: process.env.PH_MIGRATE_PGLITE === \"true\",\n forcePgVersion: parseForcePgVersion(process.env.PH_FORCE_PG_VERSION),\n drive: {\n id: \"powerhouse\",\n slug: \"powerhouse\",\n global: {\n name: \"Powerhouse\",\n icon: \"https://ipfs.io/ipfs/QmcaTDBYn8X2psGaXe7iQ6qd8q6oqHLgxvMX9yXf7f9uP7\",\n },\n local: {\n availableOffline: true,\n listeners: [],\n sharingType: \"public\",\n triggers: [],\n },\n },\n};\n","import type { PyroscopeConfig } from \"@pyroscope/nodejs\";\n\nexport async function initProfilerFromEnv(env: typeof process.env) {\n const {\n PYROSCOPE_SERVER_ADDRESS: serverAddress,\n PYROSCOPE_APPLICATION_NAME: appName,\n PYROSCOPE_USER: basicAuthUser,\n PYROSCOPE_PASSWORD: basicAuthPassword,\n PYROSCOPE_WALL_ENABLED: wallEnabled,\n PYROSCOPE_HEAP_ENABLED: heapEnabled,\n } = env;\n\n const options: PyroscopeConfig = {\n serverAddress,\n appName,\n basicAuthUser,\n basicAuthPassword,\n // Wall profiling captures wall-clock time (includes async I/O waits)\n // This shows GraphQL resolvers even when waiting for database\n wall: {\n samplingDurationMs: 10000, // 10 second sampling windows\n samplingIntervalMicros: 10000, // 10ms sampling interval (100 samples/sec)\n collectCpuTime: true, // Also collect CPU time alongside wall time\n },\n // Heap profiling for memory allocation tracking\n heap: {\n samplingIntervalBytes: 512 * 1024, // Sample every 512KB allocated\n stackDepth: 64, // Capture deeper stacks for better context\n },\n };\n return initProfiler(options, {\n wallEnabled: wallEnabled !== \"false\",\n heapEnabled: heapEnabled === \"true\",\n });\n}\n\ninterface ProfilerFlags {\n wallEnabled?: boolean;\n heapEnabled?: boolean;\n}\n\nexport async function initProfiler(\n options?: PyroscopeConfig,\n flags: ProfilerFlags = { wallEnabled: true, heapEnabled: false },\n) {\n console.log(\"Initializing Pyroscope profiler at:\", options?.serverAddress);\n console.log(\" Wall profiling:\", flags.wallEnabled ? \"enabled\" : \"disabled\");\n console.log(\" Heap profiling:\", flags.heapEnabled ? \"enabled\" : \"disabled\");\n\n const { default: Pyroscope } = await import(\"@pyroscope/nodejs\");\n Pyroscope.init(options);\n\n // Start wall profiling (captures async I/O time - shows resolvers)\n if (flags.wallEnabled) {\n Pyroscope.startWallProfiling();\n }\n\n // Start CPU profiling (captures CPU-bound work)\n Pyroscope.startCpuProfiling();\n\n // Optionally start heap profiling (memory allocations)\n if (flags.heapEnabled) {\n Pyroscope.startHeapProfiling();\n }\n}\n","#!/usr/bin/env node\n// Observability MUST load before any module that imports http/express/pg/graphql\n// so OpenTelemetry's require-time hooks can patch them. It also owns Sentry\n// init and the SIGINT/SIGTERM flush.\nimport \"./observability.mjs\";\n\nimport * as Sentry from \"@sentry/node\";\nimport { childLogger } from \"document-model\";\nimport { config } from \"./config.js\";\nimport { initProfilerFromEnv } from \"./profiler.js\";\nimport { startSwitchboard } from \"./server.mjs\";\n\nconst logger = childLogger([\"switchboard\"]);\n\nfunction ensureNodeVersion(minVersion = \"24\") {\n const version = process.versions.node;\n if (!version) {\n return;\n }\n\n if (version < minVersion) {\n console.error(\n `Node version ${minVersion} or higher is required. Current version: ${version}`,\n );\n process.exit(1);\n }\n}\n// Ensure minimum Node.js version\nensureNodeVersion(\"24\");\n\n// Each subgraph registers its own SIGINT/SIGTERM listeners, and the count\n// scales with dynamically-loaded document models beyond the default cap of 10.\nprocess.setMaxListeners(0);\n\nif (process.env.PYROSCOPE_SERVER_ADDRESS) {\n try {\n await initProfilerFromEnv(process.env);\n } catch (e) {\n Sentry.captureException(e);\n logger.error(\"Error starting profiler: @error\", e);\n }\n}\n\nconst cliMigratePglite = process.argv.slice(2).includes(\"--migrate-pglite\");\n\nstartSwitchboard({\n ...config,\n migratePglite: cliMigratePglite || config.migratePglite,\n forcePgVersion: config.forcePgVersion ?? undefined,\n}).catch(console.error);\n"],"names":["logger","logger"],"mappings":"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","debug_id":"5ffe851c-e77f-513a-a99b-e5f1ba344644"}
1
+ {"version":3,"file":"index.mjs","sources":["../src/metrics.ts","../src/observability.mts","../src/config.ts","../src/profiler.ts","../src/index.mts"],"sourcesContent":["import { OTLPMetricExporter } from \"@opentelemetry/exporter-metrics-otlp-http\";\nimport { Resource } from \"@opentelemetry/resources\";\nimport {\n MeterProvider,\n PeriodicExportingMetricReader,\n} from \"@opentelemetry/sdk-metrics\";\nimport { childLogger } from \"document-model\";\n\nconst logger = childLogger([\"switchboard\", \"metrics\"]);\n\nexport function createMeterProviderFromEnv(env: {\n OTEL_EXPORTER_OTLP_ENDPOINT?: string;\n OTEL_METRIC_EXPORT_INTERVAL?: string;\n OTEL_SERVICE_NAME?: string;\n}): MeterProvider | undefined {\n const endpoint = env.OTEL_EXPORTER_OTLP_ENDPOINT;\n if (!endpoint) return undefined;\n\n const parsed = parseInt(env.OTEL_METRIC_EXPORT_INTERVAL ?? \"\", 10);\n const exportIntervalMillis =\n Number.isFinite(parsed) && parsed > 0 ? parsed : 5_000;\n\n const base = endpoint.replace(/\\/$/, \"\");\n const exporterUrl = base.endsWith(\"/v1/metrics\")\n ? base\n : `${base}/v1/metrics`;\n\n logger.info(`Initializing OpenTelemetry metrics exporter at: ${endpoint}`);\n const meterProvider = new MeterProvider({\n resource: new Resource({\n \"service.name\": env.OTEL_SERVICE_NAME ?? \"switchboard\",\n }),\n readers: [\n new PeriodicExportingMetricReader({\n exporter: new OTLPMetricExporter({\n url: exporterUrl,\n }),\n exportIntervalMillis,\n exportTimeoutMillis: Math.max(exportIntervalMillis - 250, 1),\n }),\n ],\n });\n logger.info(`Metrics export enabled (interval: ${exportIntervalMillis}ms)`);\n return meterProvider;\n}\n","// Single observability bootstrap: Sentry + OpenTelemetry (tracing + metrics).\n//\n// MUST be imported as the very first module in apps/switchboard/src/index.mts.\n// OpenTelemetry instrumentations register require-time hooks at module load,\n// so http/express/pg/graphql must not be imported (transitively) before this\n// file runs.\n//\n// Replaces three legacy bootstrap sites:\n// - apps/switchboard/src/server.mts top-level Sentry.init\n// - apps/switchboard/src/metrics.ts standalone MeterProvider (still exported\n// here via createMeterProviderFromEnv so its tests keep passing)\n// - packages/reactor-api/src/tracing.ts side-effect NodeSDK\nimport { metrics } from \"@opentelemetry/api\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport { ExpressInstrumentation } from \"@opentelemetry/instrumentation-express\";\nimport { GraphQLInstrumentation } from \"@opentelemetry/instrumentation-graphql\";\nimport { HttpInstrumentation } from \"@opentelemetry/instrumentation-http\";\nimport { PgInstrumentation } from \"@opentelemetry/instrumentation-pg\";\nimport { Resource } from \"@opentelemetry/resources\";\nimport type { MeterProvider } from \"@opentelemetry/sdk-metrics\";\nimport { NodeSDK } from \"@opentelemetry/sdk-node\";\nimport {\n BatchSpanProcessor,\n type SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\nimport {\n ATTR_SERVICE_NAME,\n ATTR_SERVICE_VERSION,\n} from \"@opentelemetry/semantic-conventions\";\nimport * as Sentry from \"@sentry/node\";\nimport { SentryPropagator, SentrySpanProcessor } from \"@sentry/opentelemetry\";\nimport { childLogger } from \"document-model\";\nimport type { IncomingMessage } from \"node:http\";\nimport { createMeterProviderFromEnv } from \"./metrics.js\";\n\nconst logger = childLogger([\"switchboard\", \"observability\"]);\n\nconst SERVICE_NAME = process.env.OTEL_SERVICE_NAME || \"switchboard\";\nconst SERVICE_VERSION = process.env.npm_package_version || \"unknown\";\nconst TENANT_ID = process.env.TENANT_ID || \"default\";\nconst DEPLOY_ENV = process.env.NODE_ENV || \"development\";\n\nconst TEMPO_ENDPOINT = process.env.TEMPO_ENDPOINT;\nconst SENTRY_DSN = process.env.SENTRY_DSN;\n\nconst TRACING_REQUESTED =\n process.env.ENABLE_TRACING === \"true\" ||\n process.env.NODE_ENV === \"production\";\nconst HAS_TRACE_DESTINATION = Boolean(TEMPO_ENDPOINT) || Boolean(SENTRY_DSN);\nconst TRACING_ENABLED = TRACING_REQUESTED && HAS_TRACE_DESTINATION;\n\nif (TRACING_REQUESTED && !HAS_TRACE_DESTINATION) {\n logger.warn(\n \"Tracing was requested (NODE_ENV=production or ENABLE_TRACING=true) but \" +\n \"no destination is configured — instrumentation will not run. Set \" +\n \"TEMPO_ENDPOINT (e.g. http://tempo.monitoring.svc.cluster.local:4318/v1/traces) \" +\n \"to export OTLP spans, and/or SENTRY_DSN to forward spans to Sentry.\",\n );\n}\n\n// Default 10% APM sampling — Sentry's own production guidance; overridable\n// per-deploy. Only kicks in once tracesSampleRate * (sampler decision) lands.\nconst SENTRY_TRACES_SAMPLE_RATE = parseFloat(\n process.env.SENTRY_TRACES_SAMPLE_RATE ?? \"0.1\",\n);\n\nif (SENTRY_DSN) {\n logger.info(\"Initialized Sentry with env: @env\", process.env.SENTRY_ENV);\n Sentry.init({\n dsn: SENTRY_DSN,\n environment: process.env.SENTRY_ENV,\n // Match the version tag uploaded by release-branch.yml so source maps\n // resolve. Populated by the CI (WORKSPACE_VERSION) or npm at runtime.\n release:\n process.env.SENTRY_RELEASE ||\n (process.env.npm_package_version\n ? `v${process.env.npm_package_version}`\n : undefined),\n tracesSampleRate: SENTRY_TRACES_SAMPLE_RATE,\n // When tracing is on, our NodeSDK below owns the OTel globals and Sentry\n // receives spans via SentrySpanProcessor. Skipping Sentry's bundled OTel\n // setup avoids two TracerProviders fighting over setGlobalTracerProvider.\n // When tracing is off, leave the flag unset so @sentry/node's default\n // auto-OTel still records HTTP transactions for the APM dashboard.\n skipOpenTelemetrySetup: TRACING_ENABLED,\n });\n}\n\nconst meterProvider: MeterProvider | undefined = createMeterProviderFromEnv({\n OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,\n OTEL_METRIC_EXPORT_INTERVAL: process.env.OTEL_METRIC_EXPORT_INTERVAL,\n OTEL_SERVICE_NAME: process.env.OTEL_SERVICE_NAME,\n});\nif (meterProvider) {\n // One-way door: must register before any code calls metrics.getMeter() —\n // most notably ReactorInstrumentation inside the reactor module.\n metrics.setGlobalMeterProvider(meterProvider);\n}\n\nlet sdk: NodeSDK | undefined;\n\nif (TRACING_ENABLED) {\n logger.info(`Initializing OpenTelemetry tracing for ${SERVICE_NAME}`);\n if (TEMPO_ENDPOINT) logger.info(` Tempo endpoint: ${TEMPO_ENDPOINT}`);\n if (SENTRY_DSN) logger.info(` Sentry span forwarding: enabled`);\n logger.info(` Tenant: ${TENANT_ID}`);\n\n const resource = new Resource({\n [ATTR_SERVICE_NAME]: SERVICE_NAME,\n [ATTR_SERVICE_VERSION]: SERVICE_VERSION,\n \"tenant.id\": TENANT_ID,\n \"deployment.environment\": DEPLOY_ENV,\n });\n\n const spanProcessors: SpanProcessor[] = [];\n if (TEMPO_ENDPOINT) {\n spanProcessors.push(\n new BatchSpanProcessor(new OTLPTraceExporter({ url: TEMPO_ENDPOINT })),\n );\n }\n if (SENTRY_DSN) {\n // Fan the same OTel spans into Sentry — same trace IDs as Tempo, so\n // Sentry transactions cross-link to traces in Grafana.\n spanProcessors.push(new SentrySpanProcessor());\n }\n\n sdk = new NodeSDK({\n resource,\n spanProcessors,\n textMapPropagator: SENTRY_DSN ? new SentryPropagator() : undefined,\n instrumentations: [\n new HttpInstrumentation({\n ignoreIncomingRequestHook: (req) =>\n req.url === \"/health\" || req.url === \"/ready\",\n requireParentforIncomingSpans: false,\n requireParentforOutgoingSpans: false,\n requestHook: (span, request) => {\n span.setAttribute(\n \"http.route\",\n (request as IncomingMessage).url || \"\",\n );\n },\n responseHook: (span, response) => {\n if (response.statusCode) {\n span.setAttribute(\"http.status_code\", response.statusCode);\n }\n },\n }),\n new ExpressInstrumentation({\n requestHook: (span, info) => {\n if (info.route) span.setAttribute(\"http.route\", info.route);\n },\n }),\n new GraphQLInstrumentation({ mergeItems: true, allowValues: true }),\n new PgInstrumentation({ enhancedDatabaseReporting: true }),\n ],\n });\n sdk.start();\n if (SENTRY_DSN && typeof Sentry.validateOpenTelemetrySetup === \"function\") {\n Sentry.validateOpenTelemetrySetup();\n }\n logger.info(\"OpenTelemetry tracing initialized\");\n}\n\nasync function shutdown() {\n await Promise.race([\n Promise.all([\n meterProvider?.shutdown().catch(() => undefined),\n sdk?.shutdown().catch(() => undefined),\n ]),\n new Promise<void>((resolve) => setTimeout(resolve, 5_000)),\n ]);\n}\n\nprocess.on(\"SIGINT\", () => {\n void shutdown().finally(() => process.exit(0));\n});\nprocess.on(\"SIGTERM\", () => {\n void shutdown().finally(() => process.exit(0));\n});\n\nexport { meterProvider, sdk };\n","import dotenv from \"dotenv\";\ndotenv.config();\n\nimport { getConfig } from \"@powerhousedao/config/node\";\nimport { parseForcePgVersion } from \"./pglite-version.js\";\nimport type {\n SwitchboardDriveDocumentType,\n SwitchboardDriveInput,\n} from \"./types.js\";\nconst phConfig = getConfig();\nconst { switchboard } = phConfig;\ninterface Config {\n database: {\n url: string;\n };\n port: number;\n mcp: boolean;\n migratePglite: boolean;\n forcePgVersion: 16 | 17 | null;\n drive: SwitchboardDriveInput;\n}\n\nfunction parseDriveType(\n raw: string | undefined,\n): SwitchboardDriveDocumentType | undefined {\n if (!raw) return undefined;\n if (raw === \"powerhouse/document-drive\" || raw === \"powerhouse/reactor-drive\")\n return raw;\n throw new Error(\n `Invalid PH_DEFAULT_DRIVE_TYPE: ${raw}. Expected \"powerhouse/document-drive\" or \"powerhouse/reactor-drive\".`,\n );\n}\n\nexport const config: Config = {\n database: {\n // url: process.env.PH_SWITCHBOARD_DATABASE_URL ?? switchboard?.database?.url ?? \"dev.db\",\n url:\n process.env.PH_SWITCHBOARD_DATABASE_URL ??\n switchboard?.database?.url ??\n \"dev.db\",\n },\n port:\n process.env.PH_SWITCHBOARD_PORT &&\n !isNaN(Number(process.env.PH_SWITCHBOARD_PORT))\n ? Number(process.env.PH_SWITCHBOARD_PORT)\n : (switchboard?.port ?? 4001),\n mcp: true,\n migratePglite: process.env.PH_MIGRATE_PGLITE === \"true\",\n forcePgVersion: parseForcePgVersion(process.env.PH_FORCE_PG_VERSION),\n drive: {\n id: \"powerhouse\",\n slug: \"powerhouse\",\n documentType: parseDriveType(process.env.PH_DEFAULT_DRIVE_TYPE),\n global: {\n name: \"Powerhouse\",\n icon: \"https://ipfs.io/ipfs/QmcaTDBYn8X2psGaXe7iQ6qd8q6oqHLgxvMX9yXf7f9uP7\",\n },\n local: {\n availableOffline: true,\n listeners: [],\n sharingType: \"public\",\n triggers: [],\n },\n },\n};\n","import type { PyroscopeConfig } from \"@pyroscope/nodejs\";\n\nexport async function initProfilerFromEnv(env: typeof process.env) {\n const {\n PYROSCOPE_SERVER_ADDRESS: serverAddress,\n PYROSCOPE_APPLICATION_NAME: appName,\n PYROSCOPE_USER: basicAuthUser,\n PYROSCOPE_PASSWORD: basicAuthPassword,\n PYROSCOPE_WALL_ENABLED: wallEnabled,\n PYROSCOPE_HEAP_ENABLED: heapEnabled,\n } = env;\n\n const options: PyroscopeConfig = {\n serverAddress,\n appName,\n basicAuthUser,\n basicAuthPassword,\n // Wall profiling captures wall-clock time (includes async I/O waits)\n // This shows GraphQL resolvers even when waiting for database\n wall: {\n samplingDurationMs: 10000, // 10 second sampling windows\n samplingIntervalMicros: 10000, // 10ms sampling interval (100 samples/sec)\n collectCpuTime: true, // Also collect CPU time alongside wall time\n },\n // Heap profiling for memory allocation tracking\n heap: {\n samplingIntervalBytes: 512 * 1024, // Sample every 512KB allocated\n stackDepth: 64, // Capture deeper stacks for better context\n },\n };\n return initProfiler(options, {\n wallEnabled: wallEnabled !== \"false\",\n heapEnabled: heapEnabled === \"true\",\n });\n}\n\ninterface ProfilerFlags {\n wallEnabled?: boolean;\n heapEnabled?: boolean;\n}\n\nexport async function initProfiler(\n options?: PyroscopeConfig,\n flags: ProfilerFlags = { wallEnabled: true, heapEnabled: false },\n) {\n console.log(\"Initializing Pyroscope profiler at:\", options?.serverAddress);\n console.log(\" Wall profiling:\", flags.wallEnabled ? \"enabled\" : \"disabled\");\n console.log(\" Heap profiling:\", flags.heapEnabled ? \"enabled\" : \"disabled\");\n\n const { default: Pyroscope } = await import(\"@pyroscope/nodejs\");\n Pyroscope.init(options);\n\n // Start wall profiling (captures async I/O time - shows resolvers)\n if (flags.wallEnabled) {\n Pyroscope.startWallProfiling();\n }\n\n // Start CPU profiling (captures CPU-bound work)\n Pyroscope.startCpuProfiling();\n\n // Optionally start heap profiling (memory allocations)\n if (flags.heapEnabled) {\n Pyroscope.startHeapProfiling();\n }\n}\n","#!/usr/bin/env node\n// Observability MUST load before any module that imports http/express/pg/graphql\n// so OpenTelemetry's require-time hooks can patch them. It also owns Sentry\n// init and the SIGINT/SIGTERM flush.\nimport \"./observability.mjs\";\n\nimport * as Sentry from \"@sentry/node\";\nimport { childLogger } from \"document-model\";\nimport { config } from \"./config.js\";\nimport { initProfilerFromEnv } from \"./profiler.js\";\nimport { startSwitchboard } from \"./server.mjs\";\n\nconst logger = childLogger([\"switchboard\"]);\n\nfunction ensureNodeVersion(minVersion = \"24\") {\n const version = process.versions.node;\n if (!version) {\n return;\n }\n\n if (version < minVersion) {\n console.error(\n `Node version ${minVersion} or higher is required. Current version: ${version}`,\n );\n process.exit(1);\n }\n}\n// Ensure minimum Node.js version\nensureNodeVersion(\"24\");\n\n// Each subgraph registers its own SIGINT/SIGTERM listeners, and the count\n// scales with dynamically-loaded document models beyond the default cap of 10.\nprocess.setMaxListeners(0);\n\nif (process.env.PYROSCOPE_SERVER_ADDRESS) {\n try {\n await initProfilerFromEnv(process.env);\n } catch (e) {\n Sentry.captureException(e);\n logger.error(\"Error starting profiler: @error\", e);\n }\n}\n\nconst cliMigratePglite = process.argv.slice(2).includes(\"--migrate-pglite\");\n\nstartSwitchboard({\n ...config,\n migratePglite: cliMigratePglite || config.migratePglite,\n forcePgVersion: config.forcePgVersion ?? undefined,\n}).catch(console.error);\n"],"names":["logger","logger"],"mappings":"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","debug_id":"5f3b2177-d6bb-533b-b87e-e4eb03b10c87"}
package/dist/migrate.mjs CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
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]="089fa7e5-3ec6-5326-9efc-05a1101d2bd2")}catch(e){}}();
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]="faf76c26-cfd8-5dc4-94c3-936a42663059")}catch(e){}}();
4
4
  import dotenv from "dotenv";
5
5
  import { getConfig } from "@powerhousedao/config/node";
6
6
  import { REACTOR_SCHEMA, getMigrationStatus, runMigrations } from "@powerhousedao/reactor";
7
+ import { getReactorDriveMigrationStatus, runReactorDriveMigrations } from "@powerhousedao/reactor-drive";
7
8
  import { Kysely, PostgresDialect } from "kysely";
8
9
  import { Pool } from "pg";
9
10
  //#region src/migrate.mts
@@ -26,24 +27,42 @@ async function main() {
26
27
  if (command === "status") {
27
28
  console.log("\nChecking migration status...");
28
29
  const migrations = await getMigrationStatus(db, REACTOR_SCHEMA);
29
- console.log("\nMigration Status:");
30
- console.log("=================");
30
+ console.log("\nReactor Migration Status:");
31
+ console.log("=========================");
31
32
  for (const migration of migrations) {
32
33
  const status = migration.executedAt ? `[OK] Executed at ${migration.executedAt.toISOString()}` : "[--] Pending";
33
34
  console.log(`${status} - ${migration.name}`);
34
35
  }
36
+ const driveMigrations = await getReactorDriveMigrationStatus(db, REACTOR_SCHEMA);
37
+ console.log("\nReactor-Drive Migration Status:");
38
+ console.log("===============================");
39
+ for (const migration of driveMigrations) {
40
+ const status = migration.executedAt ? `[OK] Executed at ${migration.executedAt.toISOString()}` : "[--] Pending";
41
+ console.log(`${status} - ${migration.name}`);
42
+ }
35
43
  } else {
36
- console.log("\nRunning migrations...");
44
+ console.log("\nRunning reactor migrations...");
37
45
  const result = await runMigrations(db, REACTOR_SCHEMA);
38
46
  if (!result.success) {
39
47
  console.error("Migration failed:", result.error?.message);
40
48
  process.exit(1);
41
49
  }
42
- if (result.migrationsExecuted.length === 0) console.log("No migrations to run - database is up to date");
50
+ if (result.migrationsExecuted.length === 0) console.log("No reactor migrations to run - database is up to date");
43
51
  else {
44
- console.log(`Successfully executed ${result.migrationsExecuted.length} migration(s):`);
52
+ console.log(`Successfully executed ${result.migrationsExecuted.length} reactor migration(s):`);
45
53
  for (const name of result.migrationsExecuted) console.log(` - ${name}`);
46
54
  }
55
+ console.log("\nRunning reactor-drive migrations...");
56
+ const driveResult = await runReactorDriveMigrations(db, REACTOR_SCHEMA);
57
+ if (!driveResult.success) {
58
+ console.error("Reactor-drive migration failed:", driveResult.error?.message);
59
+ process.exit(1);
60
+ }
61
+ if (driveResult.migrationsExecuted.length === 0) console.log("No reactor-drive migrations to run - database is up to date");
62
+ else {
63
+ console.log(`Successfully executed ${driveResult.migrationsExecuted.length} reactor-drive migration(s):`);
64
+ for (const name of driveResult.migrationsExecuted) console.log(` - ${name}`);
65
+ }
47
66
  }
48
67
  } catch (error) {
49
68
  console.error("Error:", error instanceof Error ? error.message : String(error));
@@ -57,4 +76,4 @@ main();
57
76
  export {};
58
77
 
59
78
  //# sourceMappingURL=migrate.mjs.map
60
- //# debugId=089fa7e5-3ec6-5326-9efc-05a1101d2bd2
79
+ //# debugId=faf76c26-cfd8-5dc4-94c3-936a42663059
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.mjs","sources":["../src/migrate.mts"],"sourcesContent":["#!/usr/bin/env node\nimport dotenv from \"dotenv\";\ndotenv.config();\n\nimport { Kysely, PostgresDialect } from \"kysely\";\nimport { Pool } from \"pg\";\nimport {\n runMigrations,\n getMigrationStatus,\n REACTOR_SCHEMA,\n} from \"@powerhousedao/reactor\";\nimport { getConfig } from \"@powerhousedao/config/node\";\n\nfunction isPostgresUrl(url: string): boolean {\n return url.startsWith(\"postgresql://\") || url.startsWith(\"postgres://\");\n}\n\nasync function main() {\n const command = process.argv[2];\n const config = getConfig();\n\n const dbPath =\n process.env.PH_SWITCHBOARD_DATABASE_URL ??\n process.env.PH_REACTOR_DATABASE_URL ??\n process.env.DATABASE_URL ??\n config.switchboard?.database?.url;\n\n if (!dbPath || !isPostgresUrl(dbPath)) {\n console.log(\"No PostgreSQL URL configured. Skipping migrations.\");\n console.log(\"(PGlite migrations are handled automatically on startup)\");\n return;\n }\n\n console.log(`Database: ${dbPath}`);\n\n const pool = new Pool({ connectionString: dbPath });\n\n const db = new Kysely<any>({\n dialect: new PostgresDialect({ pool }),\n });\n\n try {\n if (command === \"status\") {\n console.log(\"\\nChecking migration status...\");\n const migrations = await getMigrationStatus(db, REACTOR_SCHEMA);\n\n console.log(\"\\nMigration Status:\");\n console.log(\"=================\");\n\n for (const migration of migrations) {\n const status = migration.executedAt\n ? `[OK] Executed at ${migration.executedAt.toISOString()}`\n : \"[--] Pending\";\n console.log(`${status} - ${migration.name}`);\n }\n } else {\n console.log(\"\\nRunning migrations...\");\n const result = await runMigrations(db, REACTOR_SCHEMA);\n\n if (!result.success) {\n console.error(\"Migration failed:\", result.error?.message);\n process.exit(1);\n }\n\n if (result.migrationsExecuted.length === 0) {\n console.log(\"No migrations to run - database is up to date\");\n } else {\n console.log(\n `Successfully executed ${result.migrationsExecuted.length} migration(s):`,\n );\n for (const name of result.migrationsExecuted) {\n console.log(` - ${name}`);\n }\n }\n }\n } catch (error) {\n console.error(\n \"Error:\",\n error instanceof Error ? error.message : String(error),\n );\n process.exit(1);\n } finally {\n await db.destroy();\n }\n}\n\nvoid main();\n"],"names":[],"mappings":";;;;;;;;;AAEA,OAAO,QAAQ;AAWf,SAAS,cAAc,KAAsB;AAC3C,QAAO,IAAI,WAAW,gBAAgB,IAAI,IAAI,WAAW,cAAc;;AAGzE,eAAe,OAAO;CACpB,MAAM,UAAU,QAAQ,KAAK;CAC7B,MAAM,SAAS,WAAW;CAE1B,MAAM,SACJ,QAAQ,IAAI,+BACZ,QAAQ,IAAI,2BACZ,QAAQ,IAAI,gBACZ,OAAO,aAAa,UAAU;AAEhC,KAAI,CAAC,UAAU,CAAC,cAAc,OAAO,EAAE;AACrC,UAAQ,IAAI,qDAAqD;AACjE,UAAQ,IAAI,2DAA2D;AACvE;;AAGF,SAAQ,IAAI,aAAa,SAAS;CAIlC,MAAM,KAAK,IAAI,OAAY,EACzB,SAAS,IAAI,gBAAgB,EAAE,MAHpB,IAAI,KAAK,EAAE,kBAAkB,QAAQ,CAAC,EAGZ,CAAC,EACvC,CAAC;AAEF,KAAI;AACF,MAAI,YAAY,UAAU;AACxB,WAAQ,IAAI,iCAAiC;GAC7C,MAAM,aAAa,MAAM,mBAAmB,IAAI,eAAe;AAE/D,WAAQ,IAAI,sBAAsB;AAClC,WAAQ,IAAI,oBAAoB;AAEhC,QAAK,MAAM,aAAa,YAAY;IAClC,MAAM,SAAS,UAAU,aACrB,oBAAoB,UAAU,WAAW,aAAa,KACtD;AACJ,YAAQ,IAAI,GAAG,OAAO,KAAK,UAAU,OAAO;;SAEzC;AACL,WAAQ,IAAI,0BAA0B;GACtC,MAAM,SAAS,MAAM,cAAc,IAAI,eAAe;AAEtD,OAAI,CAAC,OAAO,SAAS;AACnB,YAAQ,MAAM,qBAAqB,OAAO,OAAO,QAAQ;AACzD,YAAQ,KAAK,EAAE;;AAGjB,OAAI,OAAO,mBAAmB,WAAW,EACvC,SAAQ,IAAI,gDAAgD;QACvD;AACL,YAAQ,IACN,yBAAyB,OAAO,mBAAmB,OAAO,gBAC3D;AACD,SAAK,MAAM,QAAQ,OAAO,mBACxB,SAAQ,IAAI,OAAO,OAAO;;;UAIzB,OAAO;AACd,UAAQ,MACN,UACA,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CACvD;AACD,UAAQ,KAAK,EAAE;WACP;AACR,QAAM,GAAG,SAAS;;;AAIjB,MAAM","debug_id":"089fa7e5-3ec6-5326-9efc-05a1101d2bd2"}
1
+ {"version":3,"file":"migrate.mjs","sources":["../src/migrate.mts"],"sourcesContent":["#!/usr/bin/env node\nimport dotenv from \"dotenv\";\ndotenv.config();\n\nimport { Kysely, PostgresDialect } from \"kysely\";\nimport { Pool } from \"pg\";\nimport {\n runMigrations,\n getMigrationStatus,\n REACTOR_SCHEMA,\n} from \"@powerhousedao/reactor\";\nimport {\n getReactorDriveMigrationStatus,\n runReactorDriveMigrations,\n} from \"@powerhousedao/reactor-drive\";\nimport { getConfig } from \"@powerhousedao/config/node\";\n\nfunction isPostgresUrl(url: string): boolean {\n return url.startsWith(\"postgresql://\") || url.startsWith(\"postgres://\");\n}\n\nasync function main() {\n const command = process.argv[2];\n const config = getConfig();\n\n const dbPath =\n process.env.PH_SWITCHBOARD_DATABASE_URL ??\n process.env.PH_REACTOR_DATABASE_URL ??\n process.env.DATABASE_URL ??\n config.switchboard?.database?.url;\n\n if (!dbPath || !isPostgresUrl(dbPath)) {\n console.log(\"No PostgreSQL URL configured. Skipping migrations.\");\n console.log(\"(PGlite migrations are handled automatically on startup)\");\n return;\n }\n\n console.log(`Database: ${dbPath}`);\n\n const pool = new Pool({ connectionString: dbPath });\n\n const db = new Kysely<any>({\n dialect: new PostgresDialect({ pool }),\n });\n\n try {\n if (command === \"status\") {\n console.log(\"\\nChecking migration status...\");\n const migrations = await getMigrationStatus(db, REACTOR_SCHEMA);\n\n console.log(\"\\nReactor Migration Status:\");\n console.log(\"=========================\");\n\n for (const migration of migrations) {\n const status = migration.executedAt\n ? `[OK] Executed at ${migration.executedAt.toISOString()}`\n : \"[--] Pending\";\n console.log(`${status} - ${migration.name}`);\n }\n\n const driveMigrations = await getReactorDriveMigrationStatus(\n db,\n REACTOR_SCHEMA,\n );\n\n console.log(\"\\nReactor-Drive Migration Status:\");\n console.log(\"===============================\");\n\n for (const migration of driveMigrations) {\n const status = migration.executedAt\n ? `[OK] Executed at ${migration.executedAt.toISOString()}`\n : \"[--] Pending\";\n console.log(`${status} - ${migration.name}`);\n }\n } else {\n console.log(\"\\nRunning reactor migrations...\");\n const result = await runMigrations(db, REACTOR_SCHEMA);\n\n if (!result.success) {\n console.error(\"Migration failed:\", result.error?.message);\n process.exit(1);\n }\n\n if (result.migrationsExecuted.length === 0) {\n console.log(\"No reactor migrations to run - database is up to date\");\n } else {\n console.log(\n `Successfully executed ${result.migrationsExecuted.length} reactor migration(s):`,\n );\n for (const name of result.migrationsExecuted) {\n console.log(` - ${name}`);\n }\n }\n\n console.log(\"\\nRunning reactor-drive migrations...\");\n const driveResult = await runReactorDriveMigrations(db, REACTOR_SCHEMA);\n\n if (!driveResult.success) {\n console.error(\n \"Reactor-drive migration failed:\",\n driveResult.error?.message,\n );\n process.exit(1);\n }\n\n if (driveResult.migrationsExecuted.length === 0) {\n console.log(\n \"No reactor-drive migrations to run - database is up to date\",\n );\n } else {\n console.log(\n `Successfully executed ${driveResult.migrationsExecuted.length} reactor-drive migration(s):`,\n );\n for (const name of driveResult.migrationsExecuted) {\n console.log(` - ${name}`);\n }\n }\n }\n } catch (error) {\n console.error(\n \"Error:\",\n error instanceof Error ? error.message : String(error),\n );\n process.exit(1);\n } finally {\n await db.destroy();\n }\n}\n\nvoid main();\n"],"names":[],"mappings":";;;;;;;;;;AAEA,OAAO,QAAQ;AAef,SAAS,cAAc,KAAsB;AAC3C,QAAO,IAAI,WAAW,gBAAgB,IAAI,IAAI,WAAW,cAAc;;AAGzE,eAAe,OAAO;CACpB,MAAM,UAAU,QAAQ,KAAK;CAC7B,MAAM,SAAS,WAAW;CAE1B,MAAM,SACJ,QAAQ,IAAI,+BACZ,QAAQ,IAAI,2BACZ,QAAQ,IAAI,gBACZ,OAAO,aAAa,UAAU;AAEhC,KAAI,CAAC,UAAU,CAAC,cAAc,OAAO,EAAE;AACrC,UAAQ,IAAI,qDAAqD;AACjE,UAAQ,IAAI,2DAA2D;AACvE;;AAGF,SAAQ,IAAI,aAAa,SAAS;CAIlC,MAAM,KAAK,IAAI,OAAY,EACzB,SAAS,IAAI,gBAAgB,EAAE,MAHpB,IAAI,KAAK,EAAE,kBAAkB,QAAQ,CAAC,EAGZ,CAAC,EACvC,CAAC;AAEF,KAAI;AACF,MAAI,YAAY,UAAU;AACxB,WAAQ,IAAI,iCAAiC;GAC7C,MAAM,aAAa,MAAM,mBAAmB,IAAI,eAAe;AAE/D,WAAQ,IAAI,8BAA8B;AAC1C,WAAQ,IAAI,4BAA4B;AAExC,QAAK,MAAM,aAAa,YAAY;IAClC,MAAM,SAAS,UAAU,aACrB,oBAAoB,UAAU,WAAW,aAAa,KACtD;AACJ,YAAQ,IAAI,GAAG,OAAO,KAAK,UAAU,OAAO;;GAG9C,MAAM,kBAAkB,MAAM,+BAC5B,IACA,eACD;AAED,WAAQ,IAAI,oCAAoC;AAChD,WAAQ,IAAI,kCAAkC;AAE9C,QAAK,MAAM,aAAa,iBAAiB;IACvC,MAAM,SAAS,UAAU,aACrB,oBAAoB,UAAU,WAAW,aAAa,KACtD;AACJ,YAAQ,IAAI,GAAG,OAAO,KAAK,UAAU,OAAO;;SAEzC;AACL,WAAQ,IAAI,kCAAkC;GAC9C,MAAM,SAAS,MAAM,cAAc,IAAI,eAAe;AAEtD,OAAI,CAAC,OAAO,SAAS;AACnB,YAAQ,MAAM,qBAAqB,OAAO,OAAO,QAAQ;AACzD,YAAQ,KAAK,EAAE;;AAGjB,OAAI,OAAO,mBAAmB,WAAW,EACvC,SAAQ,IAAI,wDAAwD;QAC/D;AACL,YAAQ,IACN,yBAAyB,OAAO,mBAAmB,OAAO,wBAC3D;AACD,SAAK,MAAM,QAAQ,OAAO,mBACxB,SAAQ,IAAI,OAAO,OAAO;;AAI9B,WAAQ,IAAI,wCAAwC;GACpD,MAAM,cAAc,MAAM,0BAA0B,IAAI,eAAe;AAEvE,OAAI,CAAC,YAAY,SAAS;AACxB,YAAQ,MACN,mCACA,YAAY,OAAO,QACpB;AACD,YAAQ,KAAK,EAAE;;AAGjB,OAAI,YAAY,mBAAmB,WAAW,EAC5C,SAAQ,IACN,8DACD;QACI;AACL,YAAQ,IACN,yBAAyB,YAAY,mBAAmB,OAAO,8BAChE;AACD,SAAK,MAAM,QAAQ,YAAY,mBAC7B,SAAQ,IAAI,OAAO,OAAO;;;UAIzB,OAAO;AACd,UAAQ,MACN,UACA,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CACvD;AACD,UAAQ,KAAK,EAAE;WACP;AACR,QAAM,GAAG,SAAS;;;AAIjB,MAAM","debug_id":"faf76c26-cfd8-5dc4-94c3-936a42663059"}
@@ -1,6 +1,6 @@
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]="243850c6-8159-53a4-a4f4-c310a075551a")}catch(e){}}();
3
- import { n as isPostgresUrl, t as addDefaultDrive } from "./utils-DFl0ezBT.mjs";
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){}}();
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";
6
6
  import { childLogger, documentModelDocumentModelModule, setLogLevel } from "document-model";
@@ -9,15 +9,16 @@ import { getConfig } from "@powerhousedao/config/node";
9
9
  import { promises } from "node:fs";
10
10
  import path from "node:path";
11
11
  import { ReactorInstrumentation } from "@powerhousedao/opentelemetry-instrumentation-reactor";
12
- import { ChannelScheme, EventBus, ReactorBuilder, ReactorClientBuilder, driveCollectionId, parseDriveUrl } from "@powerhousedao/reactor";
12
+ import { AtomicNodeFs } from "@powerhousedao/pglite-fs";
13
+ import { ChannelScheme, EventBus, REACTOR_SCHEMA, ReactorBuilder, ReactorClientBuilder, driveCollectionId, parseDriveUrl } from "@powerhousedao/reactor";
13
14
  import { HttpPackageLoader, ImportPackageLoader, PackageManagementService, PackagesSubgraph, getUniqueDocumentModels, initializeAndStartAPI } from "@powerhousedao/reactor-api";
14
15
  import { httpsHooksPath } from "@powerhousedao/reactor-api/https-hooks";
15
16
  import { VitePackageLoader, createViteLogger, startViteServer } from "@powerhousedao/reactor-api/vite";
17
+ import { DriveNodeView, NodeProcessor, ReactorDriveClient, createReactorDriveResolvers, reactorDriveDocumentModelModule, reactorDriveSubgraphTypeDefs } from "@powerhousedao/reactor-drive";
18
+ import { processorFactory } from "@powerhousedao/vetra/processors";
16
19
  import { driveDocumentModelModule } from "@powerhousedao/shared/document-drive";
17
20
  import { documentModels } from "@powerhousedao/vetra";
18
- import { processorFactory } from "@powerhousedao/vetra/processors";
19
21
  import { Kysely, PostgresDialect } from "kysely";
20
- import { PGliteDialect } from "kysely-pglite-dialect";
21
22
  import net from "node:net";
22
23
  import path$1 from "path";
23
24
  import { Pool } from "pg";
@@ -25,6 +26,7 @@ import { AttachmentNotFound, InvalidAttachmentRef, ReservationNotFound } from "@
25
26
  import { Readable } from "node:stream";
26
27
  import { EnvVarProvider } from "@openfeature/env-var-provider";
27
28
  import { OpenFeature } from "@openfeature/server-sdk";
29
+ import { PGliteDialect } from "kysely-pglite-dialect";
28
30
  import { DEFAULT_RENOWN_URL, NodeKeyStorage, RenownBuilder, RenownCryptoBuilder, createSignatureVerifier } from "@renown/sdk/node";
29
31
  //#region src/pglite-version.ts
30
32
  const SUPPORTED_PG_MAJORS = [16, 17];
@@ -61,24 +63,33 @@ async function loadPgDump(major) {
61
63
  return (await import("@electric-sql/pglite-tools/pg_dump")).pgDump;
62
64
  }
63
65
  //#endregion
64
- //#region src/pglite-dialect.ts
65
- var ClosablePGliteDialect = class extends PGliteDialect {
66
- #pglite;
67
- constructor(pglite) {
68
- super(pglite);
69
- this.#pglite = pglite;
70
- }
71
- createDriver() {
72
- const driver = super.createDriver();
73
- const pglite = this.#pglite;
74
- const innerDestroy = driver.destroy.bind(driver);
75
- driver.destroy = async () => {
76
- await innerDestroy();
77
- if (!pglite.closed) await pglite.close();
78
- };
79
- return driver;
80
- }
81
- };
66
+ //#region src/builder-defaults.mts
67
+ /**
68
+ * Apply switchboard's standard configuration to a reactor + client builder
69
+ * pair. Each piece is opt-out via the options object; defaults mirror what
70
+ * `startSwitchboard` does when building a reactor itself. Mutates both
71
+ * builders in place.
72
+ *
73
+ * Does NOT touch kysely or read models — callers wire those themselves
74
+ * (see `withKysely` / `withReadModelFactory` on the reactor builder).
75
+ */
76
+ function applySwitchboardReactorDefaults(reactorBuilder, clientBuilder, options = {}) {
77
+ const baseModels = options.includeBaseModels !== false ? [
78
+ documentModelDocumentModelModule,
79
+ driveDocumentModelModule,
80
+ reactorDriveDocumentModelModule
81
+ ] : [];
82
+ const vetraModels = options.includeVetraModels !== false ? documentModels : [];
83
+ const extra = options.documentModels ?? [];
84
+ if (baseModels.length || vetraModels.length || extra.length) reactorBuilder.withDocumentModels(getUniqueDocumentModels(baseModels, vetraModels, extra));
85
+ const scheme = options.channelScheme === void 0 ? ChannelScheme.SWITCHBOARD : options.channelScheme;
86
+ if (scheme !== false) reactorBuilder.withChannelScheme(scheme);
87
+ if (options.signalHandlers !== false) reactorBuilder.withSignalHandlers();
88
+ if (options.executorConfig?.maxSkipThreshold !== void 0) reactorBuilder.withExecutorConfig({ maxSkipThreshold: options.executorConfig.maxSkipThreshold });
89
+ if (options.documentModelLoader) reactorBuilder.withDocumentModelLoader(options.documentModelLoader);
90
+ if (options.logger) reactorBuilder.withLogger(options.logger);
91
+ if (options.signer) clientBuilder.withSigner(options.signer);
92
+ }
82
93
  //#endregion
83
94
  //#region src/attachments/auth.ts
84
95
  /**
@@ -348,6 +359,25 @@ async function initFeatureFlags() {
348
359
  return OpenFeature.getClient();
349
360
  }
350
361
  //#endregion
362
+ //#region src/pglite-dialect.ts
363
+ var ClosablePGliteDialect = class extends PGliteDialect {
364
+ #pglite;
365
+ constructor(pglite) {
366
+ super(pglite);
367
+ this.#pglite = pglite;
368
+ }
369
+ createDriver() {
370
+ const driver = super.createDriver();
371
+ const pglite = this.#pglite;
372
+ const innerDestroy = driver.destroy.bind(driver);
373
+ driver.destroy = async () => {
374
+ await innerDestroy();
375
+ if (!pglite.closed) await pglite.close();
376
+ };
377
+ return driver;
378
+ }
379
+ };
380
+ //#endregion
351
381
  //#region src/pglite-migration.ts
352
382
  function backupPath(dataDir, major) {
353
383
  return `${dataDir}.backup-pg${major}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
@@ -489,6 +519,13 @@ const REQUIRE_SIGNATURES = "REQUIRE_SIGNATURES";
489
519
  const REQUIRE_SIGNATURES_DEFAULT = false;
490
520
  const DEFAULT_PORT = process.env.PORT ? Number(process.env.PORT) : 4001;
491
521
  const PORT_FALLBACK_ATTEMPTS = 20;
522
+ const PGLITE_FLUSH_INTERVAL_MS = (() => {
523
+ const raw = process.env.PGLITE_FLUSH_INTERVAL_MS;
524
+ if (raw === void 0) return 100;
525
+ const parsed = Number(raw);
526
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 100;
527
+ })();
528
+ const PGLITE_IN_MEMORY = process.env.PH_PGLITE_IN_MEMORY === "1";
492
529
  /**
493
530
  * Attempt to bind a throwaway TCP server to the given port. Resolves true if
494
531
  * the port is free, false if the OS reports it in use. Any other error is
@@ -521,13 +558,30 @@ async function resolveServerPort(requested, strictPort, logger) {
521
558
  }
522
559
  return requested;
523
560
  }
561
+ async function createReactorKysely(opts) {
562
+ const { reactorDbUrl, reactorPgliteDir, reactorPgliteMajor, inMemory, flushIntervalMs, logger } = opts;
563
+ if (reactorDbUrl && isPostgresUrl(reactorDbUrl)) {
564
+ const pool = new Pool({ connectionString: reactorDbUrl.includes("?") ? reactorDbUrl : `${reactorDbUrl}?sslmode=disable` });
565
+ logger.info("Using PostgreSQL for reactor storage");
566
+ return new Kysely({ dialect: new PostgresDialect({ pool }) });
567
+ }
568
+ if (!reactorPgliteDir || reactorPgliteMajor === null) throw new Error("Reactor PGLite directory not resolved");
569
+ const { PGlite } = await loadPGliteModule(reactorPgliteMajor);
570
+ const pglite = inMemory ? new PGlite() : new PGlite({ fs: new AtomicNodeFs(reactorPgliteDir, {
571
+ logger,
572
+ flushIntervalMs
573
+ }) });
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
+ return new Kysely({ dialect: new ClosablePGliteDialect(pglite) });
576
+ }
524
577
  async function initServer(serverPort, options, renown) {
525
578
  const { dev, packages = [], remoteDrives = [], logger = defaultLogger } = options;
526
579
  logger.level = LogLevel;
527
580
  const dbPath = options.dbPath ?? process.env.DATABASE_URL ?? process.env.PH_SWITCHBOARD_DATABASE_URL;
528
581
  const readModelPath = dbPath || ".ph/read-storage";
529
- const reactorDbUrl = process.env.PH_REACTOR_DATABASE_URL ?? process.env.PH_SWITCHBOARD_DATABASE_URL;
530
- const reactorPgliteDir = !reactorDbUrl || !isPostgresUrl(reactorDbUrl) ? "./.ph/reactor-storage" : null;
582
+ const reactorDbUrl = options.dbPath ?? process.env.PH_REACTOR_DATABASE_URL ?? process.env.PH_SWITCHBOARD_DATABASE_URL;
583
+ const reactorPath = reactorDbUrl || "./.ph/reactor-storage";
584
+ const reactorPgliteDir = options.reactor ? null : !reactorDbUrl || !isPostgresUrl(reactorDbUrl) ? reactorPath : null;
531
585
  const readModelPgliteDir = !dbPath || !isPostgresUrl(dbPath) ? readModelPath : null;
532
586
  const pgliteDirs = [reactorPgliteDir, readModelPgliteDir].filter((d) => d !== null);
533
587
  const detectedMajors = /* @__PURE__ */ new Map();
@@ -567,8 +621,9 @@ async function initServer(serverPort, options, renown) {
567
621
  const reactorPgliteMajor = reactorPgliteDir ? resolvePgliteMajorForDir(reactorPgliteDir) : null;
568
622
  const readModelPgliteMajor = readModelPgliteDir ? resolvePgliteMajorForDir(readModelPgliteDir) : null;
569
623
  const apiRef = { current: void 0 };
624
+ let driveNodeView;
570
625
  const config = getConfig(options.configFile ?? path$1.join(process.cwd(), "powerhouse.config.json"));
571
- const registryUrl = process.env.PH_REGISTRY_URL ?? config.packageRegistryUrl;
626
+ const registryUrl = options.registryUrl ?? process.env.PH_REGISTRY_URL ?? config.packageRegistryUrl;
572
627
  const registryPackages = process.env.PH_REGISTRY_PACKAGES;
573
628
  const dynamicModelLoading = options.dynamicModelLoading ?? process.env.DYNAMIC_MODEL_LOADING === "true";
574
629
  let httpLoader;
@@ -581,45 +636,55 @@ async function initServer(serverPort, options, renown) {
581
636
  });
582
637
  }
583
638
  const reactorLogger = logger.child(["reactor"]);
584
- const initializeClient = async (documentModels$1) => {
585
- const eventBus = new EventBus();
586
- const builder = new ReactorBuilder().withEventBus(eventBus).withDocumentModels(getUniqueDocumentModels([
587
- documentModelDocumentModelModule,
588
- driveDocumentModelModule,
589
- ...documentModels,
590
- ...documentModels$1
591
- ])).withChannelScheme(ChannelScheme.SWITCHBOARD).withSignalHandlers().withLogger(reactorLogger);
592
- const maxSkipThreshold = parseInt(process.env.MAX_SKIP_THRESHOLD ?? "", 10);
593
- if (!isNaN(maxSkipThreshold) && maxSkipThreshold > 0) {
594
- builder.withExecutorConfig({ maxSkipThreshold });
595
- logger.info(`Reactor maxSkipThreshold set to ${maxSkipThreshold}`);
596
- }
597
- if (reactorDbUrl && isPostgresUrl(reactorDbUrl)) {
598
- const kysely = new Kysely({ dialect: new PostgresDialect({ pool: new Pool({ connectionString: reactorDbUrl.includes("?") ? reactorDbUrl : `${reactorDbUrl}?sslmode=disable` }) }) });
599
- builder.withKysely(kysely);
600
- logger.info("Using PostgreSQL for reactor storage");
601
- } else {
602
- if (!reactorPgliteDir || reactorPgliteMajor === null) throw new Error("Reactor PGLite directory not resolved");
603
- const { PGlite } = await loadPGliteModule(reactorPgliteMajor);
604
- const kysely = new Kysely({ dialect: new ClosablePGliteDialect(new PGlite(reactorPgliteDir)) });
605
- builder.withKysely(kysely);
606
- logger.info(`Using PGlite (PG${reactorPgliteMajor}) for reactor storage at ${reactorPgliteDir}`);
639
+ const initializeClient = async (documentModels) => {
640
+ if (options.reactor) {
641
+ if (options.reactor.reactorModule) {
642
+ new ReactorInstrumentation(options.reactor.reactorModule).start();
643
+ reactorLogger.info("Reactor metrics instrumentation started (using caller-provided reactor)");
644
+ }
645
+ return { module: options.reactor };
607
646
  }
608
- builder.withShutdownHook(async () => {
647
+ const baseKysely = await createReactorKysely({
648
+ reactorDbUrl,
649
+ reactorPgliteDir,
650
+ reactorPgliteMajor,
651
+ inMemory: PGLITE_IN_MEMORY,
652
+ flushIntervalMs: PGLITE_FLUSH_INTERVAL_MS,
653
+ logger
654
+ });
655
+ const maxSkipThreshold = parseInt(process.env.MAX_SKIP_THRESHOLD ?? "", 10);
656
+ const hasSkipThreshold = !isNaN(maxSkipThreshold) && maxSkipThreshold > 0;
657
+ if (hasSkipThreshold) logger.info(`Reactor maxSkipThreshold set to ${maxSkipThreshold}`);
658
+ const reactorBuilder = new ReactorBuilder().withEventBus(new EventBus()).withKysely(baseKysely);
659
+ const clientBuilder = new ReactorClientBuilder().withReactorBuilder(reactorBuilder);
660
+ applySwitchboardReactorDefaults(reactorBuilder, clientBuilder, {
661
+ documentModels,
662
+ executorConfig: hasSkipThreshold ? { maxSkipThreshold } : void 0,
663
+ documentModelLoader: httpLoader && dynamicModelLoading ? httpLoader.documentModelLoader : void 0,
664
+ logger: reactorLogger,
665
+ signer: renown ? getRenownSignerConfig(renown, options.identity?.requireSignatures) : void 0
666
+ });
667
+ reactorBuilder.withReadModelFactory(async ({ operationIndex, writeCache, processorManagerConsistencyTracker }) => {
668
+ const nodeProcessor = new NodeProcessor(baseKysely, REACTOR_SCHEMA, operationIndex, writeCache, processorManagerConsistencyTracker);
669
+ await nodeProcessor.init();
670
+ return nodeProcessor;
671
+ });
672
+ reactorBuilder.withShutdownHook(async () => {
609
673
  if (apiRef.current) await apiRef.current.dispose();
610
674
  });
611
- if (httpLoader && dynamicModelLoading) builder.withDocumentModelLoader(httpLoader.documentModelLoader);
612
- const clientBuilder = new ReactorClientBuilder().withReactorBuilder(builder);
613
- if (renown) {
614
- const signerConfig = getRenownSignerConfig(renown, options.identity?.requireSignatures);
615
- clientBuilder.withSigner(signerConfig);
616
- }
617
675
  const module = await clientBuilder.buildModule();
618
676
  if (module.reactorModule) {
619
677
  new ReactorInstrumentation(module.reactorModule).start();
620
678
  reactorLogger.info("Reactor metrics instrumentation started");
621
679
  }
622
- return module;
680
+ driveNodeView = new DriveNodeView(baseKysely.withSchema(REACTOR_SCHEMA));
681
+ return {
682
+ module,
683
+ reactorDriveClient: new ReactorDriveClient({
684
+ reactor: module.client,
685
+ readModel: driveNodeView
686
+ })
687
+ };
623
688
  };
624
689
  let defaultDriveUrl = void 0;
625
690
  const basePath = process.cwd();
@@ -640,7 +705,10 @@ async function initServer(serverPort, options, renown) {
640
705
  let pgliteFactory;
641
706
  if (readModelPgliteDir && readModelPgliteMajor !== null) {
642
707
  const { PGlite: ReadModelPGlite } = await loadPGliteModule(readModelPgliteMajor);
643
- pgliteFactory = (connectionString) => new ReadModelPGlite(connectionString ?? readModelPgliteDir);
708
+ pgliteFactory = PGLITE_IN_MEMORY ? () => new ReadModelPGlite() : (connectionString) => new ReadModelPGlite({ fs: new AtomicNodeFs(connectionString ?? readModelPgliteDir, {
709
+ logger,
710
+ flushIntervalMs: PGLITE_FLUSH_INTERVAL_MS
711
+ }) });
644
712
  }
645
713
  const api = await initializeAndStartAPI(initializeClient, {
646
714
  port: serverPort,
@@ -682,9 +750,24 @@ async function initServer(serverPort, options, renown) {
682
750
  logger.error("Failed to register packages subgraph: @error", error);
683
751
  });
684
752
  }
753
+ if (driveNodeView) {
754
+ graphqlManager.setAdditionalContextFields({ readModel: driveNodeView });
755
+ const reactorDriveSubgraph = {
756
+ name: "reactor-drive",
757
+ path: graphqlManager.getBasePath(),
758
+ resolvers: createReactorDriveResolvers(),
759
+ typeDefs: reactorDriveSubgraphTypeDefs,
760
+ reactorClient: client,
761
+ relationalDb: void 0
762
+ };
763
+ graphqlManager.registerSubgraphInstance(reactorDriveSubgraph, "graphql", false).then(() => graphqlManager.updateRouter()).catch((error) => {
764
+ logger.error("Failed to register reactor-drive subgraph: @error", error);
765
+ });
766
+ }
685
767
  if (options.drive) {
686
768
  if (!renown) throw new Error("Cannot create default drive without Renown identity");
687
- defaultDriveUrl = await addDefaultDrive(client, options.drive, serverPort);
769
+ if ((options.drive.documentType ?? "powerhouse/document-drive") === "powerhouse/reactor-drive") defaultDriveUrl = await addDefaultReactorDrive(client, options.drive, serverPort);
770
+ else defaultDriveUrl = await addDefaultDrive(client, options.drive, serverPort);
688
771
  }
689
772
  if (vite) api.httpAdapter.mountRawMiddleware(vite.middlewares);
690
773
  if (remoteDrives.length > 0) for (const remoteDriveUrl of remoteDrives) {
@@ -713,9 +796,27 @@ async function initServer(serverPort, options, renown) {
713
796
  api,
714
797
  reactor: client,
715
798
  renown,
716
- port: serverPort
799
+ port: serverPort,
800
+ shutdown: () => api.dispose()
717
801
  };
718
802
  }
803
+ /**
804
+ * Boot the switchboard HTTP/GraphQL/MCP stack on top of a reactor.
805
+ *
806
+ * If `options.reactor` is provided, the switchboard reuses it instead of
807
+ * building its own — the caller then owns the reactor's lifecycle and is
808
+ * responsible for invoking `SwitchboardReactor.shutdown()` from their own
809
+ * teardown / SIGINT path. The switchboard will not reach into the caller's
810
+ * reactor; killing the reactor alone leaves the api/GraphQL/MCP resources
811
+ * dangling until the process exits.
812
+ *
813
+ * When `options.reactor` is omitted, the switchboard builds and owns the
814
+ * reactor. `shutdown()` on the returned handle only drains the api (HTTP
815
+ * server, GraphQL, MCP, attachments); the reactor itself is torn down by
816
+ * its own signal handlers (`withSignalHandlers`), which call `kill()` and
817
+ * trigger the `withShutdownHook` chain that disposes the api. Programmatic
818
+ * full teardown isn't currently exposed — wire it via SIGINT/SIGTERM.
819
+ */
719
820
  const startSwitchboard = async (options = {}) => {
720
821
  const requestedPort = options.port ?? DEFAULT_PORT;
721
822
  const logger = options.logger ?? defaultLogger;
@@ -737,7 +838,7 @@ const startSwitchboard = async (options = {}) => {
737
838
  renown = await initRenown(options.identity);
738
839
  } catch (e) {
739
840
  logger.warn("Failed to initialize ConnectCrypto: @error", e);
740
- if (options.identity?.requireExisting) throw new Error("Identity required but failed to initialize. Run \"ph login\" first.");
841
+ if (options.identity.requireExisting) throw new Error("Identity required but failed to initialize. Run \"ph login\" first.", { cause: e });
741
842
  }
742
843
  try {
743
844
  return await initServer(serverPort, options, renown);
@@ -749,7 +850,7 @@ const startSwitchboard = async (options = {}) => {
749
850
  };
750
851
  if (import.meta.main) await startSwitchboard();
751
852
  //#endregion
752
- export { startSwitchboard as n, parseForcePgVersion as r, isPortAvailable as t };
853
+ export { parseForcePgVersion as i, startSwitchboard as n, applySwitchboardReactorDefaults as r, isPortAvailable as t };
753
854
 
754
- //# sourceMappingURL=server-DPxV64Dh.mjs.map
755
- //# debugId=243850c6-8159-53a4-a4f4-c310a075551a
855
+ //# sourceMappingURL=server-bMFA4VKj.mjs.map
856
+ //# debugId=15b2a451-3254-5fb0-8fe9-4fa3c769e05c