@powerhousedao/switchboard 6.0.0-dev.253 → 6.0.0-dev.254

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## 6.0.0-dev.254 (2026-05-19)
2
+
3
+ ### 🚀 Features
4
+
5
+ - **vetra:** allow passing db-path through vetra command ([39c62d961](https://github.com/powerhouse-inc/powerhouse/commit/39c62d961))
6
+ - **reactor-api:** main integration tests now run on both reactor-drive and document-drive, includes reshuffle fix ([4f370a63f](https://github.com/powerhouse-inc/powerhouse/commit/4f370a63f))
7
+ - add dark mode script ([#2619](https://github.com/powerhouse-inc/powerhouse/pull/2619))
8
+ - single-document contention integration tests are switchable between document-drive and reactor-drive ([68b59b492](https://github.com/powerhouse-inc/powerhouse/commit/68b59b492))
9
+ - **reactor-drive:** initial commit ([d6b7c4f8c](https://github.com/powerhouse-inc/powerhouse/commit/d6b7c4f8c))
10
+
11
+ ### 🩹 Fixes
12
+
13
+ - wiring up reactor-drive dependency ([8c22b1658](https://github.com/powerhouse-inc/powerhouse/commit/8c22b1658))
14
+
15
+ ### ❤️ Thank You
16
+
17
+ - Benjamin Jordan
18
+ - Ryan Wolhuter @ryanwolhuter
19
+
1
20
  ## 6.0.0-dev.253 (2026-05-18)
2
21
 
3
22
  This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
package/dist/index.mjs CHANGED
@@ -1,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-Dd7gPta4.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 { n as startSwitchboard, r as parseForcePgVersion } from "./server-DwBiiN-E.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]="8faada52-173a-5928-8bb6-f2884360991b")}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]="3e29e419-6688-51d4-8af1-b05c7a1a5ffb")}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";
@@ -10,10 +10,11 @@ import { promises } from "node:fs";
10
10
  import path from "node:path";
11
11
  import { ReactorInstrumentation } from "@powerhousedao/opentelemetry-instrumentation-reactor";
12
12
  import { AtomicNodeFs } from "@powerhousedao/pglite-fs";
13
- import { ChannelScheme, EventBus, ReactorBuilder, ReactorClientBuilder, driveCollectionId, parseDriveUrl } from "@powerhousedao/reactor";
13
+ import { ChannelScheme, EventBus, REACTOR_SCHEMA, ReactorBuilder, ReactorClientBuilder, driveCollectionId, parseDriveUrl } from "@powerhousedao/reactor";
14
14
  import { HttpPackageLoader, ImportPackageLoader, PackageManagementService, PackagesSubgraph, getUniqueDocumentModels, initializeAndStartAPI } from "@powerhousedao/reactor-api";
15
15
  import { httpsHooksPath } from "@powerhousedao/reactor-api/https-hooks";
16
16
  import { VitePackageLoader, createViteLogger, startViteServer } from "@powerhousedao/reactor-api/vite";
17
+ import { DriveNodeView, NodeProcessor, ReactorDriveClient, createReactorDriveResolvers, reactorDriveDocumentModelModule, reactorDriveSubgraphTypeDefs } from "@powerhousedao/reactor-drive";
17
18
  import { driveDocumentModelModule } from "@powerhousedao/shared/document-drive";
18
19
  import { documentModels } from "@powerhousedao/vetra";
19
20
  import { processorFactory } from "@powerhousedao/vetra/processors";
@@ -534,8 +535,9 @@ async function initServer(serverPort, options, renown) {
534
535
  logger.level = LogLevel;
535
536
  const dbPath = options.dbPath ?? process.env.DATABASE_URL ?? process.env.PH_SWITCHBOARD_DATABASE_URL;
536
537
  const readModelPath = dbPath || ".ph/read-storage";
537
- const reactorDbUrl = process.env.PH_REACTOR_DATABASE_URL ?? process.env.PH_SWITCHBOARD_DATABASE_URL;
538
- const reactorPgliteDir = !reactorDbUrl || !isPostgresUrl(reactorDbUrl) ? "./.ph/reactor-storage" : null;
538
+ const reactorDbUrl = options.dbPath ?? process.env.PH_REACTOR_DATABASE_URL ?? process.env.PH_SWITCHBOARD_DATABASE_URL;
539
+ const reactorPath = reactorDbUrl || "./.ph/reactor-storage";
540
+ const reactorPgliteDir = !reactorDbUrl || !isPostgresUrl(reactorDbUrl) ? reactorPath : null;
539
541
  const readModelPgliteDir = !dbPath || !isPostgresUrl(dbPath) ? readModelPath : null;
540
542
  const pgliteDirs = [reactorPgliteDir, readModelPgliteDir].filter((d) => d !== null);
541
543
  const detectedMajors = /* @__PURE__ */ new Map();
@@ -575,6 +577,7 @@ async function initServer(serverPort, options, renown) {
575
577
  const reactorPgliteMajor = reactorPgliteDir ? resolvePgliteMajorForDir(reactorPgliteDir) : null;
576
578
  const readModelPgliteMajor = readModelPgliteDir ? resolvePgliteMajorForDir(readModelPgliteDir) : null;
577
579
  const apiRef = { current: void 0 };
580
+ let driveNodeView;
578
581
  const config = getConfig(options.configFile ?? path$1.join(process.cwd(), "powerhouse.config.json"));
579
582
  const registryUrl = process.env.PH_REGISTRY_URL ?? config.packageRegistryUrl;
580
583
  const registryPackages = process.env.PH_REGISTRY_PACKAGES;
@@ -594,6 +597,7 @@ async function initServer(serverPort, options, renown) {
594
597
  const builder = new ReactorBuilder().withEventBus(eventBus).withDocumentModels(getUniqueDocumentModels([
595
598
  documentModelDocumentModelModule,
596
599
  driveDocumentModelModule,
600
+ reactorDriveDocumentModelModule,
597
601
  ...documentModels,
598
602
  ...documentModels$1
599
603
  ])).withChannelScheme(ChannelScheme.SWITCHBOARD).withSignalHandlers().withLogger(reactorLogger);
@@ -602,20 +606,26 @@ async function initServer(serverPort, options, renown) {
602
606
  builder.withExecutorConfig({ maxSkipThreshold });
603
607
  logger.info(`Reactor maxSkipThreshold set to ${maxSkipThreshold}`);
604
608
  }
609
+ let baseKysely;
605
610
  if (reactorDbUrl && isPostgresUrl(reactorDbUrl)) {
606
- const kysely = new Kysely({ dialect: new PostgresDialect({ pool: new Pool({ connectionString: reactorDbUrl.includes("?") ? reactorDbUrl : `${reactorDbUrl}?sslmode=disable` }) }) });
607
- builder.withKysely(kysely);
611
+ baseKysely = new Kysely({ dialect: new PostgresDialect({ pool: new Pool({ connectionString: reactorDbUrl.includes("?") ? reactorDbUrl : `${reactorDbUrl}?sslmode=disable` }) }) });
608
612
  logger.info("Using PostgreSQL for reactor storage");
609
613
  } else {
610
614
  if (!reactorPgliteDir || reactorPgliteMajor === null) throw new Error("Reactor PGLite directory not resolved");
611
615
  const { PGlite } = await loadPGliteModule(reactorPgliteMajor);
612
- const kysely = new Kysely({ dialect: new ClosablePGliteDialect(PGLITE_IN_MEMORY ? new PGlite() : new PGlite({ fs: new AtomicNodeFs(reactorPgliteDir, {
616
+ baseKysely = new Kysely({ dialect: new ClosablePGliteDialect(PGLITE_IN_MEMORY ? new PGlite() : new PGlite({ fs: new AtomicNodeFs(reactorPgliteDir, {
613
617
  logger,
614
618
  flushIntervalMs: PGLITE_FLUSH_INTERVAL_MS
615
619
  }) })) });
616
- builder.withKysely(kysely);
617
620
  logger.info(PGLITE_IN_MEMORY ? `Using in-memory PGlite (PG${reactorPgliteMajor}) for reactor storage [PH_PGLITE_IN_MEMORY=1]` : `Using PGlite (PG${reactorPgliteMajor}) for reactor storage at ${reactorPgliteDir}`);
618
621
  }
622
+ builder.withKysely(baseKysely);
623
+ const reactorDriveSchemaDb = baseKysely.withSchema(REACTOR_SCHEMA);
624
+ builder.withReadModelFactory(async ({ operationIndex, writeCache, processorManagerConsistencyTracker }) => {
625
+ const nodeProcessor = new NodeProcessor(baseKysely, REACTOR_SCHEMA, operationIndex, writeCache, processorManagerConsistencyTracker);
626
+ await nodeProcessor.init();
627
+ return nodeProcessor;
628
+ });
619
629
  builder.withShutdownHook(async () => {
620
630
  if (apiRef.current) await apiRef.current.dispose();
621
631
  });
@@ -630,7 +640,14 @@ async function initServer(serverPort, options, renown) {
630
640
  new ReactorInstrumentation(module.reactorModule).start();
631
641
  reactorLogger.info("Reactor metrics instrumentation started");
632
642
  }
633
- return module;
643
+ driveNodeView = new DriveNodeView(reactorDriveSchemaDb);
644
+ return {
645
+ module,
646
+ reactorDriveClient: new ReactorDriveClient({
647
+ reactor: module.client,
648
+ readModel: driveNodeView
649
+ })
650
+ };
634
651
  };
635
652
  let defaultDriveUrl = void 0;
636
653
  const basePath = process.cwd();
@@ -696,9 +713,24 @@ async function initServer(serverPort, options, renown) {
696
713
  logger.error("Failed to register packages subgraph: @error", error);
697
714
  });
698
715
  }
716
+ if (driveNodeView) {
717
+ graphqlManager.setAdditionalContextFields({ readModel: driveNodeView });
718
+ const reactorDriveSubgraph = {
719
+ name: "reactor-drive",
720
+ path: graphqlManager.getBasePath(),
721
+ resolvers: createReactorDriveResolvers(),
722
+ typeDefs: reactorDriveSubgraphTypeDefs,
723
+ reactorClient: client,
724
+ relationalDb: void 0
725
+ };
726
+ graphqlManager.registerSubgraphInstance(reactorDriveSubgraph, "graphql", false).then(() => graphqlManager.updateRouter()).catch((error) => {
727
+ logger.error("Failed to register reactor-drive subgraph: @error", error);
728
+ });
729
+ }
699
730
  if (options.drive) {
700
731
  if (!renown) throw new Error("Cannot create default drive without Renown identity");
701
- defaultDriveUrl = await addDefaultDrive(client, options.drive, serverPort);
732
+ if ((options.drive.documentType ?? "powerhouse/document-drive") === "powerhouse/reactor-drive") defaultDriveUrl = await addDefaultReactorDrive(client, options.drive, serverPort);
733
+ else defaultDriveUrl = await addDefaultDrive(client, options.drive, serverPort);
702
734
  }
703
735
  if (vite) api.httpAdapter.mountRawMiddleware(vite.middlewares);
704
736
  if (remoteDrives.length > 0) for (const remoteDriveUrl of remoteDrives) {
@@ -751,7 +783,7 @@ const startSwitchboard = async (options = {}) => {
751
783
  renown = await initRenown(options.identity);
752
784
  } catch (e) {
753
785
  logger.warn("Failed to initialize ConnectCrypto: @error", e);
754
- if (options.identity?.requireExisting) throw new Error("Identity required but failed to initialize. Run \"ph login\" first.", { cause: e });
786
+ if (options.identity.requireExisting) throw new Error("Identity required but failed to initialize. Run \"ph login\" first.", { cause: e });
755
787
  }
756
788
  try {
757
789
  return await initServer(serverPort, options, renown);
@@ -765,5 +797,5 @@ if (import.meta.main) await startSwitchboard();
765
797
  //#endregion
766
798
  export { startSwitchboard as n, parseForcePgVersion as r, isPortAvailable as t };
767
799
 
768
- //# sourceMappingURL=server-Dd7gPta4.mjs.map
769
- //# debugId=8faada52-173a-5928-8bb6-f2884360991b
800
+ //# sourceMappingURL=server-DwBiiN-E.mjs.map
801
+ //# debugId=3e29e419-6688-51d4-8af1-b05c7a1a5ffb
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-DwBiiN-E.mjs","sources":["../src/pglite-version.ts","../src/attachments/auth.ts","../src/attachments/mount-auth.ts","../src/attachments/routes.ts","../src/attachments/index.ts","../src/feature-flags.ts","../src/pglite-dialect.ts","../src/pglite-migration.ts","../src/renown.ts","../src/server.mts"],"sourcesContent":["import type * as CurrentPGliteModuleNs from \"@electric-sql/pglite\";\nimport { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\n\nexport const CURRENT_PG_MAJOR = 17;\nexport const SUPPORTED_PG_MAJORS = [16, 17] as const;\nexport type SupportedPgMajor = (typeof SUPPORTED_PG_MAJORS)[number];\n\ntype CurrentPGliteModule = typeof CurrentPGliteModuleNs;\n\nexport async function readPgVersionFile(\n dataDir: string,\n): Promise<number | null> {\n try {\n const raw = await fs.readFile(path.join(dataDir, \"PG_VERSION\"), \"utf8\");\n const major = parseInt(raw.trim(), 10);\n return Number.isFinite(major) ? major : null;\n } catch {\n return null;\n }\n}\n\nexport function isSupportedMajor(major: number): major is SupportedPgMajor {\n return (SUPPORTED_PG_MAJORS as readonly number[]).includes(major);\n}\n\n/**\n * Parses the `PH_FORCE_PG_VERSION` env var. Returns the validated major, or\n * `null` when the var is unset/empty. Throws on any value that is not a\n * supported major — invalid configuration must fail before the server starts\n * touching disk.\n */\nexport function parseForcePgVersion(\n raw: string | undefined,\n): SupportedPgMajor | null {\n if (raw === undefined || raw.trim() === \"\") return null;\n const parsed = Number(raw);\n if (Number.isInteger(parsed) && isSupportedMajor(parsed)) return parsed;\n throw new Error(\n `PH_FORCE_PG_VERSION must be one of: ${SUPPORTED_PG_MAJORS.join(\", \")} (got: ${raw})`,\n );\n}\n\nexport async function loadPGliteModule(\n major: SupportedPgMajor,\n): Promise<CurrentPGliteModule> {\n if (major === 16) {\n return (await import(\"pglite-legacy-02\")) as unknown as CurrentPGliteModule;\n }\n return import(\"@electric-sql/pglite\");\n}\n\ntype PgDumpFn = (options: {\n pg: unknown;\n}) => Promise<{ text(): Promise<string> }>;\n\nexport async function loadPgDump(major: SupportedPgMajor): Promise<PgDumpFn> {\n if (major === 16) {\n const mod = (await import(\"pglite-tools-legacy-02/pg_dump\")) as {\n pgDump: PgDumpFn;\n };\n return mod.pgDump;\n }\n const mod = (await import(\"@electric-sql/pglite-tools/pg_dump\")) as {\n pgDump: PgDumpFn;\n };\n return mod.pgDump;\n}\n","import type { AuthService } from \"@powerhousedao/reactor-api\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\n\nexport type NodeHandler = (\n req: IncomingMessage,\n res: ServerResponse,\n) => Promise<void> | void;\n\n/**\n * Wrap a Node-style handler so that, when `authService` is provided and auth is\n * enabled, the request must carry a verifiable Bearer token.\n */\nexport function requireAuth(\n authService: AuthService | undefined,\n handler: NodeHandler,\n): NodeHandler {\n if (!authService) return handler;\n\n return async (req, res) => {\n let result;\n try {\n result = await authService.verifyBearer(req.headers.authorization);\n } catch {\n res.statusCode = 500;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify({ error: \"Internal authentication error\" }));\n return;\n }\n\n if (result instanceof Response) {\n const body = await result.text();\n res.statusCode = result.status;\n const contentType = result.headers.get(\"content-type\");\n if (contentType) res.setHeader(\"Content-Type\", contentType);\n res.end(body);\n return;\n }\n\n if (result.auth_enabled && !result.user) {\n res.statusCode = 401;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify({ error: \"Authentication required\" }));\n return;\n }\n\n await handler(req, res);\n };\n}\n","import type { API } from \"@powerhousedao/reactor-api\";\nimport { requireAuth, type NodeHandler } from \"./auth.js\";\n\nexport type HttpMethod = \"DELETE\" | \"GET\" | \"HEAD\" | \"POST\" | \"PUT\";\n\n/**\n * Mount a Node-style attachment route with `requireAuth` applied unconditionally.\n * When `api.authService` is undefined (auth disabled), `requireAuth` returns the\n * handler unchanged — that is the only way to opt out. To register a route\n * without auth wrapping you must call `api.httpAdapter.mountNodeRoute` directly.\n */\nexport function mountAuthenticatedNodeRoute(\n api: Pick<API, \"httpAdapter\" | \"authService\">,\n method: HttpMethod,\n path: string,\n handler: NodeHandler,\n): void {\n api.httpAdapter.mountNodeRoute(\n method,\n path,\n requireAuth(api.authService, handler),\n );\n}\n","import {\n AttachmentNotFound,\n InvalidAttachmentRef,\n ReservationNotFound,\n type AttachmentBuildResult,\n type ReserveAttachmentOptions,\n} from \"@powerhousedao/reactor-attachments\";\nimport type { AttachmentHash } from \"@powerhousedao/reactor\";\nimport { childLogger } from \"document-model\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { Readable } from \"node:stream\";\nimport type { ReadableStream as NodeReadableStream } from \"node:stream/web\";\n\nconst logger = childLogger([\"switchboard\", \"attachments\"]);\n\n// Canonical form is lowercase hex (the SHA-256 hasher emits lowercase), but\n// accept either case from the wire and normalise before lookup. This keeps\n// the API forgiving for hand-typed URLs without changing storage semantics.\nconst HASH_PATTERN = /^[a-f0-9]{64}$/i;\n// eslint-disable-next-line no-control-regex\nconst CONTROL_CHARS = /[\\x00-\\x1f\\x7f]/;\n// RFC 6838 token chars; allows optional `; param=value` pairs (token or quoted-string).\nconst MIME_TYPE_PATTERN =\n /^[!#$%&'*+\\-.^_`|~\\w]+\\/[!#$%&'*+\\-.^_`|~\\w]+(?:\\s*;\\s*[!#$%&'*+\\-.^_`|~\\w]+=(?:[!#$%&'*+\\-.^_`|~\\w]+|\"(?:[^\"\\\\\\r\\n]|\\\\[^\\r\\n])*\"))*$/;\nconst MAX_FILENAME_LEN = 255;\nconst MAX_MIMETYPE_LEN = 255;\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n res.statusCode = status;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify(body));\n}\n\nfunction sendError(res: ServerResponse, status: number, message: string): void {\n sendJson(res, status, { error: message });\n}\n\nfunction statusForError(err: unknown): number {\n if (err instanceof AttachmentNotFound) return 404;\n if (err instanceof ReservationNotFound) return 404;\n if (err instanceof InvalidAttachmentRef) return 400;\n return 500;\n}\n\nfunction sendErrorFromException(res: ServerResponse, err: unknown): void {\n const status = statusForError(err);\n if (status >= 500) {\n logger.error(\"Attachment route error: @error\", err);\n sendError(res, status, \"Internal error\");\n return;\n }\n sendError(res, status, err instanceof Error ? err.message : String(err));\n}\n\nasync function readJsonBody(\n req: IncomingMessage,\n body: unknown,\n): Promise<unknown> {\n // The Express body-parser may have already populated `body`. When that\n // happens we trust it; otherwise read the raw stream ourselves so this\n // module is independent of upstream middleware ordering.\n if (body !== undefined && body !== null && typeof body === \"object\") {\n return body;\n }\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(chunk as Buffer);\n }\n if (chunks.length === 0) return undefined;\n const text = Buffer.concat(chunks).toString(\"utf8\");\n if (text.length === 0) return undefined;\n return JSON.parse(text);\n}\n\nexport function parseReserveOptions(\n input: unknown,\n): ReserveAttachmentOptions | null {\n if (input === null || typeof input !== \"object\") return null;\n const obj = input as Record<string, unknown>;\n if (\n typeof obj.mimeType !== \"string\" ||\n obj.mimeType.length === 0 ||\n obj.mimeType.length > MAX_MIMETYPE_LEN ||\n !MIME_TYPE_PATTERN.test(obj.mimeType)\n ) {\n return null;\n }\n if (\n typeof obj.fileName !== \"string\" ||\n obj.fileName.length === 0 ||\n obj.fileName.length > MAX_FILENAME_LEN ||\n CONTROL_CHARS.test(obj.fileName)\n ) {\n return null;\n }\n let extension: string | null = null;\n if (typeof obj.extension === \"string\") {\n if (obj.extension.length === 0 || /[\\\\/]/.test(obj.extension)) return null;\n extension = obj.extension;\n } else if (obj.extension !== undefined && obj.extension !== null) {\n return null;\n }\n return {\n mimeType: obj.mimeType,\n fileName: obj.fileName,\n extension,\n };\n}\n\nexport function quoteFilename(name: string): string {\n // RFC 6266: quoted-string with internal \" and \\ escaped.\n return `\"${name.replace(/[\\\\\"]/g, \"\\\\$&\")}\"`;\n}\n\nexport function buildContentDisposition(fileName: string): string {\n // ASCII fallback: replace any byte outside printable ASCII (0x20-0x7e),\n // plus `\"` and `\\`, with `_`. Browsers fall back to this when they don't\n // grok `filename*=`; the modern parameter carries the real name.\n const ascii = fileName.replace(/[^\\x20-\\x21\\x23-\\x5b\\x5d-\\x7e]/g, \"_\");\n // RFC 5987: percent-encode UTF-8 bytes. encodeURIComponent leaves a few\n // chars that 5987 disallows in token; re-encode them.\n const encoded = encodeURIComponent(fileName).replace(\n /['()*!]/g,\n (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n return `attachment; filename=${quoteFilename(ascii)}; filename*=UTF-8''${encoded}`;\n}\n\nexport function makeReserveHandler(attachments: AttachmentBuildResult) {\n return async (\n req: IncomingMessage,\n res: ServerResponse,\n body?: unknown,\n ): Promise<void> => {\n let parsed: unknown;\n try {\n parsed = await readJsonBody(req, body);\n } catch {\n sendError(res, 400, \"Invalid JSON body\");\n return;\n }\n const opts = parseReserveOptions(parsed);\n if (!opts) {\n sendError(\n res,\n 400,\n \"Body must be { mimeType: string (type/subtype), fileName: string (no control characters, max 255 chars), extension?: string|null }\",\n );\n return;\n }\n try {\n const upload = await attachments.service.reserve(opts);\n sendJson(res, 201, { reservationId: upload.reservationId });\n } catch (err) {\n sendErrorFromException(res, err);\n }\n };\n}\n\nexport function makeUploadHandler(attachments: AttachmentBuildResult) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const reservationId = extractParam(req, \"reservationId\");\n if (!reservationId) {\n sendError(res, 400, \"Missing reservationId\");\n return;\n }\n\n let reservation;\n try {\n reservation = await attachments.reservations.get(reservationId);\n } catch (err) {\n sendErrorFromException(res, err);\n return;\n }\n\n const upload = attachments.uploadFactory.createUpload(\n reservation.reservationId,\n {\n mimeType: reservation.mimeType,\n fileName: reservation.fileName,\n extension: reservation.extension,\n },\n );\n\n const webStream = Readable.toWeb(\n req as Readable,\n ) as ReadableStream<Uint8Array>;\n\n try {\n const result = await upload.send(webStream);\n sendJson(res, 200, result);\n } catch (err) {\n sendErrorFromException(res, err);\n }\n };\n}\n\nexport function makeDownloadHandler(attachments: AttachmentBuildResult) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const hash = extractParam(req, \"hash\");\n if (!hash || !HASH_PATTERN.test(hash)) {\n sendError(res, 400, \"Invalid attachment hash\");\n return;\n }\n\n const controller = new AbortController();\n req.once(\"close\", () => controller.abort());\n\n const canonicalHash = hash.toLowerCase() as AttachmentHash;\n let response;\n try {\n response = await attachments.store.get(canonicalHash, controller.signal);\n } catch (err) {\n sendErrorFromException(res, err);\n return;\n }\n\n const { header, body } = response;\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", header.mimeType);\n res.setHeader(\"Content-Length\", String(header.sizeBytes));\n res.setHeader(\n \"Content-Disposition\",\n buildContentDisposition(header.fileName),\n );\n res.setHeader(\"Attachment-Metadata\", buildMetadataHeader(header));\n\n Readable.fromWeb(body as unknown as NodeReadableStream<Uint8Array>).pipe(\n res,\n );\n };\n}\n\nfunction buildMetadataHeader(header: {\n mimeType: string;\n fileName: string;\n sizeBytes: number;\n extension: string | null;\n createdAtUtc: string;\n lastAccessedAtUtc: string;\n}): string {\n return JSON.stringify({\n mimeType: header.mimeType,\n fileName: header.fileName,\n sizeBytes: header.sizeBytes,\n extension: header.extension,\n createdAtUtc: header.createdAtUtc,\n lastAccessedAtUtc: header.lastAccessedAtUtc,\n });\n}\n\nexport function makeStatHandler(attachments: AttachmentBuildResult) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const hash = extractParam(req, \"hash\");\n if (!hash || !HASH_PATTERN.test(hash)) {\n sendError(res, 400, \"Invalid attachment hash\");\n return;\n }\n\n const canonicalHash = hash.toLowerCase() as AttachmentHash;\n let header;\n try {\n header = await attachments.store.stat(canonicalHash);\n } catch (err) {\n sendErrorFromException(res, err);\n return;\n }\n\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", header.mimeType);\n res.setHeader(\"Content-Length\", String(header.sizeBytes));\n res.setHeader(\n \"Content-Disposition\",\n buildContentDisposition(header.fileName),\n );\n res.setHeader(\"Attachment-Metadata\", buildMetadataHeader(header));\n res.end();\n };\n}\n\nexport function makeGetReservationHandler(attachments: AttachmentBuildResult) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const reservationId = extractParam(req, \"reservationId\");\n if (!reservationId) {\n sendError(res, 400, \"Missing reservationId\");\n return;\n }\n try {\n const reservation = await attachments.reservations.get(reservationId);\n sendJson(res, 200, reservation);\n } catch (err) {\n sendErrorFromException(res, err);\n }\n };\n}\n\nexport function makeDeleteReservationHandler(\n attachments: AttachmentBuildResult,\n) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const reservationId = extractParam(req, \"reservationId\");\n if (!reservationId) {\n sendError(res, 400, \"Missing reservationId\");\n return;\n }\n try {\n await attachments.reservations.delete(reservationId);\n res.statusCode = 204;\n res.end();\n } catch (err) {\n sendErrorFromException(res, err);\n }\n };\n}\n\nfunction extractParam(req: IncomingMessage, name: string): string | undefined {\n const expressParams = (\n req as IncomingMessage & {\n params?: Record<string, string>;\n }\n ).params;\n return expressParams?.[name];\n}\n","import type { API } from \"@powerhousedao/reactor-api\";\nimport { mountAuthenticatedNodeRoute } from \"./mount-auth.js\";\nimport {\n makeDeleteReservationHandler,\n makeDownloadHandler,\n makeGetReservationHandler,\n makeReserveHandler,\n makeStatHandler,\n makeUploadHandler,\n} from \"./routes.js\";\n\nexport function registerAttachmentRoutes(api: API): void {\n const { attachments } = api;\n\n mountAuthenticatedNodeRoute(\n api,\n \"POST\",\n \"/attachments/reservations\",\n makeReserveHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"GET\",\n \"/attachments/reservations/:reservationId\",\n makeGetReservationHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"DELETE\",\n \"/attachments/reservations/:reservationId\",\n makeDeleteReservationHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"PUT\",\n \"/attachments/reservations/:reservationId\",\n makeUploadHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"HEAD\",\n \"/attachments/:hash\",\n makeStatHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"GET\",\n \"/attachments/:hash\",\n makeDownloadHandler(attachments),\n );\n}\n","import { EnvVarProvider } from \"@openfeature/env-var-provider\";\nimport { OpenFeature } from \"@openfeature/server-sdk\";\n\nexport async function initFeatureFlags() {\n // for now, we're only using env vars for feature flags\n const provider = new EnvVarProvider();\n\n await OpenFeature.setProviderAndWait(provider);\n\n return OpenFeature.getClient();\n}\n","import type { PGlite } from \"@electric-sql/pglite\";\nimport type { Driver } from \"kysely\";\nimport { PGliteDialect } from \"kysely-pglite-dialect\";\n\n// kysely-pglite-dialect's driver.destroy() only nulls its reference to the\n// PGlite client — it never calls pglite.close(). Without close(), WAL is not\n// flushed and the data dir is left in a state that aborts the wasm on the\n// next open. This wrapper closes the dialect's PGlite as part of the\n// reactor's database.destroy() chain.\nexport class ClosablePGliteDialect extends PGliteDialect {\n readonly #pglite: PGlite;\n\n constructor(pglite: PGlite) {\n super(pglite);\n this.#pglite = pglite;\n }\n\n createDriver(): Driver {\n const driver = super.createDriver();\n const pglite = this.#pglite;\n const innerDestroy = driver.destroy.bind(driver);\n driver.destroy = async () => {\n await innerDestroy();\n if (!pglite.closed) {\n await pglite.close();\n }\n };\n return driver;\n }\n}\n","import type { ILogger } from \"document-model\";\nimport { promises as fs } from \"node:fs\";\nimport {\n CURRENT_PG_MAJOR,\n isSupportedMajor,\n loadPGliteModule,\n loadPgDump,\n readPgVersionFile,\n type SupportedPgMajor,\n} from \"./pglite-version.js\";\n\ntype PGliteCtor = new (\n dataDir: string,\n options?: Record<string, unknown>,\n) => {\n waitReady: Promise<void>;\n exec: (sql: string) => Promise<unknown>;\n close: () => Promise<void>;\n};\n\nfunction backupPath(dataDir: string, major: number): string {\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n return `${dataDir}.backup-pg${major}-${stamp}`;\n}\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await fs.stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction logRestoreFailure(\n dataDir: string,\n sql: string,\n err: unknown,\n logger: ILogger,\n): void {\n const errObj = err as {\n message?: string;\n position?: string | number;\n severity?: string;\n code?: string;\n detail?: string;\n where?: string;\n };\n const position =\n typeof errObj.position === \"string\"\n ? parseInt(errObj.position, 10)\n : typeof errObj.position === \"number\"\n ? errObj.position\n : NaN;\n\n logger.error(\n `[pglite-migration] Restore failed for ${dataDir}: code=${errObj.code ?? \"\"} severity=${errObj.severity ?? \"\"} message=${errObj.message ?? \"\"} sqlLength=${sql.length}`,\n );\n\n if (Number.isFinite(position) && position > 0) {\n const zeroBased = position - 1;\n const start = Math.max(0, zeroBased - 200);\n const end = Math.min(sql.length, zeroBased + 200);\n const before = sql.slice(start, zeroBased);\n const at = sql.slice(zeroBased, zeroBased + 1);\n const after = sql.slice(zeroBased + 1, end);\n logger.error(\n `[pglite-migration] SQL context around position ${position}:\\n${before}»${at}«${after}`,\n );\n } else {\n logger.error(\n `[pglite-migration] No position info. First 2000 chars of dump:\\n${sql.slice(0, 2000)}`,\n );\n }\n}\n\n/**\n * Migrate a filesystem PGLite data directory from a legacy PG major to the\n * current one. Renames the existing dir to a timestamped backup, dumps via the\n * matching legacy `pg_dump`, restores into a fresh current-version PGLite at\n * the original path. On failure, the original dir is restored from the backup.\n *\n * No-op when the dir is missing or already at the current major.\n */\nexport async function migratePgliteDir(\n dataDir: string,\n logger: ILogger,\n): Promise<void> {\n const major = await readPgVersionFile(dataDir);\n if (major === null) {\n logger.info(\n `[pglite-migration] No PG_VERSION at ${dataDir}; skipping migration`,\n );\n return;\n }\n if (major === CURRENT_PG_MAJOR) return;\n\n if (!isSupportedMajor(major)) {\n throw new Error(\n `Unsupported legacy PGlite data dir: PG_VERSION=${major} for ${dataDir}`,\n );\n }\n\n const backupDir = backupPath(dataDir, major);\n logger.info(\n `[pglite-migration] Migrating ${dataDir} from PG${major} to PG${CURRENT_PG_MAJOR}; backup: ${backupDir}`,\n );\n\n await fs.rename(dataDir, backupDir);\n\n let sql: string;\n try {\n const [legacyMod, pgDump] = await Promise.all([\n loadPGliteModule(major as SupportedPgMajor),\n loadPgDump(major as SupportedPgMajor),\n ]);\n const LegacyPGlite = (legacyMod as unknown as { PGlite: PGliteCtor })\n .PGlite;\n const pg = new LegacyPGlite(backupDir);\n try {\n await pg.waitReady;\n const file = await pgDump({ pg });\n sql = await file.text();\n } finally {\n await pg.close();\n }\n } catch (err) {\n await rollback(dataDir, backupDir, err, logger);\n throw err;\n }\n\n try {\n const currentMod = await loadPGliteModule(CURRENT_PG_MAJOR);\n const CurrentPGlite = (currentMod as unknown as { PGlite: PGliteCtor })\n .PGlite;\n const pg = new CurrentPGlite(dataDir, { relaxedDurability: false });\n try {\n await pg.waitReady;\n try {\n await pg.exec(\"SET standard_conforming_strings = off;\");\n } catch (gucErr) {\n logger.warn(\n `[pglite-migration] Could not force standard_conforming_strings=off: ${String(gucErr)}`,\n );\n }\n try {\n await pg.exec(sql);\n } catch (execErr) {\n logRestoreFailure(dataDir, sql, execErr, logger);\n throw execErr;\n }\n } finally {\n await pg.close();\n }\n } catch (err) {\n await rollback(dataDir, backupDir, err, logger);\n throw err;\n }\n\n logger.info(\n `[pglite-migration] Migration of ${dataDir} complete. Backup retained at ${backupDir}; remove it manually once you have verified the upgrade.`,\n );\n}\n\nasync function rollback(\n dataDir: string,\n backupDir: string,\n originalError: unknown,\n logger: ILogger,\n): Promise<void> {\n try {\n if (await pathExists(dataDir)) {\n await fs.rm(dataDir, { recursive: true, force: true });\n }\n if (await pathExists(backupDir)) {\n await fs.rename(backupDir, dataDir);\n }\n } catch (rollbackErr) {\n logger.error(\n `[pglite-migration] Migration AND rollback failed for ${dataDir}. Original error: ${String(originalError)}; rollback error: ${String(rollbackErr)}; backup may still exist at ${backupDir}.`,\n );\n return;\n }\n logger.error(\n `[pglite-migration] Migration failed for ${dataDir}; rolled back from ${backupDir}. Original error: ${String(originalError)}`,\n );\n}\n","import type { SignerConfig } from \"@powerhousedao/reactor\";\nimport {\n createSignatureVerifier,\n DEFAULT_RENOWN_URL,\n NodeKeyStorage,\n RenownBuilder,\n RenownCryptoBuilder,\n type IRenown,\n} from \"@renown/sdk/node\";\nimport { childLogger } from \"document-model\";\n\nconst logger = childLogger([\"switchboard\", \"renown\"]);\n\nexport interface RenownOptions {\n /** Path to the keypair file. Defaults to .ph/.keypair.json in cwd */\n keypairPath?: string;\n /** If true, won't generate a new keypair if none exists */\n requireExisting?: boolean;\n /** Base url of the Renown instance to use */\n baseUrl?: string;\n}\n\n/**\n * Initialize Renown for the Switchboard instance.\n * This allows Switchboard to authenticate with remote services\n * using the same identity established during `ph login`.\n */\nexport async function initRenown(\n options: RenownOptions = {},\n): Promise<IRenown | null> {\n const {\n keypairPath,\n requireExisting = false,\n baseUrl = DEFAULT_RENOWN_URL,\n } = options;\n\n const keyStorage = new NodeKeyStorage(keypairPath, {\n logger,\n });\n\n // Check if we have an existing keypair\n const existingKeyPair = await keyStorage.loadKeyPair();\n\n if (!existingKeyPair && requireExisting) {\n throw new Error(\n \"No existing keypair found and requireExisting is true. \" +\n 'Run \"ph login\" to create one.',\n );\n }\n\n if (!existingKeyPair) {\n logger.info(\"No existing keypair found. A new one will be generated.\");\n }\n\n const renownCrypto = await new RenownCryptoBuilder()\n .withKeyPairStorage(keyStorage)\n .build();\n\n const renown = await new RenownBuilder(\"switchboard\", {})\n .withCrypto(renownCrypto)\n .withBaseUrl(baseUrl)\n .build();\n\n logger.info(\"Switchboard identity initialized: @did\", renownCrypto.did);\n\n return renown;\n}\n\n/**\n * Get the signer config for the given renown instance.\n *\n * @param renown - The renown instance\n * @param requireSignature - If true, unsigned actions are rejected\n */\nexport function getRenownSignerConfig(\n renown: IRenown,\n requireSignature?: boolean,\n): SignerConfig {\n return {\n signer: renown.signer,\n verifier: createSignatureVerifier(requireSignature),\n };\n}\n","#!/usr/bin/env node\nimport type { PGlite } from \"@electric-sql/pglite\";\nimport { getConfig } from \"@powerhousedao/config/node\";\nimport { ReactorInstrumentation } from \"@powerhousedao/opentelemetry-instrumentation-reactor\";\nimport { AtomicNodeFs } from \"@powerhousedao/pglite-fs\";\nimport {\n ChannelScheme,\n EventBus,\n REACTOR_SCHEMA,\n ReactorBuilder,\n ReactorClientBuilder,\n driveCollectionId,\n parseDriveUrl,\n type Database,\n} from \"@powerhousedao/reactor\";\nimport {\n HttpPackageLoader,\n ImportPackageLoader,\n PackageManagementService,\n PackagesSubgraph,\n getUniqueDocumentModels,\n initializeAndStartAPI,\n type IPackageLoader,\n} from \"@powerhousedao/reactor-api\";\nimport { httpsHooksPath } from \"@powerhousedao/reactor-api/https-hooks\";\nimport {\n VitePackageLoader,\n createViteLogger,\n startViteServer,\n} from \"@powerhousedao/reactor-api/vite\";\nimport {\n DriveNodeView,\n NodeProcessor,\n ReactorDriveClient,\n createReactorDriveResolvers,\n reactorDriveDocumentModelModule,\n reactorDriveSubgraphTypeDefs,\n type ReactorDriveDatabase,\n} from \"@powerhousedao/reactor-drive\";\nimport { driveDocumentModelModule } from \"@powerhousedao/shared/document-drive\";\nimport type { DocumentModelModule } from \"@powerhousedao/shared/document-model\";\nimport { documentModels as vetraDocumentModels } from \"@powerhousedao/vetra\";\nimport { processorFactory as vetraProcessorFactory } from \"@powerhousedao/vetra/processors\";\nimport type { IRenown } from \"@renown/sdk/node\";\nimport * as Sentry from \"@sentry/node\";\nimport {\n childLogger,\n documentModelDocumentModelModule,\n setLogLevel,\n type ILogger,\n} from \"document-model\";\nimport dotenv from \"dotenv\";\nimport { Kysely, PostgresDialect } from \"kysely\";\nimport { promises as fs } from \"node:fs\";\nimport { register } from \"node:module\";\nimport net from \"node:net\";\nimport path from \"path\";\nimport { Pool } from \"pg\";\nimport { registerAttachmentRoutes } from \"./attachments/index.js\";\nimport { initFeatureFlags } from \"./feature-flags.js\";\nimport { ClosablePGliteDialect } from \"./pglite-dialect.js\";\nimport { migratePgliteDir } from \"./pglite-migration.js\";\nimport {\n CURRENT_PG_MAJOR,\n isSupportedMajor,\n loadPGliteModule,\n readPgVersionFile,\n type SupportedPgMajor,\n} from \"./pglite-version.js\";\nimport { getRenownSignerConfig, initRenown } from \"./renown.js\";\nimport type { StartServerOptions, SwitchboardReactor } from \"./types.js\";\nimport {\n addDefaultDrive,\n addDefaultReactorDrive,\n isPostgresUrl,\n} from \"./utils.mjs\";\n\nconst defaultLogger = childLogger([\"switchboard\"]);\n\nconst LogLevel = (process.env.LOG_LEVEL as ILogger[\"level\"] | \"\") || \"info\";\nsetLogLevel(LogLevel);\n\ndotenv.config();\n\n// Feature flag constants\nconst DOCUMENT_MODEL_SUBGRAPHS_ENABLED = \"DOCUMENT_MODEL_SUBGRAPHS_ENABLED\";\nconst DOCUMENT_MODEL_SUBGRAPHS_ENABLED_DEFAULT = true;\nconst REQUIRE_SIGNATURES = \"REQUIRE_SIGNATURES\";\nconst REQUIRE_SIGNATURES_DEFAULT = false;\n\nconst DEFAULT_PORT = process.env.PORT ? Number(process.env.PORT) : 4001;\n\n// How many ports forward from the requested one we will try before giving up.\nconst PORT_FALLBACK_ATTEMPTS = 20;\n\n// AtomicNodeFs needs a flush interval to coalesce writes into a single disk write (only used locally)\nconst PGLITE_FLUSH_INTERVAL_MS = (() => {\n const raw = process.env.PGLITE_FLUSH_INTERVAL_MS;\n if (raw === undefined) return 100;\n const parsed = Number(raw);\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : 100;\n})();\n\n// When set, runs both reactor and read-model PGLite instances purely in-memory.\nconst PGLITE_IN_MEMORY = process.env.PH_PGLITE_IN_MEMORY === \"1\";\n\n/**\n * Attempt to bind a throwaway TCP server to the given port. Resolves true if\n * the port is free, false if the OS reports it in use. Any other error is\n * surfaced so we don't silently mask real issues (permissions, bad host, …).\n */\nexport function isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve, reject) => {\n const tester = net.createServer();\n tester.once(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\" || err.code === \"EACCES\") {\n resolve(false);\n } else {\n reject(err);\n }\n });\n tester.once(\"listening\", () => {\n tester.close(() => resolve(true));\n });\n // Bind on the unspecified IPv6 address so we detect collisions with both\n // IPv6 and IPv4 listeners (Node maps `::` to dual-stack on most systems).\n tester.listen({ port, host: \"::\" });\n });\n}\n\nasync function resolveServerPort(\n requested: number,\n strictPort: boolean,\n logger: ILogger,\n): Promise<number> {\n if (strictPort) return requested;\n for (let i = 0; i < PORT_FALLBACK_ATTEMPTS; i++) {\n const candidate = requested + i;\n if (await isPortAvailable(candidate)) {\n if (candidate !== requested) {\n logger.info(\n `Port ${requested} is in use. Falling back to port ${candidate}.`,\n );\n }\n return candidate;\n }\n }\n // Couldn't find a free port in the window; let the caller surface the\n // original EADDRINUSE when the real bind attempts runs.\n return requested;\n}\n\nasync function initServer(\n serverPort: number,\n options: StartServerOptions,\n renown: IRenown | null,\n) {\n const {\n dev,\n packages = [],\n remoteDrives = [],\n logger = defaultLogger,\n } = options;\n logger.level = LogLevel;\n const dbPath =\n options.dbPath ??\n process.env.DATABASE_URL ??\n process.env.PH_SWITCHBOARD_DATABASE_URL;\n\n // use postgres url for read model storage if available, otherwise use local PGlite path\n const readModelPath = dbPath || \".ph/read-storage\";\n\n const reactorDbUrl =\n options.dbPath ??\n process.env.PH_REACTOR_DATABASE_URL ??\n process.env.PH_SWITCHBOARD_DATABASE_URL;\n const reactorPath = reactorDbUrl || \"./.ph/reactor-storage\";\n const reactorPgliteDir =\n !reactorDbUrl || !isPostgresUrl(reactorDbUrl) ? reactorPath : null;\n const readModelPgliteDir =\n !dbPath || !isPostgresUrl(dbPath) ? readModelPath : null;\n\n // PGLite version pre-flight: when PH_FORCE_PG_VERSION is set, wipe local\n // data dirs and re-initdb at the chosen version. Otherwise detect on-disk\n // PG_VERSION and either migrate (when --migrate-pglite is set) or warn and\n // fall through to the matching legacy PGLite at runtime.\n const pgliteDirs = [reactorPgliteDir, readModelPgliteDir].filter(\n (d): d is string => d !== null,\n );\n const detectedMajors = new Map<string, number>();\n\n if (options.forcePgVersion !== undefined && pgliteDirs.length > 0) {\n if (options.migratePglite) {\n logger.warn(\n \"PH_FORCE_PG_VERSION is set; ignoring --migrate-pglite/PH_MIGRATE_PGLITE because the data dirs will be wiped.\",\n );\n }\n logger.warn(\n `PH_FORCE_PG_VERSION=${options.forcePgVersion} set; wiping PGLite data dirs and re-initializing at PG${options.forcePgVersion}.`,\n );\n for (const dir of pgliteDirs) {\n await fs.rm(dir, { recursive: true, force: true });\n logger.info(`Wiped PGLite data dir ${dir}`);\n }\n } else if (options.forcePgVersion === undefined) {\n for (const dir of pgliteDirs) {\n const major = await readPgVersionFile(dir);\n if (major !== null) detectedMajors.set(dir, major);\n }\n\n if (options.migratePglite) {\n for (const [dir, major] of detectedMajors) {\n if (major === CURRENT_PG_MAJOR) continue;\n await migratePgliteDir(dir, logger);\n // refresh detected major after a successful migration\n const after = await readPgVersionFile(dir);\n if (after !== null) detectedMajors.set(dir, after);\n }\n } else {\n for (const [dir, major] of detectedMajors) {\n if (major === CURRENT_PG_MAJOR) continue;\n logger.warn(\n `PGLite data dir at ${dir} was created with PG${major} but Switchboard ships PG${CURRENT_PG_MAJOR}. Running on legacy PGLite. Re-start with --migrate-pglite (or PH_MIGRATE_PGLITE=true) to upgrade.`,\n );\n }\n }\n }\n\n function resolvePgliteMajorForDir(dir: string): SupportedPgMajor {\n if (options.forcePgVersion !== undefined) return options.forcePgVersion;\n const detected = detectedMajors.get(dir);\n if (detected === undefined) return CURRENT_PG_MAJOR;\n if (!isSupportedMajor(detected)) {\n throw new Error(\n `Unsupported PGLite data dir at ${dir}: PG_VERSION=${detected}`,\n );\n }\n return detected;\n }\n\n const reactorPgliteMajor = reactorPgliteDir\n ? resolvePgliteMajorForDir(reactorPgliteDir)\n : null;\n const readModelPgliteMajor = readModelPgliteDir\n ? resolvePgliteMajorForDir(readModelPgliteDir)\n : null;\n\n // The reactor-api owns its own PGlite/HTTP/WS resources but has no shutdown\n // path of its own; we register `api.dispose` as a reactor shutdown hook so\n // those resources drain inside the reactor's SIGINT chain. The reference\n // is forward — `initializeClient` runs (and registers the hook) before\n // `initializeAndStartAPI` returns the api — so the closure reads `apiRef`\n // at hook-fire time, not at registration time.\n const apiRef: { current: { dispose: () => Promise<void> } | undefined } = {\n current: undefined,\n };\n\n let driveNodeView: DriveNodeView | undefined;\n\n // HTTP registry package loading\n const configPath =\n options.configFile ?? path.join(process.cwd(), \"powerhouse.config.json\");\n const config = getConfig(configPath);\n const registryUrl = process.env.PH_REGISTRY_URL ?? config.packageRegistryUrl;\n const registryPackages = process.env.PH_REGISTRY_PACKAGES;\n const dynamicModelLoading =\n options.dynamicModelLoading ?? process.env.DYNAMIC_MODEL_LOADING === \"true\";\n let httpLoader: HttpPackageLoader | undefined;\n\n if (registryUrl) {\n // Register HTTP/HTTPS module loader hooks for dynamic package imports\n register(httpsHooksPath, import.meta.url);\n httpLoader = new HttpPackageLoader({ registryUrl });\n registryPackages?.split(\",\").forEach((p) => {\n const name = p.trim();\n if (!packages.includes(name)) {\n packages.push(name);\n }\n });\n }\n\n const reactorLogger = logger.child([\"reactor\"]);\n const initializeClient = async (documentModels: DocumentModelModule[]) => {\n const eventBus = new EventBus();\n const builder = new ReactorBuilder()\n .withEventBus(eventBus)\n .withDocumentModels(\n getUniqueDocumentModels([\n documentModelDocumentModelModule,\n driveDocumentModelModule,\n reactorDriveDocumentModelModule as unknown as DocumentModelModule,\n ...vetraDocumentModels,\n ...documentModels,\n ]),\n )\n .withChannelScheme(ChannelScheme.SWITCHBOARD)\n .withSignalHandlers()\n .withLogger(reactorLogger);\n\n const maxSkipThreshold = parseInt(process.env.MAX_SKIP_THRESHOLD ?? \"\", 10);\n if (!isNaN(maxSkipThreshold) && maxSkipThreshold > 0) {\n builder.withExecutorConfig({ maxSkipThreshold });\n logger.info(`Reactor maxSkipThreshold set to ${maxSkipThreshold}`);\n }\n\n let baseKysely: Kysely<Database>;\n if (reactorDbUrl && isPostgresUrl(reactorDbUrl)) {\n const connectionString = reactorDbUrl.includes(\"?\")\n ? reactorDbUrl\n : `${reactorDbUrl}?sslmode=disable`;\n const pool = new Pool({ connectionString });\n baseKysely = new Kysely<Database>({\n dialect: new PostgresDialect({ pool }),\n });\n logger.info(\"Using PostgreSQL for reactor storage\");\n } else {\n if (!reactorPgliteDir || reactorPgliteMajor === null) {\n throw new Error(\"Reactor PGLite directory not resolved\");\n }\n const { PGlite } = await loadPGliteModule(reactorPgliteMajor);\n const pglite = PGLITE_IN_MEMORY\n ? new PGlite()\n : new PGlite({\n fs: new AtomicNodeFs(reactorPgliteDir, {\n logger,\n flushIntervalMs: PGLITE_FLUSH_INTERVAL_MS,\n }),\n });\n baseKysely = new Kysely<Database>({\n dialect: new ClosablePGliteDialect(pglite),\n });\n logger.info(\n PGLITE_IN_MEMORY\n ? `Using in-memory PGlite (PG${reactorPgliteMajor}) for reactor storage [PH_PGLITE_IN_MEMORY=1]`\n : `Using PGlite (PG${reactorPgliteMajor}) for reactor storage at ${reactorPgliteDir}`,\n );\n }\n builder.withKysely(baseKysely);\n\n const reactorDriveSchemaDb = baseKysely.withSchema(\n REACTOR_SCHEMA,\n ) as unknown as Kysely<ReactorDriveDatabase>;\n\n builder.withReadModelFactory(\n async ({\n operationIndex,\n writeCache,\n processorManagerConsistencyTracker,\n }) => {\n const nodeProcessor = new NodeProcessor(\n baseKysely as unknown as Kysely<unknown>,\n REACTOR_SCHEMA,\n operationIndex,\n writeCache,\n processorManagerConsistencyTracker,\n );\n await nodeProcessor.init();\n return nodeProcessor;\n },\n );\n\n builder.withShutdownHook(async () => {\n if (apiRef.current) await apiRef.current.dispose();\n });\n\n if (httpLoader && dynamicModelLoading) {\n builder.withDocumentModelLoader(httpLoader.documentModelLoader);\n }\n\n const clientBuilder = new ReactorClientBuilder().withReactorBuilder(\n builder,\n );\n\n if (renown) {\n const signerConfig = getRenownSignerConfig(\n renown,\n options.identity?.requireSignatures,\n );\n clientBuilder.withSigner(signerConfig);\n }\n\n const module = await clientBuilder.buildModule();\n\n if (module.reactorModule) {\n const instrumentation = new ReactorInstrumentation(module.reactorModule);\n instrumentation.start();\n reactorLogger.info(\"Reactor metrics instrumentation started\");\n }\n\n driveNodeView = new DriveNodeView(reactorDriveSchemaDb);\n const reactorDriveClient = new ReactorDriveClient({\n reactor: module.client,\n readModel: driveNodeView,\n });\n\n return { module, reactorDriveClient };\n };\n\n let defaultDriveUrl: undefined | string = undefined;\n\n // TODO get path from powerhouse config\n // start vite server if dev mode is enabled\n const basePath = process.cwd();\n const viteLogger = createViteLogger(logger);\n const vite = dev\n ? await startViteServer(process.cwd(), viteLogger)\n : undefined;\n\n // get paths to local document models\n if (!options.disableLocalPackages) {\n packages.push(basePath);\n }\n\n // create loaders\n const packageLoaders: IPackageLoader[] = [];\n if (vite) {\n packageLoaders.push(VitePackageLoader.build(vite));\n } else {\n packageLoaders.push(new ImportPackageLoader());\n }\n if (httpLoader) {\n packageLoaders.push(httpLoader);\n registryPackages?.split(\",\").forEach((p) => {\n const name = p.trim();\n if (!packages.includes(name)) {\n packages.push(name);\n }\n });\n }\n\n const apiLogger = logger.child([\"reactor-api\"]);\n // When the read-model store is on disk, hand reactor-api a factory that\n // constructs the matching PGLite (current or legacy) for the detected\n // PG_VERSION. reactor-api calls the factory synchronously, so the legacy\n // module is preloaded above.\n let pgliteFactory:\n | ((connectionString: string | undefined) => PGlite)\n | undefined;\n if (readModelPgliteDir && readModelPgliteMajor !== null) {\n const { PGlite: ReadModelPGlite } =\n await loadPGliteModule(readModelPgliteMajor);\n pgliteFactory = PGLITE_IN_MEMORY\n ? () => new ReadModelPGlite()\n : (connectionString) =>\n new ReadModelPGlite({\n fs: new AtomicNodeFs(\n connectionString ?? (readModelPgliteDir as string),\n { logger, flushIntervalMs: PGLITE_FLUSH_INTERVAL_MS },\n ),\n });\n }\n\n const api = await initializeAndStartAPI(\n initializeClient,\n {\n port: serverPort,\n dbPath: readModelPath,\n pgliteFactory,\n https: options.https,\n packageLoaders: packageLoaders.length > 0 ? packageLoaders : undefined,\n packages: packages,\n processorConfig: options.processorConfig,\n processors: {\n \"@powerhousedao/vetra\": [vetraProcessorFactory],\n },\n configFile:\n options.configFile ??\n path.join(process.cwd(), \"powerhouse.config.json\"),\n mcp: options.mcp ?? true,\n logger: apiLogger,\n enableDocumentModelSubgraphs: options.enableDocumentModelSubgraphs,\n },\n \"switchboard\",\n );\n apiRef.current = api;\n\n registerAttachmentRoutes(api);\n\n if (process.env.SENTRY_DSN) {\n // Register Sentry error handler after all routes are established.\n // The adapter calls the framework-specific Sentry setup internally.\n api.httpAdapter.setupSentryErrorHandler(Sentry);\n }\n\n const { client, graphqlManager, documentModelRegistry } = api;\n\n // Wire up dynamic package management if HTTP loader is configured\n if (httpLoader) {\n const packageManagementService = new PackageManagementService({\n defaultRegistryUrl: registryUrl,\n httpLoader,\n documentModelRegistry,\n });\n\n packageManagementService.setOnModelsChanged(() => {\n graphqlManager.regenerateDocumentModelSubgraphs().catch(logger.error);\n });\n\n const packagesSubgraph = new PackagesSubgraph({\n relationalDb: undefined as never,\n analyticsStore: undefined as never,\n reactorClient: client,\n graphqlManager,\n syncManager: api.syncManager,\n path: graphqlManager.getBasePath(),\n packageManagementService,\n });\n\n void graphqlManager\n .registerSubgraphInstance(packagesSubgraph, \"graphql\", false)\n .then(() => graphqlManager.updateRouter())\n .catch((error: unknown) => {\n logger.error(\"Failed to register packages subgraph: @error\", error);\n });\n }\n\n if (driveNodeView) {\n graphqlManager.setAdditionalContextFields({\n readModel: driveNodeView,\n });\n\n const reactorDriveSubgraph = {\n name: \"reactor-drive\",\n path: graphqlManager.getBasePath(),\n resolvers: createReactorDriveResolvers(),\n typeDefs: reactorDriveSubgraphTypeDefs,\n reactorClient: client,\n relationalDb: undefined as never,\n };\n\n void graphqlManager\n .registerSubgraphInstance(reactorDriveSubgraph, \"graphql\", false)\n .then(() => graphqlManager.updateRouter())\n .catch((error: unknown) => {\n logger.error(\n \"Failed to register reactor-drive subgraph: @error\",\n error,\n );\n });\n }\n\n // Create default drive if provided\n if (options.drive) {\n if (!renown) {\n throw new Error(\"Cannot create default drive without Renown identity\");\n }\n\n const driveType = options.drive.documentType ?? \"powerhouse/document-drive\";\n if (driveType === \"powerhouse/reactor-drive\") {\n defaultDriveUrl = await addDefaultReactorDrive(\n client,\n options.drive,\n serverPort,\n );\n } else {\n defaultDriveUrl = await addDefaultDrive(\n client,\n options.drive,\n serverPort,\n );\n }\n }\n\n // add vite middleware after express app is initialized if applicable\n if (vite) {\n api.httpAdapter.mountRawMiddleware(vite.middlewares);\n }\n\n // Connect to remote drives AFTER packages are loaded\n if (remoteDrives.length > 0) {\n for (const remoteDriveUrl of remoteDrives) {\n let driveId: string | undefined;\n\n try {\n const { syncManager } = api;\n const parsed = parseDriveUrl(remoteDriveUrl);\n driveId = parsed.driveId;\n const remoteName = `remote-drive-${driveId}-${crypto.randomUUID()}`;\n await syncManager.add(remoteName, driveCollectionId(\"main\", driveId), {\n type: \"gql\",\n parameters: { url: parsed.graphqlEndpoint },\n });\n logger.debug(\"Remote drive @remoteDriveUrl synced\", remoteDriveUrl);\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes(\"already exists\")\n ) {\n logger.debug(\n \"Remote drive already added: @remoteDriveUrl\",\n remoteDriveUrl,\n );\n driveId = remoteDriveUrl.split(\"/\").pop();\n } else {\n logger.error(\n \"Failed to connect to remote drive @remoteDriveUrl: @error\",\n remoteDriveUrl,\n error,\n );\n }\n } finally {\n // Construct local URL once in finally block\n if (!defaultDriveUrl && driveId) {\n const protocol = options.https ? \"https\" : \"http\";\n defaultDriveUrl = `${protocol}://localhost:${serverPort}/d/${driveId}`;\n }\n }\n }\n }\n\n return {\n defaultDriveUrl,\n api,\n reactor: client,\n renown,\n port: serverPort,\n };\n}\n\nexport const startSwitchboard = async (\n options: StartServerOptions = {},\n): Promise<SwitchboardReactor> => {\n const requestedPort = options.port ?? DEFAULT_PORT;\n const logger = options.logger ?? defaultLogger;\n const serverPort = await resolveServerPort(\n requestedPort,\n options.strictPort ?? false,\n logger,\n );\n\n // Initialize feature flags\n const featureFlags = await initFeatureFlags();\n\n const enableDocumentModelSubgraphs = await featureFlags.getBooleanValue(\n DOCUMENT_MODEL_SUBGRAPHS_ENABLED,\n options.enableDocumentModelSubgraphs ??\n DOCUMENT_MODEL_SUBGRAPHS_ENABLED_DEFAULT,\n );\n\n options.enableDocumentModelSubgraphs = enableDocumentModelSubgraphs;\n\n const requireSignatures =\n options.identity?.requireSignatures ??\n (await featureFlags.getBooleanValue(\n REQUIRE_SIGNATURES,\n REQUIRE_SIGNATURES_DEFAULT,\n ));\n options.identity = { ...options.identity, requireSignatures };\n\n logger.info(\n \"Feature flags: @flags\",\n JSON.stringify(\n {\n DOCUMENT_MODEL_SUBGRAPHS_ENABLED: enableDocumentModelSubgraphs,\n REQUIRE_SIGNATURES: requireSignatures,\n },\n null,\n 2,\n ),\n );\n\n // Initialize Renown if identity options are provided or keypair exists\n let renown: IRenown | null = null;\n try {\n renown = await initRenown(options.identity);\n } catch (e) {\n logger.warn(\"Failed to initialize ConnectCrypto: @error\", e);\n if (options.identity.requireExisting) {\n throw new Error(\n 'Identity required but failed to initialize. Run \"ph login\" first.',\n { cause: e },\n );\n }\n }\n\n try {\n return await initServer(serverPort, options, renown);\n } catch (e) {\n Sentry.captureException(e);\n logger.error(\"App crashed: @error\", e);\n throw e;\n }\n};\n\nexport * from \"./types.js\";\n\nif (import.meta.main) {\n await startSwitchboard();\n}\n"],"names":["fs","logger","#pglite","fs","fs","path","vetraDocumentModels","documentModels","vetraProcessorFactory"],"mappings":"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","debug_id":"3e29e419-6688-51d4-8af1-b05c7a1a5ffb"}
package/dist/server.d.mts CHANGED
@@ -4,6 +4,17 @@ import { DriveInput } from "@powerhousedao/shared/document-drive";
4
4
  import { IRenown } from "@renown/sdk";
5
5
 
6
6
  //#region src/types.d.ts
7
+ /**
8
+ * Drive container document type chosen for the boot-time default drive. The
9
+ * legacy `powerhouse/document-drive` keeps the existing behavior; the newer
10
+ * `powerhouse/reactor-drive` is wired through `addDefaultReactorDrive` and
11
+ * uses the relationship-based child graph.
12
+ */
13
+ type SwitchboardDriveDocumentType = "powerhouse/document-drive" | "powerhouse/reactor-drive";
14
+ /** A {@link DriveInput} extended with the optional drive-container type. */
15
+ type SwitchboardDriveInput = DriveInput & {
16
+ documentType?: SwitchboardDriveDocumentType;
17
+ };
7
18
  type StorageOptions = {
8
19
  type: "filesystem" | "memory" | "postgres" | "browser";
9
20
  filesystemPath?: string;
@@ -30,7 +41,7 @@ type StartServerOptions = {
30
41
  strictPort?: boolean;
31
42
  dev?: boolean;
32
43
  dbPath?: string;
33
- drive?: DriveInput;
44
+ drive?: SwitchboardDriveInput;
34
45
  packages?: string[];
35
46
  remoteDrives?: string[];
36
47
  https?: {
@@ -101,5 +112,5 @@ type SwitchboardReactor = {
101
112
  declare function isPortAvailable(port: number): Promise<boolean>;
102
113
  declare const startSwitchboard: (options?: StartServerOptions) => Promise<SwitchboardReactor>;
103
114
  //#endregion
104
- export { IdentityOptions, StartServerOptions, StorageOptions, SwitchboardReactor, isPortAvailable, startSwitchboard };
115
+ export { IdentityOptions, StartServerOptions, StorageOptions, SwitchboardDriveDocumentType, SwitchboardDriveInput, SwitchboardReactor, isPortAvailable, startSwitchboard };
105
116
  //# sourceMappingURL=server.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.mts","names":[],"sources":["../src/types.ts","../src/server.mts"],"mappings":";;;;;;KAKY,cAAA;EACV,IAAA;EACA,cAAA;EACA,WAAA;AAAA;AAAA,KAGU,eAAA;EALV,+DAOA,WAAA;EALA;;;AAGF;EAOE,eAAA;EAGA,OAAA,WARA;EAWA,iBAAA;AAAA;AAAA,KAGU,kBAAA;EACV,UAAA;EACA,IAAA;EAFU;;;;;EAQV,UAAA;EACA,GAAA;EACA,MAAA;EACA,KAAA,GAAQ,UAAA;EACR,QAAA;EACA,YAAA;EACA,KAAA;IAEM,OAAA;IACA,QAAA;EAAA;EAIN,IAAA;IACE,OAAA;IACA,MAAA;IACA,KAAA;IACA,MAAA;EAAA;EAJF;;;;;EAWA,QAAA,GAAW,eAAA;EACX,GAAA;EACA,eAAA,GAAkB,GAAA;EAClB,oBAAA;EACA,4BAAA;EADA;;;;;EAOA,mBAAA;EACA,MAAA,GAAS,OAAA;EAoBK;;AAGhB;;;;;;;EAbE,aAAA;EAiBQ;;;;;;;ACAV;;EDPE,cAAA;AAAA;AAAA,KAGU,kBAAA;EACV,eAAA;EACA,OAAA,EAAS,cAAA;EAET,MAAA,EAAQ,OAAA;ECybC;;;;EDpbT,IAAA;AAAA;;;;;;;AAjGF;iBC4FgB,eAAA,CAAgB,IAAA,WAAe,OAAA;AAAA,cAublC,gBAAA,GACX,OAAA,GAAS,kBAAA,KACR,OAAA,CAAQ,kBAAA"}
1
+ {"version":3,"file":"server.d.mts","names":[],"sources":["../src/types.ts","../src/server.mts"],"mappings":";;;;;;;;AAWA;;;;KAAY,4BAAA;AAKZ;AAAA,KAAY,qBAAA,GAAwB,UAAA;EAClC,YAAA,GAAe,4BAAA;AAAA;AAAA,KAGL,cAAA;EACV,IAAA;EACA,cAAA;EACA,WAAA;AAAA;AAAA,KAGU,eAAA;EANc,+DAQxB,WAAA;EARwB;;;;EAaxB,eAAA,YAVW;EAaX,OAAA,WAVyB;EAazB,iBAAA;AAAA;AAAA,KAGU,kBAAA;EACV,UAAA;EACA,IAAA;EALA;;;AAGF;;EAQE,UAAA;EACA,GAAA;EACA,MAAA;EACA,KAAA,GAAQ,qBAAA;EACR,QAAA;EACA,YAAA;EACA,KAAA;IAEM,OAAA;IACA,QAAA;EAAA;EAIN,IAAA;IACE,OAAA;IACA,MAAA;IACA,KAAA;IACA,MAAA;EAAA;EAXF;;;;;EAkBA,QAAA,GAAW,eAAA;EACX,GAAA;EACA,eAAA,GAAkB,GAAA;EAClB,oBAAA;EACA,4BAAA;EAHA;;;;;EASA,mBAAA;EACA,MAAA,GAAS,OAAA;EAAA;;;;;AAuBX;;;;EAbE,aAAA;EAeA;;;;;;;;;EALA,cAAA;AAAA;AAAA,KAGU,kBAAA;EACV,eAAA;EACA,OAAA,EAAS,cAAA,ECC2C;EDCpD,MAAA,EAAQ,OAAA;EC0jBT;;;;EDrjBC,IAAA;AAAA;;;;;;;AA1GF;iBCoGgB,eAAA,CAAgB,IAAA,WAAe,OAAA;AAAA,cA4flC,gBAAA,GACX,OAAA,GAAS,kBAAA,KACR,OAAA,CAAQ,kBAAA"}
package/dist/server.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { n as startSwitchboard, t as isPortAvailable } from "./server-Dd7gPta4.mjs";
3
- import "./utils-DFl0ezBT.mjs";
2
+ import { n as startSwitchboard, t as isPortAvailable } from "./server-DwBiiN-E.mjs";
3
+ import "./utils-BVNg1DRI.mjs";
4
4
  export { isPortAvailable, startSwitchboard };
5
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="55691e3c-4530-5b0d-9732-1b7f49600a17")}catch(e){}}();
6
- //# debugId=55691e3c-4530-5b0d-9732-1b7f49600a17
5
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="26b79028-d374-57d0-b0a6-2f10ac37c97b")}catch(e){}}();
6
+ //# debugId=26b79028-d374-57d0-b0a6-2f10ac37c97b
@@ -1,5 +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]="288dca06-b884-52d3-95cc-2b04c8be496f")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="62094c88-5d64-5b09-a31f-23862e9c8977")}catch(e){}}();
3
+ import { reactorDriveCreateDocument, reactorDriveCreateState } from "@powerhousedao/reactor-drive";
3
4
  import { driveCreateDocument, driveCreateState } from "@powerhousedao/shared/document-drive";
4
5
  import "@powerhousedao/shared/document-model";
5
6
  //#region src/utils.mts
@@ -37,11 +38,44 @@ async function addDefaultDrive(client, drive, serverPort) {
37
38
  }
38
39
  return `http://localhost:${serverPort}/d/${driveId}`;
39
40
  }
41
+ async function addDefaultReactorDrive(client, drive, serverPort) {
42
+ let driveId = drive.id;
43
+ if (!driveId || driveId.length === 0) driveId = drive.slug;
44
+ if (!driveId || driveId.length === 0) throw new Error("Invalid Drive Id");
45
+ let existingDrive;
46
+ try {
47
+ existingDrive = await client.get(driveId);
48
+ } catch {}
49
+ if (existingDrive) return `http://localhost:${serverPort}/d/${driveId}`;
50
+ const { global, local } = reactorDriveCreateState();
51
+ const document = reactorDriveCreateDocument({
52
+ global: {
53
+ ...global,
54
+ name: drive.global.name,
55
+ icon: drive.global.icon ?? global.icon
56
+ },
57
+ local: {
58
+ ...local,
59
+ availableOffline: drive.local?.availableOffline ?? local.availableOffline,
60
+ sharingType: drive.local?.sharingType ?? local.sharingType
61
+ }
62
+ });
63
+ if (drive.id && drive.id.length > 0) document.header.id = drive.id;
64
+ if (drive.slug && drive.slug.length > 0) document.header.slug = drive.slug;
65
+ if (drive.global.name) document.header.name = drive.global.name;
66
+ if (drive.preferredEditor) document.header.meta = { preferredEditor: drive.preferredEditor };
67
+ try {
68
+ await client.create(document);
69
+ } catch (e) {
70
+ if (!(e instanceof Error ? e.message : String(e)).includes("already exists")) throw e;
71
+ }
72
+ return `http://localhost:${serverPort}/d/${driveId}`;
73
+ }
40
74
  function isPostgresUrl(url) {
41
75
  return url.startsWith("postgresql") || url.startsWith("postgres");
42
76
  }
43
77
  //#endregion
44
- export { isPostgresUrl as n, addDefaultDrive as t };
78
+ export { addDefaultReactorDrive as n, isPostgresUrl as r, addDefaultDrive as t };
45
79
 
46
- //# sourceMappingURL=utils-DFl0ezBT.mjs.map
47
- //# debugId=288dca06-b884-52d3-95cc-2b04c8be496f
80
+ //# sourceMappingURL=utils-BVNg1DRI.mjs.map
81
+ //# debugId=62094c88-5d64-5b09-a31f-23862e9c8977
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils-BVNg1DRI.mjs","sources":["../src/utils.mts"],"sourcesContent":["import type { IReactorClient } from \"@powerhousedao/reactor\";\nimport {\n reactorDriveCreateDocument,\n reactorDriveCreateState,\n} from \"@powerhousedao/reactor-drive\";\nimport type { DocumentDriveDocument } from \"@powerhousedao/shared/document-drive\";\nimport {\n driveCreateDocument,\n driveCreateState,\n} from \"@powerhousedao/shared/document-drive\";\nimport type { DriveInput } from \"@powerhousedao/shared/document-drive\";\nimport { generateId } from \"@powerhousedao/shared/document-model\";\n\nexport async function addDefaultDrive(\n client: IReactorClient,\n drive: DriveInput,\n serverPort: number,\n) {\n let driveId = drive.id;\n if (!driveId || driveId.length === 0) {\n driveId = drive.slug;\n }\n\n if (!driveId || driveId.length === 0) {\n throw new Error(\"Invalid Drive Id\");\n }\n\n // check if the drive already exists\n let existingDrive;\n try {\n existingDrive = await client.get(driveId);\n } catch {\n //\n }\n\n // already exists, return the existing drive url\n if (existingDrive) {\n return `http://localhost:${serverPort}/d/${driveId}`;\n }\n\n const { global } = driveCreateState();\n const document = driveCreateDocument({\n global: {\n ...global,\n name: drive.global.name,\n icon: drive.global.icon ?? global.icon,\n },\n local: {\n availableOffline: drive.local?.availableOffline ?? false,\n sharingType: drive.local?.sharingType ?? \"public\",\n listeners: drive.local?.listeners ?? [],\n triggers: drive.local?.triggers ?? [],\n },\n });\n\n if (drive.id && drive.id.length > 0) {\n document.header.id = drive.id;\n }\n if (drive.slug && drive.slug.length > 0) {\n document.header.slug = drive.slug;\n }\n if (drive.global.name) {\n document.header.name = drive.global.name;\n }\n if (drive.preferredEditor) {\n document.header.meta = { preferredEditor: drive.preferredEditor };\n }\n\n try {\n await client.create(document);\n } catch (e) {\n const errorMessage = e instanceof Error ? e.message : String(e);\n if (!errorMessage.includes(\"already exists\")) {\n throw e;\n }\n }\n\n return `http://localhost:${serverPort}/d/${driveId}`;\n}\n\nexport async function addDefaultReactorDrive(\n client: IReactorClient,\n drive: DriveInput,\n serverPort: number,\n) {\n let driveId = drive.id;\n if (!driveId || driveId.length === 0) {\n driveId = drive.slug;\n }\n\n if (!driveId || driveId.length === 0) {\n throw new Error(\"Invalid Drive Id\");\n }\n\n let existingDrive;\n try {\n existingDrive = await client.get(driveId);\n } catch {\n //\n }\n\n if (existingDrive) {\n return `http://localhost:${serverPort}/d/${driveId}`;\n }\n\n const { global, local } = reactorDriveCreateState();\n const document = reactorDriveCreateDocument({\n global: {\n ...global,\n name: drive.global.name,\n icon: drive.global.icon ?? global.icon,\n },\n local: {\n ...local,\n availableOffline: drive.local?.availableOffline ?? local.availableOffline,\n sharingType: drive.local?.sharingType ?? local.sharingType,\n },\n });\n\n if (drive.id && drive.id.length > 0) {\n document.header.id = drive.id;\n }\n if (drive.slug && drive.slug.length > 0) {\n document.header.slug = drive.slug;\n }\n if (drive.global.name) {\n document.header.name = drive.global.name;\n }\n if (drive.preferredEditor) {\n document.header.meta = { preferredEditor: drive.preferredEditor };\n }\n\n try {\n await client.create(document);\n } catch (e) {\n const errorMessage = e instanceof Error ? e.message : String(e);\n if (!errorMessage.includes(\"already exists\")) {\n throw e;\n }\n }\n\n return `http://localhost:${serverPort}/d/${driveId}`;\n}\n\nexport function isPostgresUrl(url: string) {\n return url.startsWith(\"postgresql\") || url.startsWith(\"postgres\");\n}\n"],"names":[],"mappings":";;;;;;AAaA,eAAsB,gBACpB,QACA,OACA,YACA;CACA,IAAI,UAAU,MAAM;AACpB,KAAI,CAAC,WAAW,QAAQ,WAAW,EACjC,WAAU,MAAM;AAGlB,KAAI,CAAC,WAAW,QAAQ,WAAW,EACjC,OAAM,IAAI,MAAM,mBAAmB;CAIrC,IAAI;AACJ,KAAI;AACF,kBAAgB,MAAM,OAAO,IAAI,QAAQ;SACnC;AAKR,KAAI,cACF,QAAO,oBAAoB,WAAW,KAAK;CAG7C,MAAM,EAAE,WAAW,kBAAkB;CACrC,MAAM,WAAW,oBAAoB;EACnC,QAAQ;GACN,GAAG;GACH,MAAM,MAAM,OAAO;GACnB,MAAM,MAAM,OAAO,QAAQ,OAAO;GACnC;EACD,OAAO;GACL,kBAAkB,MAAM,OAAO,oBAAoB;GACnD,aAAa,MAAM,OAAO,eAAe;GACzC,WAAW,MAAM,OAAO,aAAa,EAAE;GACvC,UAAU,MAAM,OAAO,YAAY,EAAE;GACtC;EACF,CAAC;AAEF,KAAI,MAAM,MAAM,MAAM,GAAG,SAAS,EAChC,UAAS,OAAO,KAAK,MAAM;AAE7B,KAAI,MAAM,QAAQ,MAAM,KAAK,SAAS,EACpC,UAAS,OAAO,OAAO,MAAM;AAE/B,KAAI,MAAM,OAAO,KACf,UAAS,OAAO,OAAO,MAAM,OAAO;AAEtC,KAAI,MAAM,gBACR,UAAS,OAAO,OAAO,EAAE,iBAAiB,MAAM,iBAAiB;AAGnE,KAAI;AACF,QAAM,OAAO,OAAO,SAAS;UACtB,GAAG;AAEV,MAAI,EADiB,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,EAC7C,SAAS,iBAAiB,CAC1C,OAAM;;AAIV,QAAO,oBAAoB,WAAW,KAAK;;AAG7C,eAAsB,uBACpB,QACA,OACA,YACA;CACA,IAAI,UAAU,MAAM;AACpB,KAAI,CAAC,WAAW,QAAQ,WAAW,EACjC,WAAU,MAAM;AAGlB,KAAI,CAAC,WAAW,QAAQ,WAAW,EACjC,OAAM,IAAI,MAAM,mBAAmB;CAGrC,IAAI;AACJ,KAAI;AACF,kBAAgB,MAAM,OAAO,IAAI,QAAQ;SACnC;AAIR,KAAI,cACF,QAAO,oBAAoB,WAAW,KAAK;CAG7C,MAAM,EAAE,QAAQ,UAAU,yBAAyB;CACnD,MAAM,WAAW,2BAA2B;EAC1C,QAAQ;GACN,GAAG;GACH,MAAM,MAAM,OAAO;GACnB,MAAM,MAAM,OAAO,QAAQ,OAAO;GACnC;EACD,OAAO;GACL,GAAG;GACH,kBAAkB,MAAM,OAAO,oBAAoB,MAAM;GACzD,aAAa,MAAM,OAAO,eAAe,MAAM;GAChD;EACF,CAAC;AAEF,KAAI,MAAM,MAAM,MAAM,GAAG,SAAS,EAChC,UAAS,OAAO,KAAK,MAAM;AAE7B,KAAI,MAAM,QAAQ,MAAM,KAAK,SAAS,EACpC,UAAS,OAAO,OAAO,MAAM;AAE/B,KAAI,MAAM,OAAO,KACf,UAAS,OAAO,OAAO,MAAM,OAAO;AAEtC,KAAI,MAAM,gBACR,UAAS,OAAO,OAAO,EAAE,iBAAiB,MAAM,iBAAiB;AAGnE,KAAI;AACF,QAAM,OAAO,OAAO,SAAS;UACtB,GAAG;AAEV,MAAI,EADiB,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,EAC7C,SAAS,iBAAiB,CAC1C,OAAM;;AAIV,QAAO,oBAAoB,WAAW,KAAK;;AAG7C,SAAgB,cAAc,KAAa;AACzC,QAAO,IAAI,WAAW,aAAa,IAAI,IAAI,WAAW,WAAW","debug_id":"62094c88-5d64-5b09-a31f-23862e9c8977"}
package/dist/utils.d.mts CHANGED
@@ -3,7 +3,8 @@ import { DriveInput } from "@powerhousedao/shared/document-drive";
3
3
 
4
4
  //#region src/utils.d.mts
5
5
  declare function addDefaultDrive(client: IReactorClient, drive: DriveInput, serverPort: number): Promise<string>;
6
+ declare function addDefaultReactorDrive(client: IReactorClient, drive: DriveInput, serverPort: number): Promise<string>;
6
7
  declare function isPostgresUrl(url: string): boolean;
7
8
  //#endregion
8
- export { addDefaultDrive, isPostgresUrl };
9
+ export { addDefaultDrive, addDefaultReactorDrive, isPostgresUrl };
9
10
  //# sourceMappingURL=utils.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.mts","names":[],"sources":["../src/utils.mts"],"mappings":";;;;iBASsB,eAAA,CACpB,MAAA,EAAQ,cAAA,EACR,KAAA,EAAO,UAAA,EACP,UAAA,WAAkB,OAAA;AAAA,iBAgEJ,aAAA,CAAc,GAAA"}
1
+ {"version":3,"file":"utils.d.mts","names":[],"sources":["../src/utils.mts"],"mappings":";;;;iBAasB,eAAA,CACpB,MAAA,EAAQ,cAAA,EACR,KAAA,EAAO,UAAA,EACP,UAAA,WAAkB,OAAA;AAAA,iBAgEE,sBAAA,CACpB,MAAA,EAAQ,cAAA,EACR,KAAA,EAAO,UAAA,EACP,UAAA,WAAkB,OAAA;AAAA,iBA6DJ,aAAA,CAAc,GAAA"}
package/dist/utils.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as isPostgresUrl, t as addDefaultDrive } from "./utils-DFl0ezBT.mjs";
2
- export { addDefaultDrive, isPostgresUrl };
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]="b4435062-9bc0-5516-93ea-69f340943749")}catch(e){}}();
4
- //# debugId=b4435062-9bc0-5516-93ea-69f340943749
1
+ import { n as addDefaultReactorDrive, r as isPostgresUrl, t as addDefaultDrive } from "./utils-BVNg1DRI.mjs";
2
+ export { addDefaultDrive, addDefaultReactorDrive, isPostgresUrl };
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]="d3cbfa9b-aa13-5d02-b8eb-918b66a4c4e2")}catch(e){}}();
4
+ //# debugId=d3cbfa9b-aa13-5d02-b8eb-918b66a4c4e2
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@powerhousedao/switchboard",
3
3
  "type": "module",
4
- "version": "6.0.0-dev.253",
4
+ "version": "6.0.0-dev.254",
5
5
  "main": "dist/index.mjs",
6
6
  "exports": {
7
7
  ".": {
@@ -62,16 +62,17 @@
62
62
  "kysely-pglite-dialect": "1.2.0",
63
63
  "pg": "8.18.0",
64
64
  "vite": "8.0.8",
65
- "@powerhousedao/config": "6.0.0-dev.253",
66
- "@powerhousedao/opentelemetry-instrumentation-reactor": "6.0.0-dev.253",
67
- "@powerhousedao/reactor": "6.0.0-dev.253",
68
- "@powerhousedao/shared": "6.0.0-dev.253",
69
- "@powerhousedao/vetra": "6.0.0-dev.253",
70
- "@powerhousedao/reactor-api": "6.0.0-dev.253",
71
- "@powerhousedao/reactor-attachments": "6.0.0-dev.253",
72
- "@powerhousedao/pglite-fs": "6.0.0-dev.253",
73
- "@renown/sdk": "6.0.0-dev.253",
74
- "document-model": "6.0.0-dev.253"
65
+ "@powerhousedao/config": "6.0.0-dev.254",
66
+ "@powerhousedao/shared": "6.0.0-dev.254",
67
+ "@powerhousedao/reactor": "6.0.0-dev.254",
68
+ "@powerhousedao/vetra": "6.0.0-dev.254",
69
+ "@powerhousedao/opentelemetry-instrumentation-reactor": "6.0.0-dev.254",
70
+ "@powerhousedao/reactor-api": "6.0.0-dev.254",
71
+ "@powerhousedao/reactor-attachments": "6.0.0-dev.254",
72
+ "@powerhousedao/reactor-drive": "6.0.0-dev.254",
73
+ "@powerhousedao/pglite-fs": "6.0.0-dev.254",
74
+ "@renown/sdk": "6.0.0-dev.254",
75
+ "document-model": "6.0.0-dev.254"
75
76
  },
76
77
  "devDependencies": {
77
78
  "@types/express": "^4.17.25",
package/tsconfig.json CHANGED
@@ -31,6 +31,9 @@
31
31
  {
32
32
  "path": "../../packages/reactor-attachments"
33
33
  },
34
+ {
35
+ "path": "../../packages/reactor-drive"
36
+ },
34
37
  {
35
38
  "path": "../../packages/renown"
36
39
  },
@@ -1 +0,0 @@
1
- {"version":3,"file":"server-Dd7gPta4.mjs","sources":["../src/pglite-version.ts","../src/attachments/auth.ts","../src/attachments/mount-auth.ts","../src/attachments/routes.ts","../src/attachments/index.ts","../src/feature-flags.ts","../src/pglite-dialect.ts","../src/pglite-migration.ts","../src/renown.ts","../src/server.mts"],"sourcesContent":["import type * as CurrentPGliteModuleNs from \"@electric-sql/pglite\";\nimport { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\n\nexport const CURRENT_PG_MAJOR = 17;\nexport const SUPPORTED_PG_MAJORS = [16, 17] as const;\nexport type SupportedPgMajor = (typeof SUPPORTED_PG_MAJORS)[number];\n\ntype CurrentPGliteModule = typeof CurrentPGliteModuleNs;\n\nexport async function readPgVersionFile(\n dataDir: string,\n): Promise<number | null> {\n try {\n const raw = await fs.readFile(path.join(dataDir, \"PG_VERSION\"), \"utf8\");\n const major = parseInt(raw.trim(), 10);\n return Number.isFinite(major) ? major : null;\n } catch {\n return null;\n }\n}\n\nexport function isSupportedMajor(major: number): major is SupportedPgMajor {\n return (SUPPORTED_PG_MAJORS as readonly number[]).includes(major);\n}\n\n/**\n * Parses the `PH_FORCE_PG_VERSION` env var. Returns the validated major, or\n * `null` when the var is unset/empty. Throws on any value that is not a\n * supported major — invalid configuration must fail before the server starts\n * touching disk.\n */\nexport function parseForcePgVersion(\n raw: string | undefined,\n): SupportedPgMajor | null {\n if (raw === undefined || raw.trim() === \"\") return null;\n const parsed = Number(raw);\n if (Number.isInteger(parsed) && isSupportedMajor(parsed)) return parsed;\n throw new Error(\n `PH_FORCE_PG_VERSION must be one of: ${SUPPORTED_PG_MAJORS.join(\", \")} (got: ${raw})`,\n );\n}\n\nexport async function loadPGliteModule(\n major: SupportedPgMajor,\n): Promise<CurrentPGliteModule> {\n if (major === 16) {\n return (await import(\"pglite-legacy-02\")) as unknown as CurrentPGliteModule;\n }\n return import(\"@electric-sql/pglite\");\n}\n\ntype PgDumpFn = (options: {\n pg: unknown;\n}) => Promise<{ text(): Promise<string> }>;\n\nexport async function loadPgDump(major: SupportedPgMajor): Promise<PgDumpFn> {\n if (major === 16) {\n const mod = (await import(\"pglite-tools-legacy-02/pg_dump\")) as {\n pgDump: PgDumpFn;\n };\n return mod.pgDump;\n }\n const mod = (await import(\"@electric-sql/pglite-tools/pg_dump\")) as {\n pgDump: PgDumpFn;\n };\n return mod.pgDump;\n}\n","import type { AuthService } from \"@powerhousedao/reactor-api\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\n\nexport type NodeHandler = (\n req: IncomingMessage,\n res: ServerResponse,\n) => Promise<void> | void;\n\n/**\n * Wrap a Node-style handler so that, when `authService` is provided and auth is\n * enabled, the request must carry a verifiable Bearer token.\n */\nexport function requireAuth(\n authService: AuthService | undefined,\n handler: NodeHandler,\n): NodeHandler {\n if (!authService) return handler;\n\n return async (req, res) => {\n let result;\n try {\n result = await authService.verifyBearer(req.headers.authorization);\n } catch {\n res.statusCode = 500;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify({ error: \"Internal authentication error\" }));\n return;\n }\n\n if (result instanceof Response) {\n const body = await result.text();\n res.statusCode = result.status;\n const contentType = result.headers.get(\"content-type\");\n if (contentType) res.setHeader(\"Content-Type\", contentType);\n res.end(body);\n return;\n }\n\n if (result.auth_enabled && !result.user) {\n res.statusCode = 401;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify({ error: \"Authentication required\" }));\n return;\n }\n\n await handler(req, res);\n };\n}\n","import type { API } from \"@powerhousedao/reactor-api\";\nimport { requireAuth, type NodeHandler } from \"./auth.js\";\n\nexport type HttpMethod = \"DELETE\" | \"GET\" | \"HEAD\" | \"POST\" | \"PUT\";\n\n/**\n * Mount a Node-style attachment route with `requireAuth` applied unconditionally.\n * When `api.authService` is undefined (auth disabled), `requireAuth` returns the\n * handler unchanged — that is the only way to opt out. To register a route\n * without auth wrapping you must call `api.httpAdapter.mountNodeRoute` directly.\n */\nexport function mountAuthenticatedNodeRoute(\n api: Pick<API, \"httpAdapter\" | \"authService\">,\n method: HttpMethod,\n path: string,\n handler: NodeHandler,\n): void {\n api.httpAdapter.mountNodeRoute(\n method,\n path,\n requireAuth(api.authService, handler),\n );\n}\n","import {\n AttachmentNotFound,\n InvalidAttachmentRef,\n ReservationNotFound,\n type AttachmentBuildResult,\n type ReserveAttachmentOptions,\n} from \"@powerhousedao/reactor-attachments\";\nimport type { AttachmentHash } from \"@powerhousedao/reactor\";\nimport { childLogger } from \"document-model\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { Readable } from \"node:stream\";\nimport type { ReadableStream as NodeReadableStream } from \"node:stream/web\";\n\nconst logger = childLogger([\"switchboard\", \"attachments\"]);\n\n// Canonical form is lowercase hex (the SHA-256 hasher emits lowercase), but\n// accept either case from the wire and normalise before lookup. This keeps\n// the API forgiving for hand-typed URLs without changing storage semantics.\nconst HASH_PATTERN = /^[a-f0-9]{64}$/i;\n// eslint-disable-next-line no-control-regex\nconst CONTROL_CHARS = /[\\x00-\\x1f\\x7f]/;\n// RFC 6838 token chars; allows optional `; param=value` pairs (token or quoted-string).\nconst MIME_TYPE_PATTERN =\n /^[!#$%&'*+\\-.^_`|~\\w]+\\/[!#$%&'*+\\-.^_`|~\\w]+(?:\\s*;\\s*[!#$%&'*+\\-.^_`|~\\w]+=(?:[!#$%&'*+\\-.^_`|~\\w]+|\"(?:[^\"\\\\\\r\\n]|\\\\[^\\r\\n])*\"))*$/;\nconst MAX_FILENAME_LEN = 255;\nconst MAX_MIMETYPE_LEN = 255;\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n res.statusCode = status;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify(body));\n}\n\nfunction sendError(res: ServerResponse, status: number, message: string): void {\n sendJson(res, status, { error: message });\n}\n\nfunction statusForError(err: unknown): number {\n if (err instanceof AttachmentNotFound) return 404;\n if (err instanceof ReservationNotFound) return 404;\n if (err instanceof InvalidAttachmentRef) return 400;\n return 500;\n}\n\nfunction sendErrorFromException(res: ServerResponse, err: unknown): void {\n const status = statusForError(err);\n if (status >= 500) {\n logger.error(\"Attachment route error: @error\", err);\n sendError(res, status, \"Internal error\");\n return;\n }\n sendError(res, status, err instanceof Error ? err.message : String(err));\n}\n\nasync function readJsonBody(\n req: IncomingMessage,\n body: unknown,\n): Promise<unknown> {\n // The Express body-parser may have already populated `body`. When that\n // happens we trust it; otherwise read the raw stream ourselves so this\n // module is independent of upstream middleware ordering.\n if (body !== undefined && body !== null && typeof body === \"object\") {\n return body;\n }\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(chunk as Buffer);\n }\n if (chunks.length === 0) return undefined;\n const text = Buffer.concat(chunks).toString(\"utf8\");\n if (text.length === 0) return undefined;\n return JSON.parse(text);\n}\n\nexport function parseReserveOptions(\n input: unknown,\n): ReserveAttachmentOptions | null {\n if (input === null || typeof input !== \"object\") return null;\n const obj = input as Record<string, unknown>;\n if (\n typeof obj.mimeType !== \"string\" ||\n obj.mimeType.length === 0 ||\n obj.mimeType.length > MAX_MIMETYPE_LEN ||\n !MIME_TYPE_PATTERN.test(obj.mimeType)\n ) {\n return null;\n }\n if (\n typeof obj.fileName !== \"string\" ||\n obj.fileName.length === 0 ||\n obj.fileName.length > MAX_FILENAME_LEN ||\n CONTROL_CHARS.test(obj.fileName)\n ) {\n return null;\n }\n let extension: string | null = null;\n if (typeof obj.extension === \"string\") {\n if (obj.extension.length === 0 || /[\\\\/]/.test(obj.extension)) return null;\n extension = obj.extension;\n } else if (obj.extension !== undefined && obj.extension !== null) {\n return null;\n }\n return {\n mimeType: obj.mimeType,\n fileName: obj.fileName,\n extension,\n };\n}\n\nexport function quoteFilename(name: string): string {\n // RFC 6266: quoted-string with internal \" and \\ escaped.\n return `\"${name.replace(/[\\\\\"]/g, \"\\\\$&\")}\"`;\n}\n\nexport function buildContentDisposition(fileName: string): string {\n // ASCII fallback: replace any byte outside printable ASCII (0x20-0x7e),\n // plus `\"` and `\\`, with `_`. Browsers fall back to this when they don't\n // grok `filename*=`; the modern parameter carries the real name.\n const ascii = fileName.replace(/[^\\x20-\\x21\\x23-\\x5b\\x5d-\\x7e]/g, \"_\");\n // RFC 5987: percent-encode UTF-8 bytes. encodeURIComponent leaves a few\n // chars that 5987 disallows in token; re-encode them.\n const encoded = encodeURIComponent(fileName).replace(\n /['()*!]/g,\n (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n return `attachment; filename=${quoteFilename(ascii)}; filename*=UTF-8''${encoded}`;\n}\n\nexport function makeReserveHandler(attachments: AttachmentBuildResult) {\n return async (\n req: IncomingMessage,\n res: ServerResponse,\n body?: unknown,\n ): Promise<void> => {\n let parsed: unknown;\n try {\n parsed = await readJsonBody(req, body);\n } catch {\n sendError(res, 400, \"Invalid JSON body\");\n return;\n }\n const opts = parseReserveOptions(parsed);\n if (!opts) {\n sendError(\n res,\n 400,\n \"Body must be { mimeType: string (type/subtype), fileName: string (no control characters, max 255 chars), extension?: string|null }\",\n );\n return;\n }\n try {\n const upload = await attachments.service.reserve(opts);\n sendJson(res, 201, { reservationId: upload.reservationId });\n } catch (err) {\n sendErrorFromException(res, err);\n }\n };\n}\n\nexport function makeUploadHandler(attachments: AttachmentBuildResult) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const reservationId = extractParam(req, \"reservationId\");\n if (!reservationId) {\n sendError(res, 400, \"Missing reservationId\");\n return;\n }\n\n let reservation;\n try {\n reservation = await attachments.reservations.get(reservationId);\n } catch (err) {\n sendErrorFromException(res, err);\n return;\n }\n\n const upload = attachments.uploadFactory.createUpload(\n reservation.reservationId,\n {\n mimeType: reservation.mimeType,\n fileName: reservation.fileName,\n extension: reservation.extension,\n },\n );\n\n const webStream = Readable.toWeb(\n req as Readable,\n ) as ReadableStream<Uint8Array>;\n\n try {\n const result = await upload.send(webStream);\n sendJson(res, 200, result);\n } catch (err) {\n sendErrorFromException(res, err);\n }\n };\n}\n\nexport function makeDownloadHandler(attachments: AttachmentBuildResult) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const hash = extractParam(req, \"hash\");\n if (!hash || !HASH_PATTERN.test(hash)) {\n sendError(res, 400, \"Invalid attachment hash\");\n return;\n }\n\n const controller = new AbortController();\n req.once(\"close\", () => controller.abort());\n\n const canonicalHash = hash.toLowerCase() as AttachmentHash;\n let response;\n try {\n response = await attachments.store.get(canonicalHash, controller.signal);\n } catch (err) {\n sendErrorFromException(res, err);\n return;\n }\n\n const { header, body } = response;\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", header.mimeType);\n res.setHeader(\"Content-Length\", String(header.sizeBytes));\n res.setHeader(\n \"Content-Disposition\",\n buildContentDisposition(header.fileName),\n );\n res.setHeader(\"Attachment-Metadata\", buildMetadataHeader(header));\n\n Readable.fromWeb(body as unknown as NodeReadableStream<Uint8Array>).pipe(\n res,\n );\n };\n}\n\nfunction buildMetadataHeader(header: {\n mimeType: string;\n fileName: string;\n sizeBytes: number;\n extension: string | null;\n createdAtUtc: string;\n lastAccessedAtUtc: string;\n}): string {\n return JSON.stringify({\n mimeType: header.mimeType,\n fileName: header.fileName,\n sizeBytes: header.sizeBytes,\n extension: header.extension,\n createdAtUtc: header.createdAtUtc,\n lastAccessedAtUtc: header.lastAccessedAtUtc,\n });\n}\n\nexport function makeStatHandler(attachments: AttachmentBuildResult) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const hash = extractParam(req, \"hash\");\n if (!hash || !HASH_PATTERN.test(hash)) {\n sendError(res, 400, \"Invalid attachment hash\");\n return;\n }\n\n const canonicalHash = hash.toLowerCase() as AttachmentHash;\n let header;\n try {\n header = await attachments.store.stat(canonicalHash);\n } catch (err) {\n sendErrorFromException(res, err);\n return;\n }\n\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", header.mimeType);\n res.setHeader(\"Content-Length\", String(header.sizeBytes));\n res.setHeader(\n \"Content-Disposition\",\n buildContentDisposition(header.fileName),\n );\n res.setHeader(\"Attachment-Metadata\", buildMetadataHeader(header));\n res.end();\n };\n}\n\nexport function makeGetReservationHandler(attachments: AttachmentBuildResult) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const reservationId = extractParam(req, \"reservationId\");\n if (!reservationId) {\n sendError(res, 400, \"Missing reservationId\");\n return;\n }\n try {\n const reservation = await attachments.reservations.get(reservationId);\n sendJson(res, 200, reservation);\n } catch (err) {\n sendErrorFromException(res, err);\n }\n };\n}\n\nexport function makeDeleteReservationHandler(\n attachments: AttachmentBuildResult,\n) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const reservationId = extractParam(req, \"reservationId\");\n if (!reservationId) {\n sendError(res, 400, \"Missing reservationId\");\n return;\n }\n try {\n await attachments.reservations.delete(reservationId);\n res.statusCode = 204;\n res.end();\n } catch (err) {\n sendErrorFromException(res, err);\n }\n };\n}\n\nfunction extractParam(req: IncomingMessage, name: string): string | undefined {\n const expressParams = (\n req as IncomingMessage & {\n params?: Record<string, string>;\n }\n ).params;\n return expressParams?.[name];\n}\n","import type { API } from \"@powerhousedao/reactor-api\";\nimport { mountAuthenticatedNodeRoute } from \"./mount-auth.js\";\nimport {\n makeDeleteReservationHandler,\n makeDownloadHandler,\n makeGetReservationHandler,\n makeReserveHandler,\n makeStatHandler,\n makeUploadHandler,\n} from \"./routes.js\";\n\nexport function registerAttachmentRoutes(api: API): void {\n const { attachments } = api;\n\n mountAuthenticatedNodeRoute(\n api,\n \"POST\",\n \"/attachments/reservations\",\n makeReserveHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"GET\",\n \"/attachments/reservations/:reservationId\",\n makeGetReservationHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"DELETE\",\n \"/attachments/reservations/:reservationId\",\n makeDeleteReservationHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"PUT\",\n \"/attachments/reservations/:reservationId\",\n makeUploadHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"HEAD\",\n \"/attachments/:hash\",\n makeStatHandler(attachments),\n );\n\n mountAuthenticatedNodeRoute(\n api,\n \"GET\",\n \"/attachments/:hash\",\n makeDownloadHandler(attachments),\n );\n}\n","import { EnvVarProvider } from \"@openfeature/env-var-provider\";\nimport { OpenFeature } from \"@openfeature/server-sdk\";\n\nexport async function initFeatureFlags() {\n // for now, we're only using env vars for feature flags\n const provider = new EnvVarProvider();\n\n await OpenFeature.setProviderAndWait(provider);\n\n return OpenFeature.getClient();\n}\n","import type { PGlite } from \"@electric-sql/pglite\";\nimport type { Driver } from \"kysely\";\nimport { PGliteDialect } from \"kysely-pglite-dialect\";\n\n// kysely-pglite-dialect's driver.destroy() only nulls its reference to the\n// PGlite client — it never calls pglite.close(). Without close(), WAL is not\n// flushed and the data dir is left in a state that aborts the wasm on the\n// next open. This wrapper closes the dialect's PGlite as part of the\n// reactor's database.destroy() chain.\nexport class ClosablePGliteDialect extends PGliteDialect {\n readonly #pglite: PGlite;\n\n constructor(pglite: PGlite) {\n super(pglite);\n this.#pglite = pglite;\n }\n\n createDriver(): Driver {\n const driver = super.createDriver();\n const pglite = this.#pglite;\n const innerDestroy = driver.destroy.bind(driver);\n driver.destroy = async () => {\n await innerDestroy();\n if (!pglite.closed) {\n await pglite.close();\n }\n };\n return driver;\n }\n}\n","import type { ILogger } from \"document-model\";\nimport { promises as fs } from \"node:fs\";\nimport {\n CURRENT_PG_MAJOR,\n isSupportedMajor,\n loadPGliteModule,\n loadPgDump,\n readPgVersionFile,\n type SupportedPgMajor,\n} from \"./pglite-version.js\";\n\ntype PGliteCtor = new (\n dataDir: string,\n options?: Record<string, unknown>,\n) => {\n waitReady: Promise<void>;\n exec: (sql: string) => Promise<unknown>;\n close: () => Promise<void>;\n};\n\nfunction backupPath(dataDir: string, major: number): string {\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n return `${dataDir}.backup-pg${major}-${stamp}`;\n}\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await fs.stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction logRestoreFailure(\n dataDir: string,\n sql: string,\n err: unknown,\n logger: ILogger,\n): void {\n const errObj = err as {\n message?: string;\n position?: string | number;\n severity?: string;\n code?: string;\n detail?: string;\n where?: string;\n };\n const position =\n typeof errObj.position === \"string\"\n ? parseInt(errObj.position, 10)\n : typeof errObj.position === \"number\"\n ? errObj.position\n : NaN;\n\n logger.error(\n `[pglite-migration] Restore failed for ${dataDir}: code=${errObj.code ?? \"\"} severity=${errObj.severity ?? \"\"} message=${errObj.message ?? \"\"} sqlLength=${sql.length}`,\n );\n\n if (Number.isFinite(position) && position > 0) {\n const zeroBased = position - 1;\n const start = Math.max(0, zeroBased - 200);\n const end = Math.min(sql.length, zeroBased + 200);\n const before = sql.slice(start, zeroBased);\n const at = sql.slice(zeroBased, zeroBased + 1);\n const after = sql.slice(zeroBased + 1, end);\n logger.error(\n `[pglite-migration] SQL context around position ${position}:\\n${before}»${at}«${after}`,\n );\n } else {\n logger.error(\n `[pglite-migration] No position info. First 2000 chars of dump:\\n${sql.slice(0, 2000)}`,\n );\n }\n}\n\n/**\n * Migrate a filesystem PGLite data directory from a legacy PG major to the\n * current one. Renames the existing dir to a timestamped backup, dumps via the\n * matching legacy `pg_dump`, restores into a fresh current-version PGLite at\n * the original path. On failure, the original dir is restored from the backup.\n *\n * No-op when the dir is missing or already at the current major.\n */\nexport async function migratePgliteDir(\n dataDir: string,\n logger: ILogger,\n): Promise<void> {\n const major = await readPgVersionFile(dataDir);\n if (major === null) {\n logger.info(\n `[pglite-migration] No PG_VERSION at ${dataDir}; skipping migration`,\n );\n return;\n }\n if (major === CURRENT_PG_MAJOR) return;\n\n if (!isSupportedMajor(major)) {\n throw new Error(\n `Unsupported legacy PGlite data dir: PG_VERSION=${major} for ${dataDir}`,\n );\n }\n\n const backupDir = backupPath(dataDir, major);\n logger.info(\n `[pglite-migration] Migrating ${dataDir} from PG${major} to PG${CURRENT_PG_MAJOR}; backup: ${backupDir}`,\n );\n\n await fs.rename(dataDir, backupDir);\n\n let sql: string;\n try {\n const [legacyMod, pgDump] = await Promise.all([\n loadPGliteModule(major as SupportedPgMajor),\n loadPgDump(major as SupportedPgMajor),\n ]);\n const LegacyPGlite = (legacyMod as unknown as { PGlite: PGliteCtor })\n .PGlite;\n const pg = new LegacyPGlite(backupDir);\n try {\n await pg.waitReady;\n const file = await pgDump({ pg });\n sql = await file.text();\n } finally {\n await pg.close();\n }\n } catch (err) {\n await rollback(dataDir, backupDir, err, logger);\n throw err;\n }\n\n try {\n const currentMod = await loadPGliteModule(CURRENT_PG_MAJOR);\n const CurrentPGlite = (currentMod as unknown as { PGlite: PGliteCtor })\n .PGlite;\n const pg = new CurrentPGlite(dataDir, { relaxedDurability: false });\n try {\n await pg.waitReady;\n try {\n await pg.exec(\"SET standard_conforming_strings = off;\");\n } catch (gucErr) {\n logger.warn(\n `[pglite-migration] Could not force standard_conforming_strings=off: ${String(gucErr)}`,\n );\n }\n try {\n await pg.exec(sql);\n } catch (execErr) {\n logRestoreFailure(dataDir, sql, execErr, logger);\n throw execErr;\n }\n } finally {\n await pg.close();\n }\n } catch (err) {\n await rollback(dataDir, backupDir, err, logger);\n throw err;\n }\n\n logger.info(\n `[pglite-migration] Migration of ${dataDir} complete. Backup retained at ${backupDir}; remove it manually once you have verified the upgrade.`,\n );\n}\n\nasync function rollback(\n dataDir: string,\n backupDir: string,\n originalError: unknown,\n logger: ILogger,\n): Promise<void> {\n try {\n if (await pathExists(dataDir)) {\n await fs.rm(dataDir, { recursive: true, force: true });\n }\n if (await pathExists(backupDir)) {\n await fs.rename(backupDir, dataDir);\n }\n } catch (rollbackErr) {\n logger.error(\n `[pglite-migration] Migration AND rollback failed for ${dataDir}. Original error: ${String(originalError)}; rollback error: ${String(rollbackErr)}; backup may still exist at ${backupDir}.`,\n );\n return;\n }\n logger.error(\n `[pglite-migration] Migration failed for ${dataDir}; rolled back from ${backupDir}. Original error: ${String(originalError)}`,\n );\n}\n","import type { SignerConfig } from \"@powerhousedao/reactor\";\nimport {\n createSignatureVerifier,\n DEFAULT_RENOWN_URL,\n NodeKeyStorage,\n RenownBuilder,\n RenownCryptoBuilder,\n type IRenown,\n} from \"@renown/sdk/node\";\nimport { childLogger } from \"document-model\";\n\nconst logger = childLogger([\"switchboard\", \"renown\"]);\n\nexport interface RenownOptions {\n /** Path to the keypair file. Defaults to .ph/.keypair.json in cwd */\n keypairPath?: string;\n /** If true, won't generate a new keypair if none exists */\n requireExisting?: boolean;\n /** Base url of the Renown instance to use */\n baseUrl?: string;\n}\n\n/**\n * Initialize Renown for the Switchboard instance.\n * This allows Switchboard to authenticate with remote services\n * using the same identity established during `ph login`.\n */\nexport async function initRenown(\n options: RenownOptions = {},\n): Promise<IRenown | null> {\n const {\n keypairPath,\n requireExisting = false,\n baseUrl = DEFAULT_RENOWN_URL,\n } = options;\n\n const keyStorage = new NodeKeyStorage(keypairPath, {\n logger,\n });\n\n // Check if we have an existing keypair\n const existingKeyPair = await keyStorage.loadKeyPair();\n\n if (!existingKeyPair && requireExisting) {\n throw new Error(\n \"No existing keypair found and requireExisting is true. \" +\n 'Run \"ph login\" to create one.',\n );\n }\n\n if (!existingKeyPair) {\n logger.info(\"No existing keypair found. A new one will be generated.\");\n }\n\n const renownCrypto = await new RenownCryptoBuilder()\n .withKeyPairStorage(keyStorage)\n .build();\n\n const renown = await new RenownBuilder(\"switchboard\", {})\n .withCrypto(renownCrypto)\n .withBaseUrl(baseUrl)\n .build();\n\n logger.info(\"Switchboard identity initialized: @did\", renownCrypto.did);\n\n return renown;\n}\n\n/**\n * Get the signer config for the given renown instance.\n *\n * @param renown - The renown instance\n * @param requireSignature - If true, unsigned actions are rejected\n */\nexport function getRenownSignerConfig(\n renown: IRenown,\n requireSignature?: boolean,\n): SignerConfig {\n return {\n signer: renown.signer,\n verifier: createSignatureVerifier(requireSignature),\n };\n}\n","#!/usr/bin/env node\nimport type { PGlite } from \"@electric-sql/pglite\";\nimport { getConfig } from \"@powerhousedao/config/node\";\nimport { ReactorInstrumentation } from \"@powerhousedao/opentelemetry-instrumentation-reactor\";\nimport { AtomicNodeFs } from \"@powerhousedao/pglite-fs\";\nimport {\n ChannelScheme,\n EventBus,\n ReactorBuilder,\n ReactorClientBuilder,\n driveCollectionId,\n parseDriveUrl,\n type Database,\n} from \"@powerhousedao/reactor\";\nimport {\n HttpPackageLoader,\n ImportPackageLoader,\n PackageManagementService,\n PackagesSubgraph,\n getUniqueDocumentModels,\n initializeAndStartAPI,\n type IPackageLoader,\n} from \"@powerhousedao/reactor-api\";\nimport { httpsHooksPath } from \"@powerhousedao/reactor-api/https-hooks\";\nimport {\n VitePackageLoader,\n createViteLogger,\n startViteServer,\n} from \"@powerhousedao/reactor-api/vite\";\nimport { driveDocumentModelModule } from \"@powerhousedao/shared/document-drive\";\nimport type { DocumentModelModule } from \"@powerhousedao/shared/document-model\";\nimport { documentModels as vetraDocumentModels } from \"@powerhousedao/vetra\";\nimport { processorFactory as vetraProcessorFactory } from \"@powerhousedao/vetra/processors\";\nimport type { IRenown } from \"@renown/sdk/node\";\nimport * as Sentry from \"@sentry/node\";\nimport {\n childLogger,\n documentModelDocumentModelModule,\n setLogLevel,\n type ILogger,\n} from \"document-model\";\nimport dotenv from \"dotenv\";\nimport { Kysely, PostgresDialect } from \"kysely\";\nimport { promises as fs } from \"node:fs\";\nimport { register } from \"node:module\";\nimport net from \"node:net\";\nimport path from \"path\";\nimport { Pool } from \"pg\";\nimport { registerAttachmentRoutes } from \"./attachments/index.js\";\nimport { initFeatureFlags } from \"./feature-flags.js\";\nimport { ClosablePGliteDialect } from \"./pglite-dialect.js\";\nimport { migratePgliteDir } from \"./pglite-migration.js\";\nimport {\n CURRENT_PG_MAJOR,\n isSupportedMajor,\n loadPGliteModule,\n readPgVersionFile,\n type SupportedPgMajor,\n} from \"./pglite-version.js\";\nimport { getRenownSignerConfig, initRenown } from \"./renown.js\";\nimport type { StartServerOptions, SwitchboardReactor } from \"./types.js\";\nimport { addDefaultDrive, isPostgresUrl } from \"./utils.mjs\";\n\nconst defaultLogger = childLogger([\"switchboard\"]);\n\nconst LogLevel = (process.env.LOG_LEVEL as ILogger[\"level\"] | \"\") || \"info\";\nsetLogLevel(LogLevel);\n\ndotenv.config();\n\n// Feature flag constants\nconst DOCUMENT_MODEL_SUBGRAPHS_ENABLED = \"DOCUMENT_MODEL_SUBGRAPHS_ENABLED\";\nconst DOCUMENT_MODEL_SUBGRAPHS_ENABLED_DEFAULT = true;\nconst REQUIRE_SIGNATURES = \"REQUIRE_SIGNATURES\";\nconst REQUIRE_SIGNATURES_DEFAULT = false;\n\nconst DEFAULT_PORT = process.env.PORT ? Number(process.env.PORT) : 4001;\n\n// How many ports forward from the requested one we will try before giving up.\nconst PORT_FALLBACK_ATTEMPTS = 20;\n\n// AtomicNodeFs needs a flush interval to coalesce writes into a single disk write (only used locally)\nconst PGLITE_FLUSH_INTERVAL_MS = (() => {\n const raw = process.env.PGLITE_FLUSH_INTERVAL_MS;\n if (raw === undefined) return 100;\n const parsed = Number(raw);\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : 100;\n})();\n\n// When set, runs both reactor and read-model PGLite instances purely in-memory.\nconst PGLITE_IN_MEMORY = process.env.PH_PGLITE_IN_MEMORY === \"1\";\n\n/**\n * Attempt to bind a throwaway TCP server to the given port. Resolves true if\n * the port is free, false if the OS reports it in use. Any other error is\n * surfaced so we don't silently mask real issues (permissions, bad host, …).\n */\nexport function isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve, reject) => {\n const tester = net.createServer();\n tester.once(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\" || err.code === \"EACCES\") {\n resolve(false);\n } else {\n reject(err);\n }\n });\n tester.once(\"listening\", () => {\n tester.close(() => resolve(true));\n });\n // Bind on the unspecified IPv6 address so we detect collisions with both\n // IPv6 and IPv4 listeners (Node maps `::` to dual-stack on most systems).\n tester.listen({ port, host: \"::\" });\n });\n}\n\nasync function resolveServerPort(\n requested: number,\n strictPort: boolean,\n logger: ILogger,\n): Promise<number> {\n if (strictPort) return requested;\n for (let i = 0; i < PORT_FALLBACK_ATTEMPTS; i++) {\n const candidate = requested + i;\n if (await isPortAvailable(candidate)) {\n if (candidate !== requested) {\n logger.info(\n `Port ${requested} is in use. Falling back to port ${candidate}.`,\n );\n }\n return candidate;\n }\n }\n // Couldn't find a free port in the window; let the caller surface the\n // original EADDRINUSE when the real bind attempts runs.\n return requested;\n}\n\nasync function initServer(\n serverPort: number,\n options: StartServerOptions,\n renown: IRenown | null,\n) {\n const {\n dev,\n packages = [],\n remoteDrives = [],\n logger = defaultLogger,\n } = options;\n logger.level = LogLevel;\n const dbPath =\n options.dbPath ??\n process.env.DATABASE_URL ??\n process.env.PH_SWITCHBOARD_DATABASE_URL;\n\n // use postgres url for read model storage if available, otherwise use local PGlite path\n const readModelPath = dbPath || \".ph/read-storage\";\n\n const reactorDbUrl =\n process.env.PH_REACTOR_DATABASE_URL ??\n process.env.PH_SWITCHBOARD_DATABASE_URL;\n const reactorPgliteDir =\n !reactorDbUrl || !isPostgresUrl(reactorDbUrl)\n ? \"./.ph/reactor-storage\"\n : null;\n const readModelPgliteDir =\n !dbPath || !isPostgresUrl(dbPath) ? readModelPath : null;\n\n // PGLite version pre-flight: when PH_FORCE_PG_VERSION is set, wipe local\n // data dirs and re-initdb at the chosen version. Otherwise detect on-disk\n // PG_VERSION and either migrate (when --migrate-pglite is set) or warn and\n // fall through to the matching legacy PGLite at runtime.\n const pgliteDirs = [reactorPgliteDir, readModelPgliteDir].filter(\n (d): d is string => d !== null,\n );\n const detectedMajors = new Map<string, number>();\n\n if (options.forcePgVersion !== undefined && pgliteDirs.length > 0) {\n if (options.migratePglite) {\n logger.warn(\n \"PH_FORCE_PG_VERSION is set; ignoring --migrate-pglite/PH_MIGRATE_PGLITE because the data dirs will be wiped.\",\n );\n }\n logger.warn(\n `PH_FORCE_PG_VERSION=${options.forcePgVersion} set; wiping PGLite data dirs and re-initializing at PG${options.forcePgVersion}.`,\n );\n for (const dir of pgliteDirs) {\n await fs.rm(dir, { recursive: true, force: true });\n logger.info(`Wiped PGLite data dir ${dir}`);\n }\n } else if (options.forcePgVersion === undefined) {\n for (const dir of pgliteDirs) {\n const major = await readPgVersionFile(dir);\n if (major !== null) detectedMajors.set(dir, major);\n }\n\n if (options.migratePglite) {\n for (const [dir, major] of detectedMajors) {\n if (major === CURRENT_PG_MAJOR) continue;\n await migratePgliteDir(dir, logger);\n // refresh detected major after a successful migration\n const after = await readPgVersionFile(dir);\n if (after !== null) detectedMajors.set(dir, after);\n }\n } else {\n for (const [dir, major] of detectedMajors) {\n if (major === CURRENT_PG_MAJOR) continue;\n logger.warn(\n `PGLite data dir at ${dir} was created with PG${major} but Switchboard ships PG${CURRENT_PG_MAJOR}. Running on legacy PGLite. Re-start with --migrate-pglite (or PH_MIGRATE_PGLITE=true) to upgrade.`,\n );\n }\n }\n }\n\n function resolvePgliteMajorForDir(dir: string): SupportedPgMajor {\n if (options.forcePgVersion !== undefined) return options.forcePgVersion;\n const detected = detectedMajors.get(dir);\n if (detected === undefined) return CURRENT_PG_MAJOR;\n if (!isSupportedMajor(detected)) {\n throw new Error(\n `Unsupported PGLite data dir at ${dir}: PG_VERSION=${detected}`,\n );\n }\n return detected;\n }\n\n const reactorPgliteMajor = reactorPgliteDir\n ? resolvePgliteMajorForDir(reactorPgliteDir)\n : null;\n const readModelPgliteMajor = readModelPgliteDir\n ? resolvePgliteMajorForDir(readModelPgliteDir)\n : null;\n\n // The reactor-api owns its own PGlite/HTTP/WS resources but has no shutdown\n // path of its own; we register `api.dispose` as a reactor shutdown hook so\n // those resources drain inside the reactor's SIGINT chain. The reference\n // is forward — `initializeClient` runs (and registers the hook) before\n // `initializeAndStartAPI` returns the api — so the closure reads `apiRef`\n // at hook-fire time, not at registration time.\n const apiRef: { current: { dispose: () => Promise<void> } | undefined } = {\n current: undefined,\n };\n\n // HTTP registry package loading\n const configPath =\n options.configFile ?? path.join(process.cwd(), \"powerhouse.config.json\");\n const config = getConfig(configPath);\n const registryUrl = process.env.PH_REGISTRY_URL ?? config.packageRegistryUrl;\n const registryPackages = process.env.PH_REGISTRY_PACKAGES;\n const dynamicModelLoading =\n options.dynamicModelLoading ?? process.env.DYNAMIC_MODEL_LOADING === \"true\";\n let httpLoader: HttpPackageLoader | undefined;\n\n if (registryUrl) {\n // Register HTTP/HTTPS module loader hooks for dynamic package imports\n register(httpsHooksPath, import.meta.url);\n httpLoader = new HttpPackageLoader({ registryUrl });\n registryPackages?.split(\",\").forEach((p) => {\n const name = p.trim();\n if (!packages.includes(name)) {\n packages.push(name);\n }\n });\n }\n\n const reactorLogger = logger.child([\"reactor\"]);\n const initializeClient = async (documentModels: DocumentModelModule[]) => {\n const eventBus = new EventBus();\n const builder = new ReactorBuilder()\n .withEventBus(eventBus)\n .withDocumentModels(\n getUniqueDocumentModels([\n documentModelDocumentModelModule,\n driveDocumentModelModule,\n ...vetraDocumentModels,\n ...documentModels,\n ]),\n )\n .withChannelScheme(ChannelScheme.SWITCHBOARD)\n .withSignalHandlers()\n .withLogger(reactorLogger);\n\n const maxSkipThreshold = parseInt(process.env.MAX_SKIP_THRESHOLD ?? \"\", 10);\n if (!isNaN(maxSkipThreshold) && maxSkipThreshold > 0) {\n builder.withExecutorConfig({ maxSkipThreshold });\n logger.info(`Reactor maxSkipThreshold set to ${maxSkipThreshold}`);\n }\n\n if (reactorDbUrl && isPostgresUrl(reactorDbUrl)) {\n const connectionString = reactorDbUrl.includes(\"?\")\n ? reactorDbUrl\n : `${reactorDbUrl}?sslmode=disable`;\n const pool = new Pool({ connectionString });\n const kysely = new Kysely<Database>({\n dialect: new PostgresDialect({ pool }),\n });\n builder.withKysely(kysely);\n logger.info(\"Using PostgreSQL for reactor storage\");\n } else {\n if (!reactorPgliteDir || reactorPgliteMajor === null) {\n throw new Error(\"Reactor PGLite directory not resolved\");\n }\n const { PGlite } = await loadPGliteModule(reactorPgliteMajor);\n const pglite = PGLITE_IN_MEMORY\n ? new PGlite()\n : new PGlite({\n fs: new AtomicNodeFs(reactorPgliteDir, {\n logger,\n flushIntervalMs: PGLITE_FLUSH_INTERVAL_MS,\n }),\n });\n const kysely = new Kysely<Database>({\n dialect: new ClosablePGliteDialect(pglite),\n });\n builder.withKysely(kysely);\n logger.info(\n PGLITE_IN_MEMORY\n ? `Using in-memory PGlite (PG${reactorPgliteMajor}) for reactor storage [PH_PGLITE_IN_MEMORY=1]`\n : `Using PGlite (PG${reactorPgliteMajor}) for reactor storage at ${reactorPgliteDir}`,\n );\n }\n\n builder.withShutdownHook(async () => {\n if (apiRef.current) await apiRef.current.dispose();\n });\n\n if (httpLoader && dynamicModelLoading) {\n builder.withDocumentModelLoader(httpLoader.documentModelLoader);\n }\n\n const clientBuilder = new ReactorClientBuilder().withReactorBuilder(\n builder,\n );\n\n if (renown) {\n const signerConfig = getRenownSignerConfig(\n renown,\n options.identity?.requireSignatures,\n );\n clientBuilder.withSigner(signerConfig);\n }\n\n const module = await clientBuilder.buildModule();\n\n if (module.reactorModule) {\n const instrumentation = new ReactorInstrumentation(module.reactorModule);\n instrumentation.start();\n reactorLogger.info(\"Reactor metrics instrumentation started\");\n }\n\n return module;\n };\n\n let defaultDriveUrl: undefined | string = undefined;\n\n // TODO get path from powerhouse config\n // start vite server if dev mode is enabled\n const basePath = process.cwd();\n const viteLogger = createViteLogger(logger);\n const vite = dev\n ? await startViteServer(process.cwd(), viteLogger)\n : undefined;\n\n // get paths to local document models\n if (!options.disableLocalPackages) {\n packages.push(basePath);\n }\n\n // create loaders\n const packageLoaders: IPackageLoader[] = [];\n if (vite) {\n packageLoaders.push(VitePackageLoader.build(vite));\n } else {\n packageLoaders.push(new ImportPackageLoader());\n }\n if (httpLoader) {\n packageLoaders.push(httpLoader);\n registryPackages?.split(\",\").forEach((p) => {\n const name = p.trim();\n if (!packages.includes(name)) {\n packages.push(name);\n }\n });\n }\n\n const apiLogger = logger.child([\"reactor-api\"]);\n // When the read-model store is on disk, hand reactor-api a factory that\n // constructs the matching PGLite (current or legacy) for the detected\n // PG_VERSION. reactor-api calls the factory synchronously, so the legacy\n // module is preloaded above.\n let pgliteFactory:\n | ((connectionString: string | undefined) => PGlite)\n | undefined;\n if (readModelPgliteDir && readModelPgliteMajor !== null) {\n const { PGlite: ReadModelPGlite } =\n await loadPGliteModule(readModelPgliteMajor);\n pgliteFactory = PGLITE_IN_MEMORY\n ? () => new ReadModelPGlite()\n : (connectionString) =>\n new ReadModelPGlite({\n fs: new AtomicNodeFs(\n connectionString ?? (readModelPgliteDir as string),\n { logger, flushIntervalMs: PGLITE_FLUSH_INTERVAL_MS },\n ),\n });\n }\n\n const api = await initializeAndStartAPI(\n initializeClient,\n {\n port: serverPort,\n dbPath: readModelPath,\n pgliteFactory,\n https: options.https,\n packageLoaders: packageLoaders.length > 0 ? packageLoaders : undefined,\n packages: packages,\n processorConfig: options.processorConfig,\n processors: {\n \"@powerhousedao/vetra\": [vetraProcessorFactory],\n },\n configFile:\n options.configFile ??\n path.join(process.cwd(), \"powerhouse.config.json\"),\n mcp: options.mcp ?? true,\n logger: apiLogger,\n enableDocumentModelSubgraphs: options.enableDocumentModelSubgraphs,\n },\n \"switchboard\",\n );\n apiRef.current = api;\n\n registerAttachmentRoutes(api);\n\n if (process.env.SENTRY_DSN) {\n // Register Sentry error handler after all routes are established.\n // The adapter calls the framework-specific Sentry setup internally.\n api.httpAdapter.setupSentryErrorHandler(Sentry);\n }\n\n const { client, graphqlManager, documentModelRegistry } = api;\n\n // Wire up dynamic package management if HTTP loader is configured\n if (httpLoader) {\n const packageManagementService = new PackageManagementService({\n defaultRegistryUrl: registryUrl,\n httpLoader,\n documentModelRegistry,\n });\n\n packageManagementService.setOnModelsChanged(() => {\n graphqlManager.regenerateDocumentModelSubgraphs().catch(logger.error);\n });\n\n const packagesSubgraph = new PackagesSubgraph({\n relationalDb: undefined as never,\n analyticsStore: undefined as never,\n reactorClient: client,\n graphqlManager,\n syncManager: api.syncManager,\n path: graphqlManager.getBasePath(),\n packageManagementService,\n });\n\n void graphqlManager\n .registerSubgraphInstance(packagesSubgraph, \"graphql\", false)\n .then(() => graphqlManager.updateRouter())\n .catch((error: unknown) => {\n logger.error(\"Failed to register packages subgraph: @error\", error);\n });\n }\n\n // Create default drive if provided\n if (options.drive) {\n if (!renown) {\n throw new Error(\"Cannot create default drive without Renown identity\");\n }\n\n defaultDriveUrl = await addDefaultDrive(client, options.drive, serverPort);\n }\n\n // add vite middleware after express app is initialized if applicable\n if (vite) {\n api.httpAdapter.mountRawMiddleware(vite.middlewares);\n }\n\n // Connect to remote drives AFTER packages are loaded\n if (remoteDrives.length > 0) {\n for (const remoteDriveUrl of remoteDrives) {\n let driveId: string | undefined;\n\n try {\n const { syncManager } = api;\n const parsed = parseDriveUrl(remoteDriveUrl);\n driveId = parsed.driveId;\n const remoteName = `remote-drive-${driveId}-${crypto.randomUUID()}`;\n await syncManager.add(remoteName, driveCollectionId(\"main\", driveId), {\n type: \"gql\",\n parameters: { url: parsed.graphqlEndpoint },\n });\n logger.debug(\"Remote drive @remoteDriveUrl synced\", remoteDriveUrl);\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes(\"already exists\")\n ) {\n logger.debug(\n \"Remote drive already added: @remoteDriveUrl\",\n remoteDriveUrl,\n );\n driveId = remoteDriveUrl.split(\"/\").pop();\n } else {\n logger.error(\n \"Failed to connect to remote drive @remoteDriveUrl: @error\",\n remoteDriveUrl,\n error,\n );\n }\n } finally {\n // Construct local URL once in finally block\n if (!defaultDriveUrl && driveId) {\n const protocol = options.https ? \"https\" : \"http\";\n defaultDriveUrl = `${protocol}://localhost:${serverPort}/d/${driveId}`;\n }\n }\n }\n }\n\n return {\n defaultDriveUrl,\n api,\n reactor: client,\n renown,\n port: serverPort,\n };\n}\n\nexport const startSwitchboard = async (\n options: StartServerOptions = {},\n): Promise<SwitchboardReactor> => {\n const requestedPort = options.port ?? DEFAULT_PORT;\n const logger = options.logger ?? defaultLogger;\n const serverPort = await resolveServerPort(\n requestedPort,\n options.strictPort ?? false,\n logger,\n );\n\n // Initialize feature flags\n const featureFlags = await initFeatureFlags();\n\n const enableDocumentModelSubgraphs = await featureFlags.getBooleanValue(\n DOCUMENT_MODEL_SUBGRAPHS_ENABLED,\n options.enableDocumentModelSubgraphs ??\n DOCUMENT_MODEL_SUBGRAPHS_ENABLED_DEFAULT,\n );\n\n options.enableDocumentModelSubgraphs = enableDocumentModelSubgraphs;\n\n const requireSignatures =\n options.identity?.requireSignatures ??\n (await featureFlags.getBooleanValue(\n REQUIRE_SIGNATURES,\n REQUIRE_SIGNATURES_DEFAULT,\n ));\n options.identity = { ...options.identity, requireSignatures };\n\n logger.info(\n \"Feature flags: @flags\",\n JSON.stringify(\n {\n DOCUMENT_MODEL_SUBGRAPHS_ENABLED: enableDocumentModelSubgraphs,\n REQUIRE_SIGNATURES: requireSignatures,\n },\n null,\n 2,\n ),\n );\n\n // Initialize Renown if identity options are provided or keypair exists\n let renown: IRenown | null = null;\n try {\n renown = await initRenown(options.identity);\n } catch (e) {\n logger.warn(\"Failed to initialize ConnectCrypto: @error\", e);\n if (options.identity?.requireExisting) {\n throw new Error(\n 'Identity required but failed to initialize. Run \"ph login\" first.',\n { cause: e },\n );\n }\n }\n\n try {\n return await initServer(serverPort, options, renown);\n } catch (e) {\n Sentry.captureException(e);\n logger.error(\"App crashed: @error\", e);\n throw e;\n }\n};\n\nexport * from \"./types.js\";\n\nif (import.meta.main) {\n await startSwitchboard();\n}\n"],"names":["fs","logger","#pglite","fs","fs","path","vetraDocumentModels","documentModels","vetraProcessorFactory"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKA,MAAa,sBAAsB,CAAC,IAAI,GAAG;AAK3C,eAAsB,kBACpB,SACwB;AACxB,KAAI;EACF,MAAM,MAAM,MAAMA,SAAG,SAAS,KAAK,KAAK,SAAS,aAAa,EAAE,OAAO;EACvE,MAAM,QAAQ,SAAS,IAAI,MAAM,EAAE,GAAG;AACtC,SAAO,OAAO,SAAS,MAAM,GAAG,QAAQ;SAClC;AACN,SAAO;;;AAIX,SAAgB,iBAAiB,OAA0C;AACzE,QAAQ,oBAA0C,SAAS,MAAM;;;;;;;;AASnE,SAAgB,oBACd,KACyB;AACzB,KAAI,QAAQ,KAAA,KAAa,IAAI,MAAM,KAAK,GAAI,QAAO;CACnD,MAAM,SAAS,OAAO,IAAI;AAC1B,KAAI,OAAO,UAAU,OAAO,IAAI,iBAAiB,OAAO,CAAE,QAAO;AACjE,OAAM,IAAI,MACR,uCAAuC,oBAAoB,KAAK,KAAK,CAAC,SAAS,IAAI,GACpF;;AAGH,eAAsB,iBACpB,OAC8B;AAC9B,KAAI,UAAU,GACZ,QAAQ,MAAM,OAAO;AAEvB,QAAO,OAAO;;AAOhB,eAAsB,WAAW,OAA4C;AAC3E,KAAI,UAAU,GAIZ,SAHa,MAAM,OAAO,mCAGf;AAKb,SAHa,MAAM,OAAO,uCAGf;;;;;;;;ACtDb,SAAgB,YACd,aACA,SACa;AACb,KAAI,CAAC,YAAa,QAAO;AAEzB,QAAO,OAAO,KAAK,QAAQ;EACzB,IAAI;AACJ,MAAI;AACF,YAAS,MAAM,YAAY,aAAa,IAAI,QAAQ,cAAc;UAC5D;AACN,OAAI,aAAa;AACjB,OAAI,UAAU,gBAAgB,mBAAmB;AACjD,OAAI,IAAI,KAAK,UAAU,EAAE,OAAO,iCAAiC,CAAC,CAAC;AACnE;;AAGF,MAAI,kBAAkB,UAAU;GAC9B,MAAM,OAAO,MAAM,OAAO,MAAM;AAChC,OAAI,aAAa,OAAO;GACxB,MAAM,cAAc,OAAO,QAAQ,IAAI,eAAe;AACtD,OAAI,YAAa,KAAI,UAAU,gBAAgB,YAAY;AAC3D,OAAI,IAAI,KAAK;AACb;;AAGF,MAAI,OAAO,gBAAgB,CAAC,OAAO,MAAM;AACvC,OAAI,aAAa;AACjB,OAAI,UAAU,gBAAgB,mBAAmB;AACjD,OAAI,IAAI,KAAK,UAAU,EAAE,OAAO,2BAA2B,CAAC,CAAC;AAC7D;;AAGF,QAAM,QAAQ,KAAK,IAAI;;;;;;;;;;;AClC3B,SAAgB,4BACd,KACA,QACA,MACA,SACM;AACN,KAAI,YAAY,eACd,QACA,MACA,YAAY,IAAI,aAAa,QAAQ,CACtC;;;;ACRH,MAAMC,WAAS,YAAY,CAAC,eAAe,cAAc,CAAC;AAK1D,MAAM,eAAe;AAErB,MAAM,gBAAgB;AAEtB,MAAM,oBACJ;AACF,MAAM,mBAAmB;AACzB,MAAM,mBAAmB;AAEzB,SAAS,SAAS,KAAqB,QAAgB,MAAqB;AAC1E,KAAI,aAAa;AACjB,KAAI,UAAU,gBAAgB,mBAAmB;AACjD,KAAI,IAAI,KAAK,UAAU,KAAK,CAAC;;AAG/B,SAAS,UAAU,KAAqB,QAAgB,SAAuB;AAC7E,UAAS,KAAK,QAAQ,EAAE,OAAO,SAAS,CAAC;;AAG3C,SAAS,eAAe,KAAsB;AAC5C,KAAI,eAAe,mBAAoB,QAAO;AAC9C,KAAI,eAAe,oBAAqB,QAAO;AAC/C,KAAI,eAAe,qBAAsB,QAAO;AAChD,QAAO;;AAGT,SAAS,uBAAuB,KAAqB,KAAoB;CACvE,MAAM,SAAS,eAAe,IAAI;AAClC,KAAI,UAAU,KAAK;AACjB,WAAO,MAAM,kCAAkC,IAAI;AACnD,YAAU,KAAK,QAAQ,iBAAiB;AACxC;;AAEF,WAAU,KAAK,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CAAC;;AAG1E,eAAe,aACb,KACA,MACkB;AAIlB,KAAI,SAAS,KAAA,KAAa,SAAS,QAAQ,OAAO,SAAS,SACzD,QAAO;CAET,MAAM,SAAmB,EAAE;AAC3B,YAAW,MAAM,SAAS,IACxB,QAAO,KAAK,MAAgB;AAE9B,KAAI,OAAO,WAAW,EAAG,QAAO,KAAA;CAChC,MAAM,OAAO,OAAO,OAAO,OAAO,CAAC,SAAS,OAAO;AACnD,KAAI,KAAK,WAAW,EAAG,QAAO,KAAA;AAC9B,QAAO,KAAK,MAAM,KAAK;;AAGzB,SAAgB,oBACd,OACiC;AACjC,KAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO;CACxD,MAAM,MAAM;AACZ,KACE,OAAO,IAAI,aAAa,YACxB,IAAI,SAAS,WAAW,KACxB,IAAI,SAAS,SAAS,oBACtB,CAAC,kBAAkB,KAAK,IAAI,SAAS,CAErC,QAAO;AAET,KACE,OAAO,IAAI,aAAa,YACxB,IAAI,SAAS,WAAW,KACxB,IAAI,SAAS,SAAS,oBACtB,cAAc,KAAK,IAAI,SAAS,CAEhC,QAAO;CAET,IAAI,YAA2B;AAC/B,KAAI,OAAO,IAAI,cAAc,UAAU;AACrC,MAAI,IAAI,UAAU,WAAW,KAAK,QAAQ,KAAK,IAAI,UAAU,CAAE,QAAO;AACtE,cAAY,IAAI;YACP,IAAI,cAAc,KAAA,KAAa,IAAI,cAAc,KAC1D,QAAO;AAET,QAAO;EACL,UAAU,IAAI;EACd,UAAU,IAAI;EACd;EACD;;AAGH,SAAgB,cAAc,MAAsB;AAElD,QAAO,IAAI,KAAK,QAAQ,UAAU,OAAO,CAAC;;AAG5C,SAAgB,wBAAwB,UAA0B;CAIhE,MAAM,QAAQ,SAAS,QAAQ,mCAAmC,IAAI;CAGtE,MAAM,UAAU,mBAAmB,SAAS,CAAC,QAC3C,aACC,MAAM,IAAI,EAAE,WAAW,EAAE,CAAC,SAAS,GAAG,CAAC,aAAa,GACtD;AACD,QAAO,wBAAwB,cAAc,MAAM,CAAC,qBAAqB;;AAG3E,SAAgB,mBAAmB,aAAoC;AACrE,QAAO,OACL,KACA,KACA,SACkB;EAClB,IAAI;AACJ,MAAI;AACF,YAAS,MAAM,aAAa,KAAK,KAAK;UAChC;AACN,aAAU,KAAK,KAAK,oBAAoB;AACxC;;EAEF,MAAM,OAAO,oBAAoB,OAAO;AACxC,MAAI,CAAC,MAAM;AACT,aACE,KACA,KACA,qIACD;AACD;;AAEF,MAAI;AAEF,YAAS,KAAK,KAAK,EAAE,gBADN,MAAM,YAAY,QAAQ,QAAQ,KAAK,EACX,eAAe,CAAC;WACpD,KAAK;AACZ,0BAAuB,KAAK,IAAI;;;;AAKtC,SAAgB,kBAAkB,aAAoC;AACpE,QAAO,OAAO,KAAsB,QAAuC;EACzE,MAAM,gBAAgB,aAAa,KAAK,gBAAgB;AACxD,MAAI,CAAC,eAAe;AAClB,aAAU,KAAK,KAAK,wBAAwB;AAC5C;;EAGF,IAAI;AACJ,MAAI;AACF,iBAAc,MAAM,YAAY,aAAa,IAAI,cAAc;WACxD,KAAK;AACZ,0BAAuB,KAAK,IAAI;AAChC;;EAGF,MAAM,SAAS,YAAY,cAAc,aACvC,YAAY,eACZ;GACE,UAAU,YAAY;GACtB,UAAU,YAAY;GACtB,WAAW,YAAY;GACxB,CACF;EAED,MAAM,YAAY,SAAS,MACzB,IACD;AAED,MAAI;AAEF,YAAS,KAAK,KADC,MAAM,OAAO,KAAK,UAAU,CACjB;WACnB,KAAK;AACZ,0BAAuB,KAAK,IAAI;;;;AAKtC,SAAgB,oBAAoB,aAAoC;AACtE,QAAO,OAAO,KAAsB,QAAuC;EACzE,MAAM,OAAO,aAAa,KAAK,OAAO;AACtC,MAAI,CAAC,QAAQ,CAAC,aAAa,KAAK,KAAK,EAAE;AACrC,aAAU,KAAK,KAAK,0BAA0B;AAC9C;;EAGF,MAAM,aAAa,IAAI,iBAAiB;AACxC,MAAI,KAAK,eAAe,WAAW,OAAO,CAAC;EAE3C,MAAM,gBAAgB,KAAK,aAAa;EACxC,IAAI;AACJ,MAAI;AACF,cAAW,MAAM,YAAY,MAAM,IAAI,eAAe,WAAW,OAAO;WACjE,KAAK;AACZ,0BAAuB,KAAK,IAAI;AAChC;;EAGF,MAAM,EAAE,QAAQ,SAAS;AACzB,MAAI,aAAa;AACjB,MAAI,UAAU,gBAAgB,OAAO,SAAS;AAC9C,MAAI,UAAU,kBAAkB,OAAO,OAAO,UAAU,CAAC;AACzD,MAAI,UACF,uBACA,wBAAwB,OAAO,SAAS,CACzC;AACD,MAAI,UAAU,uBAAuB,oBAAoB,OAAO,CAAC;AAEjE,WAAS,QAAQ,KAAkD,CAAC,KAClE,IACD;;;AAIL,SAAS,oBAAoB,QAOlB;AACT,QAAO,KAAK,UAAU;EACpB,UAAU,OAAO;EACjB,UAAU,OAAO;EACjB,WAAW,OAAO;EAClB,WAAW,OAAO;EAClB,cAAc,OAAO;EACrB,mBAAmB,OAAO;EAC3B,CAAC;;AAGJ,SAAgB,gBAAgB,aAAoC;AAClE,QAAO,OAAO,KAAsB,QAAuC;EACzE,MAAM,OAAO,aAAa,KAAK,OAAO;AACtC,MAAI,CAAC,QAAQ,CAAC,aAAa,KAAK,KAAK,EAAE;AACrC,aAAU,KAAK,KAAK,0BAA0B;AAC9C;;EAGF,MAAM,gBAAgB,KAAK,aAAa;EACxC,IAAI;AACJ,MAAI;AACF,YAAS,MAAM,YAAY,MAAM,KAAK,cAAc;WAC7C,KAAK;AACZ,0BAAuB,KAAK,IAAI;AAChC;;AAGF,MAAI,aAAa;AACjB,MAAI,UAAU,gBAAgB,OAAO,SAAS;AAC9C,MAAI,UAAU,kBAAkB,OAAO,OAAO,UAAU,CAAC;AACzD,MAAI,UACF,uBACA,wBAAwB,OAAO,SAAS,CACzC;AACD,MAAI,UAAU,uBAAuB,oBAAoB,OAAO,CAAC;AACjE,MAAI,KAAK;;;AAIb,SAAgB,0BAA0B,aAAoC;AAC5E,QAAO,OAAO,KAAsB,QAAuC;EACzE,MAAM,gBAAgB,aAAa,KAAK,gBAAgB;AACxD,MAAI,CAAC,eAAe;AAClB,aAAU,KAAK,KAAK,wBAAwB;AAC5C;;AAEF,MAAI;AAEF,YAAS,KAAK,KADM,MAAM,YAAY,aAAa,IAAI,cAAc,CACtC;WACxB,KAAK;AACZ,0BAAuB,KAAK,IAAI;;;;AAKtC,SAAgB,6BACd,aACA;AACA,QAAO,OAAO,KAAsB,QAAuC;EACzE,MAAM,gBAAgB,aAAa,KAAK,gBAAgB;AACxD,MAAI,CAAC,eAAe;AAClB,aAAU,KAAK,KAAK,wBAAwB;AAC5C;;AAEF,MAAI;AACF,SAAM,YAAY,aAAa,OAAO,cAAc;AACpD,OAAI,aAAa;AACjB,OAAI,KAAK;WACF,KAAK;AACZ,0BAAuB,KAAK,IAAI;;;;AAKtC,SAAS,aAAa,KAAsB,MAAkC;AAM5E,QAJE,IAGA,SACqB;;;;ACtTzB,SAAgB,yBAAyB,KAAgB;CACvD,MAAM,EAAE,gBAAgB;AAExB,6BACE,KACA,QACA,6BACA,mBAAmB,YAAY,CAChC;AAED,6BACE,KACA,OACA,4CACA,0BAA0B,YAAY,CACvC;AAED,6BACE,KACA,UACA,4CACA,6BAA6B,YAAY,CAC1C;AAED,6BACE,KACA,OACA,4CACA,kBAAkB,YAAY,CAC/B;AAED,6BACE,KACA,QACA,sBACA,gBAAgB,YAAY,CAC7B;AAED,6BACE,KACA,OACA,sBACA,oBAAoB,YAAY,CACjC;;;;ACnDH,eAAsB,mBAAmB;CAEvC,MAAM,WAAW,IAAI,gBAAgB;AAErC,OAAM,YAAY,mBAAmB,SAAS;AAE9C,QAAO,YAAY,WAAW;;;;ACAhC,IAAa,wBAAb,cAA2C,cAAc;CACvD;CAEA,YAAY,QAAgB;AAC1B,QAAM,OAAO;AACb,QAAA,SAAe;;CAGjB,eAAuB;EACrB,MAAM,SAAS,MAAM,cAAc;EACnC,MAAM,SAAS,MAAA;EACf,MAAM,eAAe,OAAO,QAAQ,KAAK,OAAO;AAChD,SAAO,UAAU,YAAY;AAC3B,SAAM,cAAc;AACpB,OAAI,CAAC,OAAO,OACV,OAAM,OAAO,OAAO;;AAGxB,SAAO;;;;;ACPX,SAAS,WAAW,SAAiB,OAAuB;AAE1D,QAAO,GAAG,QAAQ,YAAY,MAAM,oBADtB,IAAI,MAAM,EAAC,aAAa,CAAC,QAAQ,SAAS,IAAI;;AAI9D,eAAe,WAAW,GAA6B;AACrD,KAAI;AACF,QAAME,SAAG,KAAK,EAAE;AAChB,SAAO;SACD;AACN,SAAO;;;AAIX,SAAS,kBACP,SACA,KACA,KACA,QACM;CACN,MAAM,SAAS;CAQf,MAAM,WACJ,OAAO,OAAO,aAAa,WACvB,SAAS,OAAO,UAAU,GAAG,GAC7B,OAAO,OAAO,aAAa,WACzB,OAAO,WACP;AAER,QAAO,MACL,yCAAyC,QAAQ,SAAS,OAAO,QAAQ,GAAG,YAAY,OAAO,YAAY,GAAG,WAAW,OAAO,WAAW,GAAG,aAAa,IAAI,SAChK;AAED,KAAI,OAAO,SAAS,SAAS,IAAI,WAAW,GAAG;EAC7C,MAAM,YAAY,WAAW;EAC7B,MAAM,QAAQ,KAAK,IAAI,GAAG,YAAY,IAAI;EAC1C,MAAM,MAAM,KAAK,IAAI,IAAI,QAAQ,YAAY,IAAI;EACjD,MAAM,SAAS,IAAI,MAAM,OAAO,UAAU;EAC1C,MAAM,KAAK,IAAI,MAAM,WAAW,YAAY,EAAE;EAC9C,MAAM,QAAQ,IAAI,MAAM,YAAY,GAAG,IAAI;AAC3C,SAAO,MACL,kDAAkD,SAAS,KAAK,OAAO,GAAG,GAAG,GAAG,QACjF;OAED,QAAO,MACL,mEAAmE,IAAI,MAAM,GAAG,IAAK,GACtF;;;;;;;;;;AAYL,eAAsB,iBACpB,SACA,QACe;CACf,MAAM,QAAQ,MAAM,kBAAkB,QAAQ;AAC9C,KAAI,UAAU,MAAM;AAClB,SAAO,KACL,uCAAuC,QAAQ,sBAChD;AACD;;AAEF,KAAI,UAAA,GAA4B;AAEhC,KAAI,CAAC,iBAAiB,MAAM,CAC1B,OAAM,IAAI,MACR,kDAAkD,MAAM,OAAO,UAChE;CAGH,MAAM,YAAY,WAAW,SAAS,MAAM;AAC5C,QAAO,KACL,gCAAgC,QAAQ,UAAU,MAAM,oBAAqC,YAC9F;AAED,OAAMA,SAAG,OAAO,SAAS,UAAU;CAEnC,IAAI;AACJ,KAAI;EACF,MAAM,CAAC,WAAW,UAAU,MAAM,QAAQ,IAAI,CAC5C,iBAAiB,MAA0B,EAC3C,WAAW,MAA0B,CACtC,CAAC;EACF,MAAM,eAAgB,UACnB;EACH,MAAM,KAAK,IAAI,aAAa,UAAU;AACtC,MAAI;AACF,SAAM,GAAG;AAET,SAAM,OADO,MAAM,OAAO,EAAE,IAAI,CAAC,EAChB,MAAM;YACf;AACR,SAAM,GAAG,OAAO;;UAEX,KAAK;AACZ,QAAM,SAAS,SAAS,WAAW,KAAK,OAAO;AAC/C,QAAM;;AAGR,KAAI;EAEF,MAAM,iBADa,MAAM,iBAAA,GAAkC,EAExD;EACH,MAAM,KAAK,IAAI,cAAc,SAAS,EAAE,mBAAmB,OAAO,CAAC;AACnE,MAAI;AACF,SAAM,GAAG;AACT,OAAI;AACF,UAAM,GAAG,KAAK,yCAAyC;YAChD,QAAQ;AACf,WAAO,KACL,uEAAuE,OAAO,OAAO,GACtF;;AAEH,OAAI;AACF,UAAM,GAAG,KAAK,IAAI;YACX,SAAS;AAChB,sBAAkB,SAAS,KAAK,SAAS,OAAO;AAChD,UAAM;;YAEA;AACR,SAAM,GAAG,OAAO;;UAEX,KAAK;AACZ,QAAM,SAAS,SAAS,WAAW,KAAK,OAAO;AAC/C,QAAM;;AAGR,QAAO,KACL,mCAAmC,QAAQ,gCAAgC,UAAU,0DACtF;;AAGH,eAAe,SACb,SACA,WACA,eACA,QACe;AACf,KAAI;AACF,MAAI,MAAM,WAAW,QAAQ,CAC3B,OAAMA,SAAG,GAAG,SAAS;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;AAExD,MAAI,MAAM,WAAW,UAAU,CAC7B,OAAMA,SAAG,OAAO,WAAW,QAAQ;UAE9B,aAAa;AACpB,SAAO,MACL,wDAAwD,QAAQ,oBAAoB,OAAO,cAAc,CAAC,oBAAoB,OAAO,YAAY,CAAC,8BAA8B,UAAU,GAC3L;AACD;;AAEF,QAAO,MACL,2CAA2C,QAAQ,qBAAqB,UAAU,oBAAoB,OAAO,cAAc,GAC5H;;;;AC9KH,MAAM,SAAS,YAAY,CAAC,eAAe,SAAS,CAAC;;;;;;AAgBrD,eAAsB,WACpB,UAAyB,EAAE,EACF;CACzB,MAAM,EACJ,aACA,kBAAkB,OAClB,UAAU,uBACR;CAEJ,MAAM,aAAa,IAAI,eAAe,aAAa,EACjD,QACD,CAAC;CAGF,MAAM,kBAAkB,MAAM,WAAW,aAAa;AAEtD,KAAI,CAAC,mBAAmB,gBACtB,OAAM,IAAI,MACR,yFAED;AAGH,KAAI,CAAC,gBACH,QAAO,KAAK,0DAA0D;CAGxE,MAAM,eAAe,MAAM,IAAI,qBAAqB,CACjD,mBAAmB,WAAW,CAC9B,OAAO;CAEV,MAAM,SAAS,MAAM,IAAI,cAAc,eAAe,EAAE,CAAC,CACtD,WAAW,aAAa,CACxB,YAAY,QAAQ,CACpB,OAAO;AAEV,QAAO,KAAK,0CAA0C,aAAa,IAAI;AAEvE,QAAO;;;;;;;;AAST,SAAgB,sBACd,QACA,kBACc;AACd,QAAO;EACL,QAAQ,OAAO;EACf,UAAU,wBAAwB,iBAAiB;EACpD;;;;AClBH,MAAM,gBAAgB,YAAY,CAAC,cAAc,CAAC;AAElD,MAAM,WAAY,QAAQ,IAAI,aAAuC;AACrE,YAAY,SAAS;AAErB,OAAO,QAAQ;AAGf,MAAM,mCAAmC;AACzC,MAAM,2CAA2C;AACjD,MAAM,qBAAqB;AAC3B,MAAM,6BAA6B;AAEnC,MAAM,eAAe,QAAQ,IAAI,OAAO,OAAO,QAAQ,IAAI,KAAK,GAAG;AAGnE,MAAM,yBAAyB;AAG/B,MAAM,kCAAkC;CACtC,MAAM,MAAM,QAAQ,IAAI;AACxB,KAAI,QAAQ,KAAA,EAAW,QAAO;CAC9B,MAAM,SAAS,OAAO,IAAI;AAC1B,QAAO,OAAO,SAAS,OAAO,IAAI,UAAU,IAAI,SAAS;IACvD;AAGJ,MAAM,mBAAmB,QAAQ,IAAI,wBAAwB;;;;;;AAO7D,SAAgB,gBAAgB,MAAgC;AAC9D,QAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,SAAS,IAAI,cAAc;AACjC,SAAO,KAAK,UAAU,QAA+B;AACnD,OAAI,IAAI,SAAS,gBAAgB,IAAI,SAAS,SAC5C,SAAQ,MAAM;OAEd,QAAO,IAAI;IAEb;AACF,SAAO,KAAK,mBAAmB;AAC7B,UAAO,YAAY,QAAQ,KAAK,CAAC;IACjC;AAGF,SAAO,OAAO;GAAE;GAAM,MAAM;GAAM,CAAC;GACnC;;AAGJ,eAAe,kBACb,WACA,YACA,QACiB;AACjB,KAAI,WAAY,QAAO;AACvB,MAAK,IAAI,IAAI,GAAG,IAAI,wBAAwB,KAAK;EAC/C,MAAM,YAAY,YAAY;AAC9B,MAAI,MAAM,gBAAgB,UAAU,EAAE;AACpC,OAAI,cAAc,UAChB,QAAO,KACL,QAAQ,UAAU,mCAAmC,UAAU,GAChE;AAEH,UAAO;;;AAKX,QAAO;;AAGT,eAAe,WACb,YACA,SACA,QACA;CACA,MAAM,EACJ,KACA,WAAW,EAAE,EACb,eAAe,EAAE,EACjB,SAAS,kBACP;AACJ,QAAO,QAAQ;CACf,MAAM,SACJ,QAAQ,UACR,QAAQ,IAAI,gBACZ,QAAQ,IAAI;CAGd,MAAM,gBAAgB,UAAU;CAEhC,MAAM,eACJ,QAAQ,IAAI,2BACZ,QAAQ,IAAI;CACd,MAAM,mBACJ,CAAC,gBAAgB,CAAC,cAAc,aAAa,GACzC,0BACA;CACN,MAAM,qBACJ,CAAC,UAAU,CAAC,cAAc,OAAO,GAAG,gBAAgB;CAMtD,MAAM,aAAa,CAAC,kBAAkB,mBAAmB,CAAC,QACvD,MAAmB,MAAM,KAC3B;CACD,MAAM,iCAAiB,IAAI,KAAqB;AAEhD,KAAI,QAAQ,mBAAmB,KAAA,KAAa,WAAW,SAAS,GAAG;AACjE,MAAI,QAAQ,cACV,QAAO,KACL,+GACD;AAEH,SAAO,KACL,uBAAuB,QAAQ,eAAe,yDAAyD,QAAQ,eAAe,GAC/H;AACD,OAAK,MAAM,OAAO,YAAY;AAC5B,SAAMC,SAAG,GAAG,KAAK;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC;AAClD,UAAO,KAAK,yBAAyB,MAAM;;YAEpC,QAAQ,mBAAmB,KAAA,GAAW;AAC/C,OAAK,MAAM,OAAO,YAAY;GAC5B,MAAM,QAAQ,MAAM,kBAAkB,IAAI;AAC1C,OAAI,UAAU,KAAM,gBAAe,IAAI,KAAK,MAAM;;AAGpD,MAAI,QAAQ,cACV,MAAK,MAAM,CAAC,KAAK,UAAU,gBAAgB;AACzC,OAAI,UAAA,GAA4B;AAChC,SAAM,iBAAiB,KAAK,OAAO;GAEnC,MAAM,QAAQ,MAAM,kBAAkB,IAAI;AAC1C,OAAI,UAAU,KAAM,gBAAe,IAAI,KAAK,MAAM;;MAGpD,MAAK,MAAM,CAAC,KAAK,UAAU,gBAAgB;AACzC,OAAI,UAAA,GAA4B;AAChC,UAAO,KACL,sBAAsB,IAAI,sBAAsB,MAAM,+HACvD;;;CAKP,SAAS,yBAAyB,KAA+B;AAC/D,MAAI,QAAQ,mBAAmB,KAAA,EAAW,QAAO,QAAQ;EACzD,MAAM,WAAW,eAAe,IAAI,IAAI;AACxC,MAAI,aAAa,KAAA,EAAW,QAAA;AAC5B,MAAI,CAAC,iBAAiB,SAAS,CAC7B,OAAM,IAAI,MACR,kCAAkC,IAAI,eAAe,WACtD;AAEH,SAAO;;CAGT,MAAM,qBAAqB,mBACvB,yBAAyB,iBAAiB,GAC1C;CACJ,MAAM,uBAAuB,qBACzB,yBAAyB,mBAAmB,GAC5C;CAQJ,MAAM,SAAoE,EACxE,SAAS,KAAA,GACV;CAKD,MAAM,SAAS,UADb,QAAQ,cAAcC,OAAK,KAAK,QAAQ,KAAK,EAAE,yBAAyB,CACtC;CACpC,MAAM,cAAc,QAAQ,IAAI,mBAAmB,OAAO;CAC1D,MAAM,mBAAmB,QAAQ,IAAI;CACrC,MAAM,sBACJ,QAAQ,uBAAuB,QAAQ,IAAI,0BAA0B;CACvE,IAAI;AAEJ,KAAI,aAAa;AAEf,WAAS,gBAAgB,OAAO,KAAK,IAAI;AACzC,eAAa,IAAI,kBAAkB,EAAE,aAAa,CAAC;AACnD,oBAAkB,MAAM,IAAI,CAAC,SAAS,MAAM;GAC1C,MAAM,OAAO,EAAE,MAAM;AACrB,OAAI,CAAC,SAAS,SAAS,KAAK,CAC1B,UAAS,KAAK,KAAK;IAErB;;CAGJ,MAAM,gBAAgB,OAAO,MAAM,CAAC,UAAU,CAAC;CAC/C,MAAM,mBAAmB,OAAO,qBAA0C;EACxE,MAAM,WAAW,IAAI,UAAU;EAC/B,MAAM,UAAU,IAAI,gBAAgB,CACjC,aAAa,SAAS,CACtB,mBACC,wBAAwB;GACtB;GACA;GACA,GAAGC;GACH,GAAGC;GACJ,CAAC,CACH,CACA,kBAAkB,cAAc,YAAY,CAC5C,oBAAoB,CACpB,WAAW,cAAc;EAE5B,MAAM,mBAAmB,SAAS,QAAQ,IAAI,sBAAsB,IAAI,GAAG;AAC3E,MAAI,CAAC,MAAM,iBAAiB,IAAI,mBAAmB,GAAG;AACpD,WAAQ,mBAAmB,EAAE,kBAAkB,CAAC;AAChD,UAAO,KAAK,mCAAmC,mBAAmB;;AAGpE,MAAI,gBAAgB,cAAc,aAAa,EAAE;GAK/C,MAAM,SAAS,IAAI,OAAiB,EAClC,SAAS,IAAI,gBAAgB,EAAE,MAFpB,IAAI,KAAK,EAAE,kBAHC,aAAa,SAAS,IAAI,GAC/C,eACA,GAAG,aAAa,mBACsB,CAAC,EAEJ,CAAC,EACvC,CAAC;AACF,WAAQ,WAAW,OAAO;AAC1B,UAAO,KAAK,uCAAuC;SAC9C;AACL,OAAI,CAAC,oBAAoB,uBAAuB,KAC9C,OAAM,IAAI,MAAM,wCAAwC;GAE1D,MAAM,EAAE,WAAW,MAAM,iBAAiB,mBAAmB;GAS7D,MAAM,SAAS,IAAI,OAAiB,EAClC,SAAS,IAAI,sBATA,mBACX,IAAI,QAAQ,GACZ,IAAI,OAAO,EACT,IAAI,IAAI,aAAa,kBAAkB;IACrC;IACA,iBAAiB;IAClB,CAAC,EACH,CAAC,CAEsC,EAC3C,CAAC;AACF,WAAQ,WAAW,OAAO;AAC1B,UAAO,KACL,mBACI,6BAA6B,mBAAmB,iDAChD,mBAAmB,mBAAmB,2BAA2B,mBACtE;;AAGH,UAAQ,iBAAiB,YAAY;AACnC,OAAI,OAAO,QAAS,OAAM,OAAO,QAAQ,SAAS;IAClD;AAEF,MAAI,cAAc,oBAChB,SAAQ,wBAAwB,WAAW,oBAAoB;EAGjE,MAAM,gBAAgB,IAAI,sBAAsB,CAAC,mBAC/C,QACD;AAED,MAAI,QAAQ;GACV,MAAM,eAAe,sBACnB,QACA,QAAQ,UAAU,kBACnB;AACD,iBAAc,WAAW,aAAa;;EAGxC,MAAM,SAAS,MAAM,cAAc,aAAa;AAEhD,MAAI,OAAO,eAAe;AACA,OAAI,uBAAuB,OAAO,cAAc,CACxD,OAAO;AACvB,iBAAc,KAAK,0CAA0C;;AAG/D,SAAO;;CAGT,IAAI,kBAAsC,KAAA;CAI1C,MAAM,WAAW,QAAQ,KAAK;CAC9B,MAAM,aAAa,iBAAiB,OAAO;CAC3C,MAAM,OAAO,MACT,MAAM,gBAAgB,QAAQ,KAAK,EAAE,WAAW,GAChD,KAAA;AAGJ,KAAI,CAAC,QAAQ,qBACX,UAAS,KAAK,SAAS;CAIzB,MAAM,iBAAmC,EAAE;AAC3C,KAAI,KACF,gBAAe,KAAK,kBAAkB,MAAM,KAAK,CAAC;KAElD,gBAAe,KAAK,IAAI,qBAAqB,CAAC;AAEhD,KAAI,YAAY;AACd,iBAAe,KAAK,WAAW;AAC/B,oBAAkB,MAAM,IAAI,CAAC,SAAS,MAAM;GAC1C,MAAM,OAAO,EAAE,MAAM;AACrB,OAAI,CAAC,SAAS,SAAS,KAAK,CAC1B,UAAS,KAAK,KAAK;IAErB;;CAGJ,MAAM,YAAY,OAAO,MAAM,CAAC,cAAc,CAAC;CAK/C,IAAI;AAGJ,KAAI,sBAAsB,yBAAyB,MAAM;EACvD,MAAM,EAAE,QAAQ,oBACd,MAAM,iBAAiB,qBAAqB;AAC9C,kBAAgB,yBACN,IAAI,iBAAiB,IAC1B,qBACC,IAAI,gBAAgB,EAClB,IAAI,IAAI,aACN,oBAAqB,oBACrB;GAAE;GAAQ,iBAAiB;GAA0B,CACtD,EACF,CAAC;;CAGV,MAAM,MAAM,MAAM,sBAChB,kBACA;EACE,MAAM;EACN,QAAQ;EACR;EACA,OAAO,QAAQ;EACf,gBAAgB,eAAe,SAAS,IAAI,iBAAiB,KAAA;EACnD;EACV,iBAAiB,QAAQ;EACzB,YAAY,EACV,wBAAwB,CAACC,iBAAsB,EAChD;EACD,YACE,QAAQ,cACRH,OAAK,KAAK,QAAQ,KAAK,EAAE,yBAAyB;EACpD,KAAK,QAAQ,OAAO;EACpB,QAAQ;EACR,8BAA8B,QAAQ;EACvC,EACD,cACD;AACD,QAAO,UAAU;AAEjB,0BAAyB,IAAI;AAE7B,KAAI,QAAQ,IAAI,WAGd,KAAI,YAAY,wBAAwB,OAAO;CAGjD,MAAM,EAAE,QAAQ,gBAAgB,0BAA0B;AAG1D,KAAI,YAAY;EACd,MAAM,2BAA2B,IAAI,yBAAyB;GAC5D,oBAAoB;GACpB;GACA;GACD,CAAC;AAEF,2BAAyB,yBAAyB;AAChD,kBAAe,kCAAkC,CAAC,MAAM,OAAO,MAAM;IACrE;EAEF,MAAM,mBAAmB,IAAI,iBAAiB;GAC5C,cAAc,KAAA;GACd,gBAAgB,KAAA;GAChB,eAAe;GACf;GACA,aAAa,IAAI;GACjB,MAAM,eAAe,aAAa;GAClC;GACD,CAAC;AAEG,iBACF,yBAAyB,kBAAkB,WAAW,MAAM,CAC5D,WAAW,eAAe,cAAc,CAAC,CACzC,OAAO,UAAmB;AACzB,UAAO,MAAM,gDAAgD,MAAM;IACnE;;AAIN,KAAI,QAAQ,OAAO;AACjB,MAAI,CAAC,OACH,OAAM,IAAI,MAAM,sDAAsD;AAGxE,oBAAkB,MAAM,gBAAgB,QAAQ,QAAQ,OAAO,WAAW;;AAI5E,KAAI,KACF,KAAI,YAAY,mBAAmB,KAAK,YAAY;AAItD,KAAI,aAAa,SAAS,EACxB,MAAK,MAAM,kBAAkB,cAAc;EACzC,IAAI;AAEJ,MAAI;GACF,MAAM,EAAE,gBAAgB;GACxB,MAAM,SAAS,cAAc,eAAe;AAC5C,aAAU,OAAO;GACjB,MAAM,aAAa,gBAAgB,QAAQ,GAAG,OAAO,YAAY;AACjE,SAAM,YAAY,IAAI,YAAY,kBAAkB,QAAQ,QAAQ,EAAE;IACpE,MAAM;IACN,YAAY,EAAE,KAAK,OAAO,iBAAiB;IAC5C,CAAC;AACF,UAAO,MAAM,uCAAuC,eAAe;WAC5D,OAAO;AACd,OACE,iBAAiB,SACjB,MAAM,QAAQ,SAAS,iBAAiB,EACxC;AACA,WAAO,MACL,+CACA,eACD;AACD,cAAU,eAAe,MAAM,IAAI,CAAC,KAAK;SAEzC,QAAO,MACL,6DACA,gBACA,MACD;YAEK;AAER,OAAI,CAAC,mBAAmB,QAEtB,mBAAkB,GADD,QAAQ,QAAQ,UAAU,OACb,eAAe,WAAW,KAAK;;;AAMrE,QAAO;EACL;EACA;EACA,SAAS;EACT;EACA,MAAM;EACP;;AAGH,MAAa,mBAAmB,OAC9B,UAA8B,EAAE,KACA;CAChC,MAAM,gBAAgB,QAAQ,QAAQ;CACtC,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,aAAa,MAAM,kBACvB,eACA,QAAQ,cAAc,OACtB,OACD;CAGD,MAAM,eAAe,MAAM,kBAAkB;CAE7C,MAAM,+BAA+B,MAAM,aAAa,gBACtD,kCACA,QAAQ,gCACN,yCACH;AAED,SAAQ,+BAA+B;CAEvC,MAAM,oBACJ,QAAQ,UAAU,qBACjB,MAAM,aAAa,gBAClB,oBACA,2BACD;AACH,SAAQ,WAAW;EAAE,GAAG,QAAQ;EAAU;EAAmB;AAE7D,QAAO,KACL,yBACA,KAAK,UACH;EACE,kCAAkC;EAClC,oBAAoB;EACrB,EACD,MACA,EACD,CACF;CAGD,IAAI,SAAyB;AAC7B,KAAI;AACF,WAAS,MAAM,WAAW,QAAQ,SAAS;UACpC,GAAG;AACV,SAAO,KAAK,8CAA8C,EAAE;AAC5D,MAAI,QAAQ,UAAU,gBACpB,OAAM,IAAI,MACR,uEACA,EAAE,OAAO,GAAG,CACb;;AAIL,KAAI;AACF,SAAO,MAAM,WAAW,YAAY,SAAS,OAAO;UAC7C,GAAG;AACV,SAAO,iBAAiB,EAAE;AAC1B,SAAO,MAAM,uBAAuB,EAAE;AACtC,QAAM;;;AAMV,IAAI,OAAO,KAAK,KACd,OAAM,kBAAkB","debug_id":"8faada52-173a-5928-8bb6-f2884360991b"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils-DFl0ezBT.mjs","sources":["../src/utils.mts"],"sourcesContent":["import type { IReactorClient } from \"@powerhousedao/reactor\";\nimport type { DocumentDriveDocument } from \"@powerhousedao/shared/document-drive\";\nimport {\n driveCreateDocument,\n driveCreateState,\n} from \"@powerhousedao/shared/document-drive\";\nimport type { DriveInput } from \"@powerhousedao/shared/document-drive\";\nimport { generateId } from \"@powerhousedao/shared/document-model\";\n\nexport async function addDefaultDrive(\n client: IReactorClient,\n drive: DriveInput,\n serverPort: number,\n) {\n let driveId = drive.id;\n if (!driveId || driveId.length === 0) {\n driveId = drive.slug;\n }\n\n if (!driveId || driveId.length === 0) {\n throw new Error(\"Invalid Drive Id\");\n }\n\n // check if the drive already exists\n let existingDrive;\n try {\n existingDrive = await client.get(driveId);\n } catch {\n //\n }\n\n // already exists, return the existing drive url\n if (existingDrive) {\n return `http://localhost:${serverPort}/d/${driveId}`;\n }\n\n const { global } = driveCreateState();\n const document = driveCreateDocument({\n global: {\n ...global,\n name: drive.global.name,\n icon: drive.global.icon ?? global.icon,\n },\n local: {\n availableOffline: drive.local?.availableOffline ?? false,\n sharingType: drive.local?.sharingType ?? \"public\",\n listeners: drive.local?.listeners ?? [],\n triggers: drive.local?.triggers ?? [],\n },\n });\n\n if (drive.id && drive.id.length > 0) {\n document.header.id = drive.id;\n }\n if (drive.slug && drive.slug.length > 0) {\n document.header.slug = drive.slug;\n }\n if (drive.global.name) {\n document.header.name = drive.global.name;\n }\n if (drive.preferredEditor) {\n document.header.meta = { preferredEditor: drive.preferredEditor };\n }\n\n try {\n await client.create(document);\n } catch (e) {\n const errorMessage = e instanceof Error ? e.message : String(e);\n if (!errorMessage.includes(\"already exists\")) {\n throw e;\n }\n }\n\n return `http://localhost:${serverPort}/d/${driveId}`;\n}\n\nexport function isPostgresUrl(url: string) {\n return url.startsWith(\"postgresql\") || url.startsWith(\"postgres\");\n}\n"],"names":[],"mappings":";;;;;AASA,eAAsB,gBACpB,QACA,OACA,YACA;CACA,IAAI,UAAU,MAAM;AACpB,KAAI,CAAC,WAAW,QAAQ,WAAW,EACjC,WAAU,MAAM;AAGlB,KAAI,CAAC,WAAW,QAAQ,WAAW,EACjC,OAAM,IAAI,MAAM,mBAAmB;CAIrC,IAAI;AACJ,KAAI;AACF,kBAAgB,MAAM,OAAO,IAAI,QAAQ;SACnC;AAKR,KAAI,cACF,QAAO,oBAAoB,WAAW,KAAK;CAG7C,MAAM,EAAE,WAAW,kBAAkB;CACrC,MAAM,WAAW,oBAAoB;EACnC,QAAQ;GACN,GAAG;GACH,MAAM,MAAM,OAAO;GACnB,MAAM,MAAM,OAAO,QAAQ,OAAO;GACnC;EACD,OAAO;GACL,kBAAkB,MAAM,OAAO,oBAAoB;GACnD,aAAa,MAAM,OAAO,eAAe;GACzC,WAAW,MAAM,OAAO,aAAa,EAAE;GACvC,UAAU,MAAM,OAAO,YAAY,EAAE;GACtC;EACF,CAAC;AAEF,KAAI,MAAM,MAAM,MAAM,GAAG,SAAS,EAChC,UAAS,OAAO,KAAK,MAAM;AAE7B,KAAI,MAAM,QAAQ,MAAM,KAAK,SAAS,EACpC,UAAS,OAAO,OAAO,MAAM;AAE/B,KAAI,MAAM,OAAO,KACf,UAAS,OAAO,OAAO,MAAM,OAAO;AAEtC,KAAI,MAAM,gBACR,UAAS,OAAO,OAAO,EAAE,iBAAiB,MAAM,iBAAiB;AAGnE,KAAI;AACF,QAAM,OAAO,OAAO,SAAS;UACtB,GAAG;AAEV,MAAI,EADiB,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,EAC7C,SAAS,iBAAiB,CAC1C,OAAM;;AAIV,QAAO,oBAAoB,WAAW,KAAK;;AAG7C,SAAgB,cAAc,KAAa;AACzC,QAAO,IAAI,WAAW,aAAa,IAAI,IAAI,WAAW,WAAW","debug_id":"288dca06-b884-52d3-95cc-2b04c8be496f"}