@powerhousedao/switchboard 6.0.0-dev.10 → 6.0.0-dev.100

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/Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
1
  # Build stage
2
- FROM node:22-alpine AS build
2
+ FROM node:24-alpine AS build
3
3
 
4
4
  WORKDIR /app
5
5
 
@@ -19,8 +19,8 @@ RUN pnpm config set @jsr:registry https://npm.jsr.io
19
19
  ARG TAG=latest
20
20
  ARG PH_PACKAGES=""
21
21
 
22
- # Install ph-cmd and prisma
23
- RUN pnpm add -g ph-cmd@$TAG prisma@5.17.0
22
+ # Install ph-cmd, prisma, and prettier
23
+ RUN pnpm add -g ph-cmd@$TAG prisma@5.17.0 prettier
24
24
 
25
25
  # Initialize project based on tag
26
26
  RUN case "$TAG" in \
@@ -44,7 +44,7 @@ RUN if [ -n "$PH_PACKAGES" ]; then \
44
44
  RUN prisma generate --schema node_modules/document-drive/dist/prisma/schema.prisma
45
45
 
46
46
  # Final stage - slim node image
47
- FROM node:22-alpine
47
+ FROM node:24-alpine
48
48
 
49
49
  WORKDIR /app
50
50
 
package/dist/src/index.js CHANGED
@@ -1,7 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ import * as Sentry from "@sentry/node";
3
+ import { childLogger } from "document-drive";
2
4
  import { config } from "./config.js";
5
+ import { createMeterProviderFromEnv } from "./metrics.js";
6
+ import { initProfilerFromEnv } from "./profiler.js";
3
7
  import { startSwitchboard } from "./server.js";
4
- function ensureNodeVersion(minVersion = "22") {
8
+ const logger = childLogger(["switchboard"]);
9
+ function ensureNodeVersion(minVersion = "24") {
5
10
  const version = process.versions.node;
6
11
  if (!version) {
7
12
  return;
@@ -12,10 +17,33 @@ function ensureNodeVersion(minVersion = "22") {
12
17
  }
13
18
  }
14
19
  // Ensure minimum Node.js version
15
- ensureNodeVersion("22");
16
- process.on("SIGINT", () => {
20
+ ensureNodeVersion("24");
21
+ const meterProvider = createMeterProviderFromEnv({
22
+ OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
23
+ OTEL_METRIC_EXPORT_INTERVAL: process.env.OTEL_METRIC_EXPORT_INTERVAL,
24
+ OTEL_SERVICE_NAME: process.env.OTEL_SERVICE_NAME,
25
+ });
26
+ async function shutdown() {
17
27
  console.log("\nShutting down...");
28
+ // Flush final metrics before exit. Races against a 5s deadline so an
29
+ // unresponsive OTLP endpoint cannot exhaust terminationGracePeriodSeconds.
30
+ await Promise.race([
31
+ meterProvider?.shutdown().catch(() => undefined),
32
+ new Promise((resolve) => setTimeout(resolve, 5_000)),
33
+ ]);
18
34
  process.exit(0);
19
- });
20
- startSwitchboard(config).catch(console.error);
35
+ }
36
+ // SIGINT: Ctrl-C in development; SIGTERM: graceful shutdown in Docker/Kubernetes
37
+ process.on("SIGINT", shutdown);
38
+ process.on("SIGTERM", shutdown);
39
+ if (process.env.PYROSCOPE_SERVER_ADDRESS) {
40
+ try {
41
+ await initProfilerFromEnv(process.env);
42
+ }
43
+ catch (e) {
44
+ Sentry.captureException(e);
45
+ logger.error("Error starting profiler: @error", e);
46
+ }
47
+ }
48
+ startSwitchboard({ ...config, meterProvider }).catch(console.error);
21
49
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,SAAS,iBAAiB,CAAC,UAAU,GAAG,IAAI;IAC1C,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;IACtC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO;IACT,CAAC;IAED,IAAI,OAAO,GAAG,UAAU,EAAE,CAAC;QACzB,OAAO,CAAC,KAAK,CACX,gBAAgB,UAAU,4CAA4C,OAAO,EAAE,CAChF,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AACD,iCAAiC;AACjC,iBAAiB,CAAC,IAAI,CAAC,CAAC;AAExB,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;IACxB,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IAClC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,gBAAgB,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AACA,OAAO,KAAK,MAAM,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;AAE5C,SAAS,iBAAiB,CAAC,UAAU,GAAG,IAAI;IAC1C,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;IACtC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO;IACT,CAAC;IAED,IAAI,OAAO,GAAG,UAAU,EAAE,CAAC;QACzB,OAAO,CAAC,KAAK,CACX,gBAAgB,UAAU,4CAA4C,OAAO,EAAE,CAChF,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AACD,iCAAiC;AACjC,iBAAiB,CAAC,IAAI,CAAC,CAAC;AAExB,MAAM,aAAa,GAAG,0BAA0B,CAAC;IAC/C,2BAA2B,EAAE,OAAO,CAAC,GAAG,CAAC,2BAA2B;IACpE,2BAA2B,EAAE,OAAO,CAAC,GAAG,CAAC,2BAA2B;IACpE,iBAAiB,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB;CACjD,CAAC,CAAC;AAEH,KAAK,UAAU,QAAQ;IACrB,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IAClC,qEAAqE;IACrE,2EAA2E;IAC3E,MAAM,OAAO,CAAC,IAAI,CAAC;QACjB,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;QAChD,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;KAC3D,CAAC,CAAC;IACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,iFAAiF;AACjF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAEhC,IAAI,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,CAAC;IACzC,IAAI,CAAC;QACH,MAAM,mBAAmB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;QAC3B,MAAM,CAAC,KAAK,CAAC,iCAAiC,EAAE,CAAC,CAAC,CAAC;IACrD,CAAC;AACH,CAAC;AAED,gBAAgB,CAAC,EAAE,GAAG,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { MeterProvider } from "@opentelemetry/sdk-metrics";
2
+ export declare function createMeterProviderFromEnv(env: {
3
+ OTEL_EXPORTER_OTLP_ENDPOINT?: string;
4
+ OTEL_METRIC_EXPORT_INTERVAL?: string;
5
+ OTEL_SERVICE_NAME?: string;
6
+ }): MeterProvider | undefined;
7
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/metrics.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,aAAa,EAEd,MAAM,4BAA4B,CAAC;AAKpC,wBAAgB,0BAA0B,CAAC,GAAG,EAAE;IAC9C,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,GAAG,aAAa,GAAG,SAAS,CA8B5B"}
@@ -0,0 +1,34 @@
1
+ import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
2
+ import { Resource } from "@opentelemetry/resources";
3
+ import { MeterProvider, PeriodicExportingMetricReader, } from "@opentelemetry/sdk-metrics";
4
+ import { childLogger } from "document-drive";
5
+ const logger = childLogger(["switchboard", "metrics"]);
6
+ export function createMeterProviderFromEnv(env) {
7
+ const endpoint = env.OTEL_EXPORTER_OTLP_ENDPOINT;
8
+ if (!endpoint)
9
+ return undefined;
10
+ const parsed = parseInt(env.OTEL_METRIC_EXPORT_INTERVAL ?? "", 10);
11
+ const exportIntervalMillis = Number.isFinite(parsed) && parsed > 0 ? parsed : 5_000;
12
+ const base = endpoint.replace(/\/$/, "");
13
+ const exporterUrl = base.endsWith("/v1/metrics")
14
+ ? base
15
+ : `${base}/v1/metrics`;
16
+ logger.info(`Initializing OpenTelemetry metrics exporter at: ${endpoint}`);
17
+ const meterProvider = new MeterProvider({
18
+ resource: new Resource({
19
+ "service.name": env.OTEL_SERVICE_NAME ?? "switchboard",
20
+ }),
21
+ readers: [
22
+ new PeriodicExportingMetricReader({
23
+ exporter: new OTLPMetricExporter({
24
+ url: exporterUrl,
25
+ }),
26
+ exportIntervalMillis,
27
+ exportTimeoutMillis: Math.max(exportIntervalMillis - 250, 1),
28
+ }),
29
+ ],
30
+ });
31
+ logger.info(`Metrics export enabled (interval: ${exportIntervalMillis}ms)`);
32
+ return meterProvider;
33
+ }
34
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAC/E,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EACL,aAAa,EACb,6BAA6B,GAC9B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC;AAEvD,MAAM,UAAU,0BAA0B,CAAC,GAI1C;IACC,MAAM,QAAQ,GAAG,GAAG,CAAC,2BAA2B,CAAC;IACjD,IAAI,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAC;IAEhC,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,2BAA2B,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACnE,MAAM,oBAAoB,GACxB,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;IAEzD,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACzC,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC;QAC9C,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,GAAG,IAAI,aAAa,CAAC;IAEzB,MAAM,CAAC,IAAI,CAAC,mDAAmD,QAAQ,EAAE,CAAC,CAAC;IAC3E,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC;QACtC,QAAQ,EAAE,IAAI,QAAQ,CAAC;YACrB,cAAc,EAAE,GAAG,CAAC,iBAAiB,IAAI,aAAa;SACvD,CAAC;QACF,OAAO,EAAE;YACP,IAAI,6BAA6B,CAAC;gBAChC,QAAQ,EAAE,IAAI,kBAAkB,CAAC;oBAC/B,GAAG,EAAE,WAAW;iBACjB,CAAC;gBACF,oBAAoB;gBACpB,mBAAmB,EAAE,IAAI,CAAC,GAAG,CAAC,oBAAoB,GAAG,GAAG,EAAE,CAAC,CAAC;aAC7D,CAAC;SACH;KACF,CAAC,CAAC;IACH,MAAM,CAAC,IAAI,CAAC,qCAAqC,oBAAoB,KAAK,CAAC,CAAC;IAC5E,OAAO,aAAa,CAAC;AACvB,CAAC"}
@@ -1,4 +1,9 @@
1
1
  import type { PyroscopeConfig } from "@pyroscope/nodejs";
2
2
  export declare function initProfilerFromEnv(env: typeof process.env): Promise<void>;
3
- export declare function initProfiler(options?: PyroscopeConfig): Promise<void>;
3
+ interface ProfilerFlags {
4
+ wallEnabled?: boolean;
5
+ heapEnabled?: boolean;
6
+ }
7
+ export declare function initProfiler(options?: PyroscopeConfig, flags?: ProfilerFlags): Promise<void>;
8
+ export {};
4
9
  //# sourceMappingURL=profiler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"profiler.d.ts","sourceRoot":"","sources":["../../src/profiler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,OAAO,OAAO,CAAC,GAAG,iBAehE;AAED,wBAAsB,YAAY,CAAC,OAAO,CAAC,EAAE,eAAe,iBAK3D"}
1
+ {"version":3,"file":"profiler.d.ts","sourceRoot":"","sources":["../../src/profiler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,OAAO,OAAO,CAAC,GAAG,iBAgChE;AAED,UAAU,aAAa;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,YAAY,CAChC,OAAO,CAAC,EAAE,eAAe,EACzB,KAAK,GAAE,aAAyD,iBAqBjE"}
@@ -1,17 +1,43 @@
1
1
  export async function initProfilerFromEnv(env) {
2
- const { PYROSCOPE_SERVER_ADDRESS: serverAddress, PYROSCOPE_APPLICATION_NAME: appName, PYROSCOPE_USER: basicAuthUser, PYROSCOPE_PASSWORD: basicAuthPassword, } = env;
2
+ const { PYROSCOPE_SERVER_ADDRESS: serverAddress, PYROSCOPE_APPLICATION_NAME: appName, PYROSCOPE_USER: basicAuthUser, PYROSCOPE_PASSWORD: basicAuthPassword, PYROSCOPE_WALL_ENABLED: wallEnabled, PYROSCOPE_HEAP_ENABLED: heapEnabled, } = env;
3
3
  const options = {
4
4
  serverAddress,
5
5
  appName,
6
6
  basicAuthUser,
7
7
  basicAuthPassword,
8
+ // Wall profiling captures wall-clock time (includes async I/O waits)
9
+ // This shows GraphQL resolvers even when waiting for database
10
+ wall: {
11
+ samplingDurationMs: 10000, // 10 second sampling windows
12
+ samplingIntervalMicros: 10000, // 10ms sampling interval (100 samples/sec)
13
+ collectCpuTime: true, // Also collect CPU time alongside wall time
14
+ },
15
+ // Heap profiling for memory allocation tracking
16
+ heap: {
17
+ samplingIntervalBytes: 512 * 1024, // Sample every 512KB allocated
18
+ stackDepth: 64, // Capture deeper stacks for better context
19
+ },
8
20
  };
9
- return initProfiler(options);
21
+ return initProfiler(options, {
22
+ wallEnabled: wallEnabled !== "false",
23
+ heapEnabled: heapEnabled === "true",
24
+ });
10
25
  }
11
- export async function initProfiler(options) {
26
+ export async function initProfiler(options, flags = { wallEnabled: true, heapEnabled: false }) {
12
27
  console.log("Initializing Pyroscope profiler at:", options?.serverAddress);
13
- const Pyroscope = await import("@pyroscope/nodejs");
28
+ console.log(" Wall profiling:", flags.wallEnabled ? "enabled" : "disabled");
29
+ console.log(" Heap profiling:", flags.heapEnabled ? "enabled" : "disabled");
30
+ const { default: Pyroscope } = await import("@pyroscope/nodejs");
14
31
  Pyroscope.init(options);
15
- Pyroscope.start();
32
+ // Start wall profiling (captures async I/O time - shows resolvers)
33
+ if (flags.wallEnabled) {
34
+ Pyroscope.startWallProfiling();
35
+ }
36
+ // Start CPU profiling (captures CPU-bound work)
37
+ Pyroscope.startCpuProfiling();
38
+ // Optionally start heap profiling (memory allocations)
39
+ if (flags.heapEnabled) {
40
+ Pyroscope.startHeapProfiling();
41
+ }
16
42
  }
17
43
  //# sourceMappingURL=profiler.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"profiler.js","sourceRoot":"","sources":["../../src/profiler.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,GAAuB;IAC/D,MAAM,EACJ,wBAAwB,EAAE,aAAa,EACvC,0BAA0B,EAAE,OAAO,EACnC,cAAc,EAAE,aAAa,EAC7B,kBAAkB,EAAE,iBAAiB,GACtC,GAAG,GAAG,CAAC;IAER,MAAM,OAAO,GAAoB;QAC/B,aAAa;QACb,OAAO;QACP,aAAa;QACb,iBAAiB;KAClB,CAAC;IACF,OAAO,YAAY,CAAC,OAAO,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAyB;IAC1D,OAAO,CAAC,GAAG,CAAC,qCAAqC,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;IAC3E,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACpD,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACxB,SAAS,CAAC,KAAK,EAAE,CAAC;AACpB,CAAC"}
1
+ {"version":3,"file":"profiler.js","sourceRoot":"","sources":["../../src/profiler.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,GAAuB;IAC/D,MAAM,EACJ,wBAAwB,EAAE,aAAa,EACvC,0BAA0B,EAAE,OAAO,EACnC,cAAc,EAAE,aAAa,EAC7B,kBAAkB,EAAE,iBAAiB,EACrC,sBAAsB,EAAE,WAAW,EACnC,sBAAsB,EAAE,WAAW,GACpC,GAAG,GAAG,CAAC;IAER,MAAM,OAAO,GAAoB;QAC/B,aAAa;QACb,OAAO;QACP,aAAa;QACb,iBAAiB;QACjB,qEAAqE;QACrE,8DAA8D;QAC9D,IAAI,EAAE;YACJ,kBAAkB,EAAE,KAAK,EAAE,6BAA6B;YACxD,sBAAsB,EAAE,KAAK,EAAE,2CAA2C;YAC1E,cAAc,EAAE,IAAI,EAAE,4CAA4C;SACnE;QACD,gDAAgD;QAChD,IAAI,EAAE;YACJ,qBAAqB,EAAE,GAAG,GAAG,IAAI,EAAE,+BAA+B;YAClE,UAAU,EAAE,EAAE,EAAE,2CAA2C;SAC5D;KACF,CAAC;IACF,OAAO,YAAY,CAAC,OAAO,EAAE;QAC3B,WAAW,EAAE,WAAW,KAAK,OAAO;QACpC,WAAW,EAAE,WAAW,KAAK,MAAM;KACpC,CAAC,CAAC;AACL,CAAC;AAOD,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,OAAyB,EACzB,QAAuB,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE;IAEhE,OAAO,CAAC,GAAG,CAAC,qCAAqC,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;IAC3E,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IAC7E,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IAE7E,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACjE,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAExB,mEAAmE;IACnE,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;QACtB,SAAS,CAAC,kBAAkB,EAAE,CAAC;IACjC,CAAC;IAED,gDAAgD;IAChD,SAAS,CAAC,iBAAiB,EAAE,CAAC;IAE9B,uDAAuD;IACvD,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;QACtB,SAAS,CAAC,kBAAkB,EAAE,CAAC;IACjC,CAAC;AACH,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { type IRenown } from "@renown/sdk/node";
2
+ export interface RenownOptions {
3
+ /** Path to the keypair file. Defaults to .ph/.keypair.json in cwd */
4
+ keypairPath?: string;
5
+ /** If true, won't generate a new keypair if none exists */
6
+ requireExisting?: boolean;
7
+ /** Base url of the Renown instance to use */
8
+ baseUrl?: string;
9
+ }
10
+ /**
11
+ * Initialize Renown for the Switchboard instance.
12
+ * This allows Switchboard to authenticate with remote services
13
+ * using the same identity established during `ph login`.
14
+ */
15
+ export declare function initRenown(options?: RenownOptions): Promise<IRenown | null>;
16
+ //# sourceMappingURL=renown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renown.d.ts","sourceRoot":"","sources":["../../src/renown.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,OAAO,EACb,MAAM,kBAAkB,CAAC;AAI1B,MAAM,WAAW,aAAa;IAC5B,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAqCzB"}
@@ -0,0 +1,33 @@
1
+ import { childLogger } from "document-drive";
2
+ import { DEFAULT_RENOWN_URL, NodeKeyStorage, RenownBuilder, RenownCryptoBuilder, } from "@renown/sdk/node";
3
+ const logger = childLogger(["switchboard", "renown"]);
4
+ /**
5
+ * Initialize Renown for the Switchboard instance.
6
+ * This allows Switchboard to authenticate with remote services
7
+ * using the same identity established during `ph login`.
8
+ */
9
+ export async function initRenown(options = {}) {
10
+ const { keypairPath, requireExisting = false, baseUrl = DEFAULT_RENOWN_URL, } = options;
11
+ const keyStorage = new NodeKeyStorage(keypairPath, {
12
+ logger,
13
+ });
14
+ // Check if we have an existing keypair
15
+ const existingKeyPair = await keyStorage.loadKeyPair();
16
+ if (!existingKeyPair && requireExisting) {
17
+ throw new Error("No existing keypair found and requireExisting is true. " +
18
+ 'Run "ph login" to create one.');
19
+ }
20
+ if (!existingKeyPair) {
21
+ logger.info("No existing keypair found. A new one will be generated.");
22
+ }
23
+ const renownCrypto = await new RenownCryptoBuilder()
24
+ .withKeyPairStorage(keyStorage)
25
+ .build();
26
+ const renown = await new RenownBuilder("switchboard", {})
27
+ .withCrypto(renownCrypto)
28
+ .withBaseUrl(baseUrl)
29
+ .build();
30
+ logger.info("Switchboard identity initialized: @did", renownCrypto.did);
31
+ return renown;
32
+ }
33
+ //# sourceMappingURL=renown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renown.js","sourceRoot":"","sources":["../../src/renown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,mBAAmB,GAEpB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC;AAWtD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,UAAyB,EAAE;IAE3B,MAAM,EACJ,WAAW,EACX,eAAe,GAAG,KAAK,EACvB,OAAO,GAAG,kBAAkB,GAC7B,GAAG,OAAO,CAAC;IAEZ,MAAM,UAAU,GAAG,IAAI,cAAc,CAAC,WAAW,EAAE;QACjD,MAAM;KACP,CAAC,CAAC;IAEH,uCAAuC;IACvC,MAAM,eAAe,GAAG,MAAM,UAAU,CAAC,WAAW,EAAE,CAAC;IAEvD,IAAI,CAAC,eAAe,IAAI,eAAe,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CACb,yDAAyD;YACvD,+BAA+B,CAClC,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,MAAM,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,IAAI,mBAAmB,EAAE;SACjD,kBAAkB,CAAC,UAAU,CAAC;SAC9B,KAAK,EAAE,CAAC;IAEX,MAAM,MAAM,GAAG,MAAM,IAAI,aAAa,CAAC,aAAa,EAAE,EAAE,CAAC;SACtD,UAAU,CAAC,YAAY,CAAC;SACxB,WAAW,CAAC,OAAO,CAAC;SACpB,KAAK,EAAE,CAAC;IAEX,MAAM,CAAC,IAAI,CAAC,wCAAwC,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC;IAExE,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import type { StartServerOptions, SwitchboardReactor } from "./types.js";
3
3
  export declare const startSwitchboard: (options?: StartServerOptions) => Promise<SwitchboardReactor>;
4
- export { getBearerToken, getConnectCrypto, getConnectDid, } from "./connect-crypto.js";
5
4
  export * from "./types.js";
6
5
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":";AA2CA,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAiSzE,eAAO,MAAM,gBAAgB,GAC3B,UAAS,kBAAuB,KAC/B,OAAO,CAAC,kBAAkB,CAiE5B,CAAC;AAEF,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":";AAsDA,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAwYzE,eAAO,MAAM,gBAAgB,GAC3B,UAAS,kBAAuB,KAC/B,OAAO,CAAC,kBAAkB,CAiE5B,CAAC;AAEF,cAAc,YAAY,CAAC"}
@@ -1,8 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ import { httpsHooksPath } from "@powerhousedao/reactor-api";
3
+ import { register } from "node:module";
4
+ // Register HTTP/HTTPS module loader hooks for dynamic package imports
5
+ register(httpsHooksPath, import.meta.url);
2
6
  import { PGlite } from "@electric-sql/pglite";
3
- import { CompositeChannelFactory, ConsoleLogger, EventBus, ReactorBuilder, ReactorClientBuilder, SyncBuilder, } from "@powerhousedao/reactor";
4
- import { VitePackageLoader, getUniqueDocumentModels, initializeAndStartAPI, startViteServer, } from "@powerhousedao/reactor-api";
5
- import { ConnectCryptoSigner } from "@renown/sdk";
7
+ import { ReactorInstrumentation } from "@powerhousedao/opentelemetry-instrumentation-reactor";
8
+ import { ChannelScheme, EventBus, ReactorBuilder, ReactorClientBuilder, driveCollectionId, parseDriveUrl, } from "@powerhousedao/reactor";
9
+ import { metrics } from "@opentelemetry/api";
10
+ import { HttpPackageLoader, PackageManagementService, PackagesSubgraph, VitePackageLoader, createViteLogger, getUniqueDocumentModels, initializeAndStartAPI, startViteServer, } from "@powerhousedao/reactor-api";
11
+ import {} from "@renown/sdk";
6
12
  import * as Sentry from "@sentry/node";
7
13
  import { DocumentAlreadyExistsError, InMemoryCache, ReactorBuilder as LegacyReactorBuilder, childLogger, driveDocumentModelModule, } from "document-drive";
8
14
  import { RedisCache } from "document-drive/cache/redis";
@@ -16,11 +22,10 @@ import { PGliteDialect } from "kysely-pglite-dialect";
16
22
  import path from "path";
17
23
  import { Pool } from "pg";
18
24
  import { initRedis } from "./clients/redis.js";
19
- import { initConnectCrypto } from "./connect-crypto.js";
20
25
  import { initFeatureFlags } from "./feature-flags.js";
21
- import { initProfilerFromEnv } from "./profiler.js";
26
+ import { initRenown } from "./renown.js";
22
27
  import { addDefaultDrive, addRemoteDrive, isPostgresUrl } from "./utils.js";
23
- const logger = childLogger(["switchboard"]);
28
+ const defaultLogger = childLogger(["switchboard"]);
24
29
  dotenv.config();
25
30
  // Feature flag constants
26
31
  const DOCUMENT_MODEL_SUBGRAPHS_ENABLED = "DOCUMENT_MODEL_SUBGRAPHS_ENABLED";
@@ -32,7 +37,7 @@ const ENABLE_DUAL_ACTION_CREATE_DEFAULT = true;
32
37
  // Create a monolith express app for all subgraphs
33
38
  const app = express();
34
39
  if (process.env.SENTRY_DSN) {
35
- logger.info("Initialized Sentry with env:", process.env.SENTRY_ENV);
40
+ defaultLogger.info("Initialized Sentry with env: @env", process.env.SENTRY_ENV);
36
41
  Sentry.init({
37
42
  dsn: process.env.SENTRY_DSN,
38
43
  environment: process.env.SENTRY_ENV,
@@ -51,10 +56,10 @@ async function initPrismaStorage(connectionString, cache) {
51
56
  if (e instanceof Error && e.message.includes(prismaConnectError)) {
52
57
  const dbUrl = connectionString;
53
58
  const safeUrl = `${dbUrl.slice(0, dbUrl.indexOf(":") + 1)}{...}${dbUrl.slice(dbUrl.indexOf("@"), dbUrl.lastIndexOf("?"))}`;
54
- logger.warn(`Can't reach database server at '${safeUrl}'`);
59
+ defaultLogger.warn("Can't reach database server at '@safeUrl'", safeUrl);
55
60
  }
56
61
  else {
57
- logger.error(e);
62
+ defaultLogger.error("@error", e);
58
63
  }
59
64
  throw e;
60
65
  }
@@ -71,7 +76,7 @@ async function initReactorStorage(cache, dbPath = "./.ph/drive-storage") {
71
76
  }
72
77
  }
73
78
  catch {
74
- logger.warn("Falling back to filesystem storage");
79
+ defaultLogger.warn("Falling back to filesystem storage");
75
80
  }
76
81
  // if url was postgres and connection failed, fallback to filesystem on default path
77
82
  const filesystemPath = isPostgres ? "./.ph/drive-storage" : dbPath;
@@ -80,8 +85,15 @@ async function initReactorStorage(cache, dbPath = "./.ph/drive-storage") {
80
85
  storagePath: filesystemPath,
81
86
  };
82
87
  }
83
- async function initServer(serverPort, options, connectCrypto) {
84
- const { dev, packages = [], remoteDrives = [] } = options;
88
+ async function initServer(serverPort, options, renown) {
89
+ // Register the global MeterProvider before ReactorInstrumentation is
90
+ // constructed. setGlobalMeterProvider is a one-way door — once set it cannot
91
+ // be unset — so this must happen before initializeClient calls
92
+ // instrumentation.start() → createMetrics() → metrics.getMeter().
93
+ if (options.meterProvider) {
94
+ metrics.setGlobalMeterProvider(options.meterProvider);
95
+ }
96
+ const { dev, packages = [], remoteDrives = [], logger = defaultLogger, } = options;
85
97
  const dbPath = options.dbPath ?? process.env.DATABASE_URL;
86
98
  // start redis if configured
87
99
  const redisUrl = process.env.REDIS_TLS_URL ?? process.env.REDIS_URL;
@@ -91,7 +103,7 @@ async function initServer(serverPort, options, connectCrypto) {
91
103
  redis = await initRedis(redisUrl);
92
104
  }
93
105
  catch (e) {
94
- logger.error(e);
106
+ logger.error("@error", e);
95
107
  }
96
108
  }
97
109
  const cache = redis ? new RedisCache(redis) : new InMemoryCache();
@@ -101,11 +113,25 @@ async function initServer(serverPort, options, connectCrypto) {
101
113
  const readModelPath = !dbPath || (isPostgresUrl(dbPath) && dbPath !== storagePath)
102
114
  ? ".ph/read-storage"
103
115
  : dbPath;
116
+ // HTTP registry package loading
117
+ let httpDocumentModels = [];
118
+ const registryUrl = process.env.PH_REGISTRY_URL;
119
+ const registryPackages = process.env.PH_REGISTRY_PACKAGES;
120
+ let httpLoader;
121
+ if (registryUrl) {
122
+ httpLoader = new HttpPackageLoader({ registryUrl });
123
+ }
124
+ if (httpLoader && registryPackages) {
125
+ const packageNames = registryPackages.split(",").map((p) => p.trim());
126
+ httpDocumentModels = await httpLoader.loadPackages(packageNames, logger);
127
+ logger.info(`Loaded ${httpDocumentModels.length} HTTP document models from ${packageNames.length} packages`);
128
+ }
104
129
  const initializeDriveServer = async (documentModels) => {
105
130
  const driveServer = new LegacyReactorBuilder(getUniqueDocumentModels([
106
131
  documentModelDocumentModelModule,
107
132
  driveDocumentModelModule,
108
133
  ...documentModels,
134
+ ...httpDocumentModels,
109
135
  ]))
110
136
  .withStorage(storage)
111
137
  .withCache(cache)
@@ -119,6 +145,7 @@ async function initServer(serverPort, options, connectCrypto) {
119
145
  await driveServer.initialize();
120
146
  return driveServer;
121
147
  };
148
+ const reactorLogger = logger.child(["reactor"]);
122
149
  const initializeClient = async (driveServer, documentModels) => {
123
150
  const eventBus = new EventBus();
124
151
  const builder = new ReactorBuilder()
@@ -127,12 +154,16 @@ async function initServer(serverPort, options, connectCrypto) {
127
154
  documentModelDocumentModelModule,
128
155
  driveDocumentModelModule,
129
156
  ...documentModels,
157
+ ...httpDocumentModels,
130
158
  ]))
131
- .withLegacyStorage(storage)
132
- .withSync(new SyncBuilder().withChannelFactory(new CompositeChannelFactory(new ConsoleLogger(["switchboard"]))))
133
- .withFeatures({
134
- legacyStorageEnabled: !options.reactorOptions?.storageV2,
135
- });
159
+ .withChannelScheme(ChannelScheme.SWITCHBOARD)
160
+ .withSignalHandlers()
161
+ .withLogger(reactorLogger);
162
+ const maxSkipThreshold = parseInt(process.env.MAX_SKIP_THRESHOLD ?? "", 10);
163
+ if (!isNaN(maxSkipThreshold) && maxSkipThreshold > 0) {
164
+ builder.withExecutorConfig({ maxSkipThreshold });
165
+ logger.info(`Reactor maxSkipThreshold set to ${maxSkipThreshold}`);
166
+ }
136
167
  const reactorDbUrl = process.env.PH_REACTOR_DATABASE_URL;
137
168
  if (reactorDbUrl && isPostgresUrl(reactorDbUrl)) {
138
169
  const connectionString = reactorDbUrl.includes("?")
@@ -154,29 +185,40 @@ async function initServer(serverPort, options, connectCrypto) {
154
185
  builder.withKysely(kysely);
155
186
  logger.info("Using PGlite for reactor storage");
156
187
  }
188
+ if (httpLoader) {
189
+ builder.withDocumentModelLoader(httpLoader);
190
+ }
157
191
  const clientBuilder = new ReactorClientBuilder().withReactorBuilder(builder);
158
- if (connectCrypto) {
159
- clientBuilder.withSigner(new ConnectCryptoSigner(connectCrypto));
192
+ if (renown) {
193
+ clientBuilder.withSigner(renown.signer);
160
194
  }
161
195
  const module = await clientBuilder.buildModule();
162
- const syncManager = module.reactorModule?.syncModule?.syncManager;
163
- if (!syncManager) {
164
- throw new Error("SyncManager not available from ReactorClientBuilder");
196
+ if (module.reactorModule) {
197
+ const instrumentation = new ReactorInstrumentation(module.reactorModule);
198
+ instrumentation.start();
199
+ reactorLogger.info("Reactor metrics instrumentation started");
165
200
  }
166
- return { client: module.client, syncManager };
201
+ return module;
167
202
  };
168
203
  let defaultDriveUrl = undefined;
169
204
  // TODO get path from powerhouse config
170
205
  // start vite server if dev mode is enabled
171
206
  const basePath = process.cwd();
172
- const vite = dev ? await startViteServer(process.cwd()) : undefined;
207
+ const viteLogger = createViteLogger(logger);
208
+ const vite = dev
209
+ ? await startViteServer(process.cwd(), viteLogger)
210
+ : undefined;
173
211
  // get paths to local document models
174
212
  if (!options.disableLocalPackages) {
175
213
  packages.push(basePath);
176
214
  }
177
- // create loader
178
- const packageLoader = vite ? VitePackageLoader.build(vite) : undefined;
179
- // Start the API with the reactor and options
215
+ // storageV2=true means use new reactor (NOT legacy)
216
+ const legacyReactor = !options.reactorOptions?.storageV2;
217
+ // create loader with legacyReactor option
218
+ const packageLoader = vite
219
+ ? VitePackageLoader.build(vite, { legacyReactor })
220
+ : undefined;
221
+ const apiLogger = logger.child(["reactor-api"]);
180
222
  const api = await initializeAndStartAPI(initializeDriveServer, initializeClient, {
181
223
  express: app,
182
224
  port: serverPort,
@@ -188,13 +230,41 @@ async function initServer(serverPort, options, connectCrypto) {
188
230
  configFile: options.configFile ??
189
231
  path.join(process.cwd(), "powerhouse.config.json"),
190
232
  mcp: options.mcp ?? true,
233
+ logger: apiLogger,
191
234
  enableDocumentModelSubgraphs: options.enableDocumentModelSubgraphs,
192
- });
193
- const { client, driveServer } = api;
235
+ legacyReactor,
236
+ }, "switchboard");
237
+ const { client, driveServer, graphqlManager, documentModelRegistry } = api;
238
+ // Wire up dynamic package management if HTTP loader is configured
239
+ if (httpLoader) {
240
+ const packageManagementService = new PackageManagementService({
241
+ defaultRegistryUrl: registryUrl,
242
+ httpLoader,
243
+ documentModelRegistry,
244
+ });
245
+ packageManagementService.setOnModelsChanged(async () => {
246
+ await graphqlManager.regenerateDocumentModelSubgraphs();
247
+ });
248
+ const packagesSubgraph = new PackagesSubgraph({
249
+ relationalDb: undefined,
250
+ analyticsStore: undefined,
251
+ reactorClient: client,
252
+ graphqlManager,
253
+ syncManager: api.syncManager,
254
+ path: graphqlManager.getBasePath(),
255
+ packageManagementService,
256
+ });
257
+ void graphqlManager
258
+ .registerSubgraphInstance(packagesSubgraph, "graphql", false)
259
+ .then(() => graphqlManager.updateRouter())
260
+ .catch((error) => {
261
+ logger.error("Failed to register packages subgraph: @error", error);
262
+ });
263
+ }
194
264
  // Create default drive if provided
195
265
  if (options.drive) {
196
- if (!connectCrypto) {
197
- throw new Error("Cannot create default drive without ConnectCrypto identity");
266
+ if (!renown) {
267
+ throw new Error("Cannot create default drive without Renown identity");
198
268
  }
199
269
  defaultDriveUrl = await addDefaultDrive(driveServer, client, options.drive, serverPort);
200
270
  }
@@ -207,17 +277,31 @@ async function initServer(serverPort, options, connectCrypto) {
207
277
  for (const remoteDriveUrl of remoteDrives) {
208
278
  let driveId;
209
279
  try {
210
- const remoteDrive = await addRemoteDrive(driveServer, remoteDriveUrl);
211
- driveId = remoteDrive.header.id;
212
- logger.debug(`Remote drive ${remoteDriveUrl} synced`);
280
+ if (legacyReactor) {
281
+ // Use legacy reactor's addRemoteDrive
282
+ const remoteDrive = await addRemoteDrive(driveServer, remoteDriveUrl);
283
+ driveId = remoteDrive.header.id;
284
+ }
285
+ else {
286
+ // Use new reactor's sync manager
287
+ const { syncManager } = api;
288
+ const parsed = parseDriveUrl(remoteDriveUrl);
289
+ driveId = parsed.driveId;
290
+ const remoteName = `remote-drive-${driveId}-${crypto.randomUUID()}`;
291
+ await syncManager.add(remoteName, driveCollectionId("main", driveId), {
292
+ type: "gql",
293
+ parameters: { url: parsed.graphqlEndpoint },
294
+ });
295
+ }
296
+ logger.debug("Remote drive @remoteDriveUrl synced", remoteDriveUrl);
213
297
  }
214
298
  catch (error) {
215
299
  if (error instanceof DocumentAlreadyExistsError) {
216
- logger.debug(`Remote drive already added: ${remoteDriveUrl}`);
300
+ logger.debug("Remote drive already added: @remoteDriveUrl", remoteDriveUrl);
217
301
  driveId = remoteDriveUrl.split("/").pop();
218
302
  }
219
303
  else {
220
- logger.error(`Failed to connect to remote drive ${remoteDriveUrl}:`, error);
304
+ logger.error("Failed to connect to remote drive @remoteDriveUrl: @error", remoteDriveUrl, error);
221
305
  }
222
306
  }
223
307
  finally {
@@ -232,8 +316,9 @@ async function initServer(serverPort, options, connectCrypto) {
232
316
  return {
233
317
  defaultDriveUrl,
234
318
  api,
235
- reactor: driveServer,
236
- connectCrypto,
319
+ reactor: client,
320
+ legacyReactor: driveServer,
321
+ renown,
237
322
  };
238
323
  }
239
324
  export const startSwitchboard = async (options = {}) => {
@@ -250,40 +335,34 @@ export const startSwitchboard = async (options = {}) => {
250
335
  enableDualActionCreate,
251
336
  storageV2,
252
337
  };
253
- logger.info("Feature flags:", {
338
+ const logger = options.logger ?? defaultLogger;
339
+ logger.info("Feature flags: @flags", JSON.stringify({
254
340
  DOCUMENT_MODEL_SUBGRAPHS_ENABLED: enableDocumentModelSubgraphs,
255
341
  REACTOR_STORAGE_V2: storageV2,
256
342
  ENABLE_DUAL_ACTION_CREATE: enableDualActionCreate,
257
- });
258
- if (process.env.PYROSCOPE_SERVER_ADDRESS) {
259
- try {
260
- await initProfilerFromEnv(process.env);
261
- }
262
- catch (e) {
263
- Sentry.captureException(e);
264
- logger.error("Error starting profiler", e);
265
- }
266
- }
267
- // Initialize ConnectCrypto if identity options are provided or keypair exists
268
- let connectCrypto = null;
343
+ }, null, 2));
344
+ // Initialize Renown if identity options are provided or keypair exists
345
+ let renown = null;
269
346
  try {
270
- connectCrypto = await initConnectCrypto(options.identity);
347
+ renown = await initRenown(options.identity);
271
348
  }
272
349
  catch (e) {
273
- logger.warn("Failed to initialize ConnectCrypto:", e);
350
+ logger.warn("Failed to initialize ConnectCrypto: @error", e);
274
351
  if (options.identity?.requireExisting) {
275
352
  throw new Error('Identity required but failed to initialize. Run "ph login" first.');
276
353
  }
277
354
  }
278
355
  try {
279
- return await initServer(serverPort, options, connectCrypto);
356
+ return await initServer(serverPort, options, renown);
280
357
  }
281
358
  catch (e) {
282
359
  Sentry.captureException(e);
283
- logger.error("App crashed", e);
360
+ logger.error("App crashed: @error", e);
284
361
  throw e;
285
362
  }
286
363
  };
287
- export { getBearerToken, getConnectCrypto, getConnectDid, } from "./connect-crypto.js";
288
364
  export * from "./types.js";
365
+ if (import.meta.main) {
366
+ await startSwitchboard();
367
+ }
289
368
  //# sourceMappingURL=server.js.map