@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/.env +2 -2
- package/CHANGELOG.md +457 -338
- package/dist/index.mjs +10 -4
- package/dist/index.mjs.map +1 -1
- package/dist/migrate.mjs +26 -7
- package/dist/migrate.mjs.map +1 -1
- package/dist/{server-DPxV64Dh.mjs → server-bMFA4VKj.mjs} +165 -64
- package/dist/server-bMFA4VKj.mjs.map +1 -0
- package/dist/server.d.mts +114 -3
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +5 -5
- package/dist/{utils-DFl0ezBT.mjs → utils-BVNg1DRI.mjs} +38 -4
- package/dist/utils-BVNg1DRI.mjs.map +1 -0
- package/dist/utils.d.mts +2 -1
- package/dist/utils.d.mts.map +1 -1
- package/dist/utils.mjs +4 -4
- package/package.json +15 -10
- package/test/attachments/routes-integration.test.ts +1 -1
- package/tsconfig.json +7 -1
- package/dist/server-DPxV64Dh.mjs.map +0 -1
- package/dist/utils-DFl0ezBT.mjs.map +0 -1
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]="
|
|
4
|
-
import {
|
|
5
|
-
import "./utils-
|
|
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=
|
|
224
|
+
//# debugId=5f3b2177-d6bb-533b-b87e-e4eb03b10c87
|
package/dist/index.mjs.map
CHANGED
|
@@ -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]="
|
|
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("\
|
|
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=
|
|
79
|
+
//# debugId=faf76c26-cfd8-5dc4-94c3-936a42663059
|
package/dist/migrate.mjs.map
CHANGED
|
@@ -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(\"\\
|
|
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]="
|
|
3
|
-
import { n as isPostgresUrl, t as addDefaultDrive } from "./utils-
|
|
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 {
|
|
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/
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
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
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
853
|
+
export { parseForcePgVersion as i, startSwitchboard as n, applySwitchboardReactorDefaults as r, isPortAvailable as t };
|
|
753
854
|
|
|
754
|
-
//# sourceMappingURL=server-
|
|
755
|
-
//# debugId=
|
|
855
|
+
//# sourceMappingURL=server-bMFA4VKj.mjs.map
|
|
856
|
+
//# debugId=15b2a451-3254-5fb0-8fe9-4fa3c769e05c
|