@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/CHANGELOG.md +796 -0
- package/Dockerfile +4 -4
- package/dist/src/index.js +33 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/metrics.d.ts +7 -0
- package/dist/src/metrics.d.ts.map +1 -0
- package/dist/src/metrics.js +34 -0
- package/dist/src/metrics.js.map +1 -0
- package/dist/src/profiler.d.ts +6 -1
- package/dist/src/profiler.d.ts.map +1 -1
- package/dist/src/profiler.js +31 -5
- package/dist/src/profiler.js.map +1 -1
- package/dist/src/renown.d.ts +16 -0
- package/dist/src/renown.d.ts.map +1 -0
- package/dist/src/renown.js +33 -0
- package/dist/src/renown.js.map +1 -0
- package/dist/src/server.d.ts +0 -1
- package/dist/src/server.d.ts.map +1 -1
- package/dist/src/server.js +136 -57
- package/dist/src/server.js.map +1 -1
- package/dist/src/types.d.ts +19 -6
- package/dist/src/types.d.ts.map +1 -1
- package/dist/test/metrics.test.d.ts +2 -0
- package/dist/test/metrics.test.d.ts.map +1 -0
- package/dist/test/metrics.test.js +121 -0
- package/dist/test/metrics.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/dist/vitest.config.js +15 -0
- package/dist/vitest.config.js.map +1 -0
- package/package.json +30 -22
- package/test/metrics.test.ts +202 -0
- package/tsconfig.json +8 -2
- package/vitest.config.ts +15 -0
- package/dist/src/connect-crypto.d.ts +0 -41
- package/dist/src/connect-crypto.d.ts.map +0 -1
- package/dist/src/connect-crypto.js +0 -127
- package/dist/src/connect-crypto.js.map +0 -1
package/Dockerfile
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Build stage
|
|
2
|
-
FROM node:
|
|
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
|
|
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:
|
|
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
|
-
|
|
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("
|
|
16
|
-
|
|
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
|
-
|
|
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
|
package/dist/src/index.js.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/src/profiler.d.ts
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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"}
|
package/dist/src/profiler.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
package/dist/src/profiler.js.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/src/server.d.ts
CHANGED
|
@@ -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
|
package/dist/src/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":";
|
|
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"}
|
package/dist/src/server.js
CHANGED
|
@@ -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 {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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 {
|
|
26
|
+
import { initRenown } from "./renown.js";
|
|
22
27
|
import { addDefaultDrive, addRemoteDrive, isPostgresUrl } from "./utils.js";
|
|
23
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
59
|
+
defaultLogger.warn("Can't reach database server at '@safeUrl'", safeUrl);
|
|
55
60
|
}
|
|
56
61
|
else {
|
|
57
|
-
|
|
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
|
-
|
|
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,
|
|
84
|
-
|
|
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
|
-
.
|
|
132
|
-
.
|
|
133
|
-
.
|
|
134
|
-
|
|
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 (
|
|
159
|
-
clientBuilder.withSigner(
|
|
192
|
+
if (renown) {
|
|
193
|
+
clientBuilder.withSigner(renown.signer);
|
|
160
194
|
}
|
|
161
195
|
const module = await clientBuilder.buildModule();
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
|
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
|
|
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
|
-
//
|
|
178
|
-
const
|
|
179
|
-
//
|
|
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
|
-
|
|
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 (!
|
|
197
|
-
throw new Error("Cannot create default drive without
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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(
|
|
300
|
+
logger.debug("Remote drive already added: @remoteDriveUrl", remoteDriveUrl);
|
|
217
301
|
driveId = remoteDriveUrl.split("/").pop();
|
|
218
302
|
}
|
|
219
303
|
else {
|
|
220
|
-
logger.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:
|
|
236
|
-
|
|
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.
|
|
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
|
|
259
|
-
|
|
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
|
-
|
|
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,
|
|
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
|