@powerhousedao/switchboard 6.2.0-dev.8 → 6.2.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.tsbuild/tsconfig.tsbuildinfo +1 -1
- package/Auth.md +1 -1
- package/CHANGELOG.md +216 -2
- package/dist/index.mjs +14 -8
- package/dist/index.mjs.map +1 -1
- package/dist/{server-BsNSEbMd.mjs → server-WpAvDbVc.mjs} +44 -40
- package/dist/server-WpAvDbVc.mjs.map +1 -0
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +3 -3
- package/package.json +16 -15
- package/tsconfig.json +2 -6
- package/dist/server-BsNSEbMd.mjs.map +0 -1
package/Auth.md
CHANGED
|
@@ -123,7 +123,7 @@ Authentication produces a verified user; authorization is then decided by a glob
|
|
|
123
123
|
interface AuthConfig {
|
|
124
124
|
enabled: boolean;
|
|
125
125
|
admins: string[]; // Wallet addresses with global admin (bypass) access
|
|
126
|
-
skipCredentialVerification?: boolean; //
|
|
126
|
+
skipCredentialVerification?: boolean; // DANGER (test/dev only): skips the Renown credential re-check, the only binding between a token's claimed address and its signing key — allows identity spoofing. Refused at boot unless VITEST/NODE_ENV=test or ALLOW_INSECURE_SKIP_CREDENTIAL_VERIFICATION=true.
|
|
127
127
|
}
|
|
128
128
|
```
|
|
129
129
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,217 @@
|
|
|
1
|
+
## 6.2.0-rc.0 (2026-06-26)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- **release:** support release/rc/x.x.x branches publishing under -rc.x npm tag ([51a030c04](https://github.com/powerhouse-inc/powerhouse/commit/51a030c04))
|
|
6
|
+
|
|
7
|
+
### ❤️ Thank You
|
|
8
|
+
|
|
9
|
+
- Guillermo Puente @gpuente
|
|
10
|
+
|
|
11
|
+
## 6.2.0-dev.38 (2026-06-26)
|
|
12
|
+
|
|
13
|
+
This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
|
|
14
|
+
|
|
15
|
+
## 6.2.0-dev.37 (2026-06-25)
|
|
16
|
+
|
|
17
|
+
This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
|
|
18
|
+
|
|
19
|
+
## 6.2.0-dev.36 (2026-06-25)
|
|
20
|
+
|
|
21
|
+
This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
|
|
22
|
+
|
|
23
|
+
## 6.2.0-dev.35 (2026-06-25)
|
|
24
|
+
|
|
25
|
+
This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
|
|
26
|
+
|
|
27
|
+
## 6.2.0-dev.34 (2026-06-25)
|
|
28
|
+
|
|
29
|
+
This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
|
|
30
|
+
|
|
31
|
+
## 6.2.0-dev.33 (2026-06-25)
|
|
32
|
+
|
|
33
|
+
### 🩹 Fixes
|
|
34
|
+
|
|
35
|
+
- package updates never made it in ([a543795e3](https://github.com/powerhouse-inc/powerhouse/commit/a543795e3))
|
|
36
|
+
- test fixes ([7784d7e6b](https://github.com/powerhouse-inc/powerhouse/commit/7784d7e6b))
|
|
37
|
+
- **academy:** repair broken academy build and broken links ([b6de178fc](https://github.com/powerhouse-inc/powerhouse/commit/b6de178fc))
|
|
38
|
+
|
|
39
|
+
### ❤️ Thank You
|
|
40
|
+
|
|
41
|
+
- Benjamin Jordan
|
|
42
|
+
- CallmeT-ty @CallmeT-ty
|
|
43
|
+
- Claude Opus 4.8
|
|
44
|
+
|
|
45
|
+
## 6.2.0-dev.31 (2026-06-23)
|
|
46
|
+
|
|
47
|
+
This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
|
|
48
|
+
|
|
49
|
+
## 6.2.0-dev.30 (2026-06-23)
|
|
50
|
+
|
|
51
|
+
### 🔥 Performance
|
|
52
|
+
|
|
53
|
+
- **codegen:** replace prettier with oxfmt for generation-time formatting ([a2faa630d](https://github.com/powerhouse-inc/powerhouse/commit/a2faa630d))
|
|
54
|
+
|
|
55
|
+
### ❤️ Thank You
|
|
56
|
+
|
|
57
|
+
- acaldas
|
|
58
|
+
|
|
59
|
+
## 6.2.0-dev.29 (2026-06-23)
|
|
60
|
+
|
|
61
|
+
This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
|
|
62
|
+
|
|
63
|
+
## 6.2.0-dev.28 (2026-06-22)
|
|
64
|
+
|
|
65
|
+
This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
|
|
66
|
+
|
|
67
|
+
## 6.2.0-dev.27 (2026-06-22)
|
|
68
|
+
|
|
69
|
+
This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
|
|
70
|
+
|
|
71
|
+
## 6.2.0-dev.26 (2026-06-21)
|
|
72
|
+
|
|
73
|
+
This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
|
|
74
|
+
|
|
75
|
+
## 6.2.0-dev.25 (2026-06-20)
|
|
76
|
+
|
|
77
|
+
### 🚀 Features
|
|
78
|
+
|
|
79
|
+
- doc-capture skill for ph-lora( ([446d854b4](https://github.com/powerhouse-inc/powerhouse/commit/446d854b4))
|
|
80
|
+
|
|
81
|
+
### 🩹 Fixes
|
|
82
|
+
|
|
83
|
+
- academy agents ([4630e22d9](https://github.com/powerhouse-inc/powerhouse/commit/4630e22d9))
|
|
84
|
+
|
|
85
|
+
### ❤️ Thank You
|
|
86
|
+
|
|
87
|
+
- CallmeT-ty @CallmeT-ty
|
|
88
|
+
|
|
89
|
+
## 6.2.0-dev.24 (2026-06-19)
|
|
90
|
+
|
|
91
|
+
### 🩹 Fixes
|
|
92
|
+
|
|
93
|
+
- release unblocker & clean up ([56090fd53](https://github.com/powerhouse-inc/powerhouse/commit/56090fd53))
|
|
94
|
+
|
|
95
|
+
### ❤️ Thank You
|
|
96
|
+
|
|
97
|
+
- CallmeT-ty @CallmeT-ty
|
|
98
|
+
|
|
99
|
+
## 6.2.0-dev.23 (2026-06-18)
|
|
100
|
+
|
|
101
|
+
This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
|
|
102
|
+
|
|
103
|
+
## 6.2.0-dev.21 (2026-06-17)
|
|
104
|
+
|
|
105
|
+
### 🚀 Features
|
|
106
|
+
|
|
107
|
+
- use shadcn type theming class names ([#2720](https://github.com/powerhouse-inc/powerhouse/pull/2720))
|
|
108
|
+
|
|
109
|
+
### 🔥 Performance
|
|
110
|
+
|
|
111
|
+
- **connect:** serve Connect + heavy deps from a prebuilt vendor in dev (opt-in) ([8c530ad4a](https://github.com/powerhouse-inc/powerhouse/commit/8c530ad4a))
|
|
112
|
+
|
|
113
|
+
### ❤️ Thank You
|
|
114
|
+
|
|
115
|
+
- acaldas
|
|
116
|
+
- Ryan Wolhuter @ryanwolhuter
|
|
117
|
+
|
|
118
|
+
## 6.2.0-dev.20 (2026-06-17)
|
|
119
|
+
|
|
120
|
+
This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
|
|
121
|
+
|
|
122
|
+
## 6.2.0-dev.19 (2026-06-16)
|
|
123
|
+
|
|
124
|
+
### 🩹 Fixes
|
|
125
|
+
|
|
126
|
+
- allow UPPER_SNAKE_CASE ([5cd154fb7](https://github.com/powerhouse-inc/powerhouse/commit/5cd154fb7))
|
|
127
|
+
|
|
128
|
+
### ❤️ Thank You
|
|
129
|
+
|
|
130
|
+
- Benjamin Jordan
|
|
131
|
+
|
|
132
|
+
## 6.2.0-dev.18 (2026-06-16)
|
|
133
|
+
|
|
134
|
+
### 🩹 Fixes
|
|
135
|
+
|
|
136
|
+
- **switchboard:** load reactor-api vite loader lazily ([05c966791](https://github.com/powerhouse-inc/powerhouse/commit/05c966791))
|
|
137
|
+
|
|
138
|
+
### ❤️ Thank You
|
|
139
|
+
|
|
140
|
+
- acaldas
|
|
141
|
+
|
|
142
|
+
## 6.2.0-dev.17 (2026-06-16)
|
|
143
|
+
|
|
144
|
+
This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
|
|
145
|
+
|
|
146
|
+
## 6.2.0-dev.16 (2026-06-15)
|
|
147
|
+
|
|
148
|
+
This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
|
|
149
|
+
|
|
150
|
+
## 6.2.0-dev.15 (2026-06-15)
|
|
151
|
+
|
|
152
|
+
This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
|
|
153
|
+
|
|
154
|
+
## 6.2.0-dev.14 (2026-06-14)
|
|
155
|
+
|
|
156
|
+
This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
|
|
157
|
+
|
|
158
|
+
## 6.2.0-dev.13 (2026-06-13)
|
|
159
|
+
|
|
160
|
+
### 🩹 Fixes
|
|
161
|
+
|
|
162
|
+
- very simple ttl cache on renown credentials inside the reactor-api, full fix is grpc s2s call and/or shared cache ([dbf3d698c](https://github.com/powerhouse-inc/powerhouse/commit/dbf3d698c))
|
|
163
|
+
|
|
164
|
+
### ❤️ Thank You
|
|
165
|
+
|
|
166
|
+
- Benjamin Jordan
|
|
167
|
+
|
|
168
|
+
## 6.2.0-dev.12 (2026-06-12)
|
|
169
|
+
|
|
170
|
+
This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
|
|
171
|
+
|
|
172
|
+
## 6.2.0-dev.11 (2026-06-12)
|
|
173
|
+
|
|
174
|
+
### 🚀 Features
|
|
175
|
+
|
|
176
|
+
- **connect:** move theme toggle into settings nav ([7622718a7](https://github.com/powerhouse-inc/powerhouse/commit/7622718a7))
|
|
177
|
+
|
|
178
|
+
### 🩹 Fixes
|
|
179
|
+
|
|
180
|
+
- build fix because of vite incompatibility ([4dc94747e](https://github.com/powerhouse-inc/powerhouse/commit/4dc94747e))
|
|
181
|
+
- kysely types were getting embedded in type defs and not matching later on because typescript is a broken and terrible language ([b5947709f](https://github.com/powerhouse-inc/powerhouse/commit/b5947709f))
|
|
182
|
+
|
|
183
|
+
### ❤️ Thank You
|
|
184
|
+
|
|
185
|
+
- Benjamin Jordan
|
|
186
|
+
- CallmeT-ty @CallmeT-ty
|
|
187
|
+
- Claude Sonnet 4.6
|
|
188
|
+
|
|
189
|
+
## 6.2.0-dev.10 (2026-06-11)
|
|
190
|
+
|
|
191
|
+
### 🩹 Fixes
|
|
192
|
+
|
|
193
|
+
- **connect:** make PH_CONNECT_CONFIG_JSON overrides win over baked runtime-config defaults ([145a3d423](https://github.com/powerhouse-inc/powerhouse/commit/145a3d423))
|
|
194
|
+
|
|
195
|
+
### ❤️ Thank You
|
|
196
|
+
|
|
197
|
+
- Guillermo Puente @gpuente
|
|
198
|
+
|
|
199
|
+
## 6.2.0-dev.9 (2026-06-11)
|
|
200
|
+
|
|
201
|
+
### 🚀 Features
|
|
202
|
+
|
|
203
|
+
- **switchboard:** add errors-only sentry mode via env gate ([c29bb21ee](https://github.com/powerhouse-inc/powerhouse/commit/c29bb21ee))
|
|
204
|
+
|
|
205
|
+
### 🩹 Fixes
|
|
206
|
+
|
|
207
|
+
- **connect:** stop nginx root-file regex from hijacking /assets at default base path ([f72fe2fe5](https://github.com/powerhouse-inc/powerhouse/commit/f72fe2fe5))
|
|
208
|
+
- **switchboard:** stop tracing background DB polls + align @sentry versions ([c5b307333](https://github.com/powerhouse-inc/powerhouse/commit/c5b307333))
|
|
209
|
+
|
|
210
|
+
### ❤️ Thank You
|
|
211
|
+
|
|
212
|
+
- Frank Pfeift
|
|
213
|
+
- Guillermo Puente @gpuente
|
|
214
|
+
|
|
1
215
|
## 6.2.0-dev.8 (2026-06-11)
|
|
2
216
|
|
|
3
217
|
This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
|
|
@@ -176,7 +390,7 @@ This was a version bump only for @powerhousedao/switchboard to align it with oth
|
|
|
176
390
|
|
|
177
391
|
### 🩹 Fixes
|
|
178
392
|
|
|
179
|
-
- **connect:** resolve @powerhousedao/connect
|
|
393
|
+
- **connect:** resolve @powerhousedao/connect/\* aliases in vitest ([f7de787c6](https://github.com/powerhouse-inc/powerhouse/commit/f7de787c6))
|
|
180
394
|
- pnpm-lock file ([d5019827c](https://github.com/powerhouse-inc/powerhouse/commit/d5019827c))
|
|
181
395
|
|
|
182
396
|
### ❤️ Thank You
|
|
@@ -484,7 +698,7 @@ This was a version bump only for @powerhousedao/switchboard to align it with oth
|
|
|
484
698
|
|
|
485
699
|
### 🩹 Fixes
|
|
486
700
|
|
|
487
|
-
- **ci:** pnpm 11 reads
|
|
701
|
+
- **ci:** pnpm 11 reads PNPM*CONFIG*_ not NPM*CONFIG*_ ([b6c05fb23](https://github.com/powerhouse-inc/powerhouse/commit/b6c05fb23))
|
|
488
702
|
- **ci, docker:** pnpm 11 uses pnpm-workspace.yaml for allowBuilds; env var for min-release-age ([37c04c28a](https://github.com/powerhouse-inc/powerhouse/commit/37c04c28a))
|
|
489
703
|
|
|
490
704
|
### ❤️ Thank You
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
4
|
-
import { a as parseForcePgVersion, r as startSwitchboard } from "./server-
|
|
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]="5a65c537-fadb-5705-95c0-4cec44d7b4d2")}catch(e){}}();
|
|
4
|
+
import { a as parseForcePgVersion, r as startSwitchboard } from "./server-WpAvDbVc.mjs";
|
|
5
5
|
import "./utils-Baw7rThP.mjs";
|
|
6
6
|
import { metrics } from "@opentelemetry/api";
|
|
7
7
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
@@ -9,6 +9,7 @@ import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
|
|
|
9
9
|
import { GraphQLInstrumentation } from "@opentelemetry/instrumentation-graphql";
|
|
10
10
|
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
|
|
11
11
|
import { PgInstrumentation } from "@opentelemetry/instrumentation-pg";
|
|
12
|
+
import { UndiciInstrumentation } from "@opentelemetry/instrumentation-undici";
|
|
12
13
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
13
14
|
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
14
15
|
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
@@ -50,6 +51,7 @@ const TENANT_ID = process.env.TENANT_ID || "default";
|
|
|
50
51
|
const DEPLOY_ENV = process.env.NODE_ENV || "development";
|
|
51
52
|
const TEMPO_ENDPOINT = process.env.TEMPO_ENDPOINT;
|
|
52
53
|
const SENTRY_DSN = process.env.SENTRY_DSN;
|
|
54
|
+
const SENTRY_TRACING_TO_SENTRY = Boolean(SENTRY_DSN) && process.env.SENTRY_TRACING_ENABLED !== "false";
|
|
53
55
|
const TRACING_REQUESTED = process.env.ENABLE_TRACING === "true" || process.env.NODE_ENV === "production";
|
|
54
56
|
const HAS_TRACE_DESTINATION = Boolean(TEMPO_ENDPOINT) || Boolean(SENTRY_DSN);
|
|
55
57
|
const TRACING_ENABLED = TRACING_REQUESTED && HAS_TRACE_DESTINATION;
|
|
@@ -61,7 +63,7 @@ if (SENTRY_DSN) {
|
|
|
61
63
|
dsn: SENTRY_DSN,
|
|
62
64
|
environment: process.env.SENTRY_ENV,
|
|
63
65
|
release: process.env.SENTRY_RELEASE || (process.env.npm_package_version ? `v${process.env.npm_package_version}` : void 0),
|
|
64
|
-
tracesSampleRate: SENTRY_TRACES_SAMPLE_RATE,
|
|
66
|
+
tracesSampleRate: SENTRY_TRACING_TO_SENTRY ? SENTRY_TRACES_SAMPLE_RATE : 0,
|
|
65
67
|
skipOpenTelemetrySetup: TRACING_ENABLED
|
|
66
68
|
});
|
|
67
69
|
}
|
|
@@ -85,11 +87,11 @@ if (TRACING_ENABLED) {
|
|
|
85
87
|
});
|
|
86
88
|
const spanProcessors = [];
|
|
87
89
|
if (TEMPO_ENDPOINT) spanProcessors.push(new BatchSpanProcessor(new OTLPTraceExporter({ url: TEMPO_ENDPOINT })));
|
|
88
|
-
if (
|
|
90
|
+
if (SENTRY_TRACING_TO_SENTRY) spanProcessors.push(new SentrySpanProcessor());
|
|
89
91
|
sdk = new NodeSDK({
|
|
90
92
|
resource,
|
|
91
93
|
spanProcessors,
|
|
92
|
-
textMapPropagator:
|
|
94
|
+
textMapPropagator: SENTRY_TRACING_TO_SENTRY ? new SentryPropagator() : void 0,
|
|
93
95
|
instrumentations: [
|
|
94
96
|
new HttpInstrumentation({
|
|
95
97
|
ignoreIncomingRequestHook: (req) => req.url === "/health" || req.url === "/ready",
|
|
@@ -105,15 +107,19 @@ if (TRACING_ENABLED) {
|
|
|
105
107
|
new ExpressInstrumentation({ requestHook: (span, info) => {
|
|
106
108
|
if (info.route) span.setAttribute("http.route", info.route);
|
|
107
109
|
} }),
|
|
110
|
+
new UndiciInstrumentation(),
|
|
108
111
|
new GraphQLInstrumentation({
|
|
109
112
|
mergeItems: true,
|
|
110
113
|
allowValues: true
|
|
111
114
|
}),
|
|
112
|
-
new PgInstrumentation({
|
|
115
|
+
new PgInstrumentation({
|
|
116
|
+
enhancedDatabaseReporting: true,
|
|
117
|
+
requireParentSpan: true
|
|
118
|
+
})
|
|
113
119
|
]
|
|
114
120
|
});
|
|
115
121
|
sdk.start();
|
|
116
|
-
if (
|
|
122
|
+
if (SENTRY_TRACING_TO_SENTRY && typeof Sentry.validateOpenTelemetrySetup === "function") Sentry.validateOpenTelemetrySetup();
|
|
117
123
|
logger$1.info("OpenTelemetry tracing initialized");
|
|
118
124
|
}
|
|
119
125
|
async function shutdown() {
|
|
@@ -221,4 +227,4 @@ startSwitchboard({
|
|
|
221
227
|
export {};
|
|
222
228
|
|
|
223
229
|
//# sourceMappingURL=index.mjs.map
|
|
224
|
-
//# debugId=
|
|
230
|
+
//# debugId=5a65c537-fadb-5705-95c0-4cec44d7b4d2
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../src/metrics.ts","../src/observability.mts","../src/config.ts","../src/profiler.ts","../src/index.mts"],"sourcesContent":["import { OTLPMetricExporter } from \"@opentelemetry/exporter-metrics-otlp-http\";\nimport { resourceFromAttributes } 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: resourceFromAttributes({\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 { resourceFromAttributes } 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 = resourceFromAttributes({\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":"93967b4b-2ee5-5ba6-9c59-5152ee59080f"}
|
|
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 { resourceFromAttributes } 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: resourceFromAttributes({\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 { UndiciInstrumentation } from \"@opentelemetry/instrumentation-undici\";\nimport { resourceFromAttributes } 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\n// Whether to forward TRANSACTIONS (spans) to Sentry. Errors are always sent\n// when SENTRY_DSN is set — this flag only gates APM/tracing into Sentry.\n// Default on (back-compat). Set SENTRY_TRACING_ENABLED=false for an\n// \"errors-only\" deployment: errors still go to Sentry, traces still go to\n// Tempo, but no transactions hit Sentry (no Kafka/ClickHouse/nodestore\n// cost). This is the recommended mode for tenant workloads at scale —\n// Sentry's value there is error grouping; traces live in Tempo.\nconst SENTRY_TRACING_TO_SENTRY =\n Boolean(SENTRY_DSN) && process.env.SENTRY_TRACING_ENABLED !== \"false\";\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// APM sampling for the Sentry-SDK-managed path (i.e. when TRACING is OFF and\n// @sentry/node runs its own bundled OTel — see skipOpenTelemetrySetup below).\n//\n// IMPORTANT: when TRACING_ENABLED, this value does NOT govern span volume.\n// Our NodeSDK (below) owns the pipeline and is constructed with no explicit\n// `sampler`, so @opentelemetry/sdk-node falls back to buildSamplerFromEnv()\n// and the REAL head-sampling knob is the standard OTEL_TRACES_SAMPLER /\n// OTEL_TRACES_SAMPLER_ARG env (set per-deploy in the k8s chart). That head\n// decision gates spans before any processor runs, so it bounds BOTH the\n// SentrySpanProcessor (→ Sentry transactions) and the Tempo OTLP export.\n// (Wiring @sentry/opentelemetry's SentrySampler here would let this rate\n// drive both backends and make DSC/sample_rand propagation spec-correct, but\n// that only matters for Sentry server-side dynamic sampling — a SaaS feature\n// our self-hosted install doesn't run — so it's intentionally deferred.)\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 // 0 in errors-only mode so even @sentry/node's bundled auto-OTel path\n // (used when TRACING_ENABLED is false) produces no transactions.\n tracesSampleRate: SENTRY_TRACING_TO_SENTRY ? SENTRY_TRACES_SAMPLE_RATE : 0,\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 = resourceFromAttributes({\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_TRACING_TO_SENTRY) {\n // Fan the same OTel spans into Sentry — same trace IDs as Tempo, so\n // Sentry transactions cross-link to traces in Grafana. Skipped in\n // errors-only mode (SENTRY_TRACING_ENABLED=false): spans still flow to\n // Tempo via the BatchSpanProcessor above, just not to Sentry.\n spanProcessors.push(new SentrySpanProcessor());\n }\n\n sdk = new NodeSDK({\n resource,\n spanProcessors,\n textMapPropagator: SENTRY_TRACING_TO_SENTRY\n ? new SentryPropagator()\n : 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 // HttpInstrumentation only patches node:http/https; outbound global\n // fetch() goes through undici and is otherwise untraced — e.g. the\n // per-request Renown credential check in AuthService.verifyCredentialExists.\n new UndiciInstrumentation(),\n new GraphQLInstrumentation({ mergeItems: true, allowValues: true }),\n // requireParentSpan: only trace DB queries that run inside a request\n // (HTTP/GraphQL) span. Parentless queries — the management\n // switchboard's background polling loops (vetra-cloud-observability\n // reconcile @60s + clint pull-worker @15s, each writing\n // environment_pods / clint_runtime_endpoints) — would otherwise each\n // become a standalone root transaction. That volume scales O(tenant\n // count) and was ~70% of all Sentry transactions before this change.\n // Dropping it at the instrumentation layer (no span created at all) is\n // cheaper and cleaner than sampling it away downstream.\n new PgInstrumentation({\n enhancedDatabaseReporting: true,\n requireParentSpan: true,\n }),\n ],\n });\n sdk.start();\n if (\n SENTRY_TRACING_TO_SENTRY &&\n typeof Sentry.validateOpenTelemetrySetup === \"function\"\n ) {\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":"5a65c537-fadb-5705-95c0-4cec44d7b4d2"}
|
|
@@ -1,5 +1,5 @@
|
|
|
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]="
|
|
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]="70a6d766-8ac5-5798-ab44-6be8c35cb7d5")}catch(e){}}();
|
|
3
3
|
import { n as addDefaultReactorDrive, r as isPostgresUrl, t as addDefaultDrive } from "./utils-Baw7rThP.mjs";
|
|
4
4
|
import { register } from "node:module";
|
|
5
5
|
import * as Sentry from "@sentry/node";
|
|
@@ -10,20 +10,19 @@ 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, REACTOR_SCHEMA, ReactorBuilder, ReactorClientBuilder,
|
|
14
|
-
import { AttachmentAlreadyExists, AttachmentNotFound, AttachmentPending, HashMismatch, InvalidAttachmentRef, ReservationNotFound, SizeMismatch, UploadTooLarge, createRemoteAttachmentService } from "@powerhousedao/reactor-attachments";
|
|
13
|
+
import { ChannelScheme, DriveCollectionId, EventBus, REACTOR_SCHEMA, ReactorBuilder, ReactorClientBuilder, parseDriveUrl } from "@powerhousedao/reactor";
|
|
15
14
|
import { HttpPackageLoader, ImportPackageLoader, PackageManagementService, PackagesSubgraph, getUniqueDocumentModels, initializeAndStartAPI } from "@powerhousedao/reactor-api";
|
|
16
15
|
import { httpsHooksPath } from "@powerhousedao/reactor-api/https-hooks";
|
|
17
|
-
import {
|
|
16
|
+
import { AttachmentAlreadyExists, AttachmentNotFound, AttachmentPending, HashMismatch, InvalidAttachmentRef, ReservationNotFound, SizeMismatch, UploadTooLarge, createRemoteAttachmentService } from "@powerhousedao/reactor-attachments";
|
|
18
17
|
import { DriveNodeView, NodeProcessor, ReactorDriveClient, createReactorDriveResolvers, reactorDriveDocumentModelModule, reactorDriveSubgraphTypeDefs } from "@powerhousedao/reactor-drive";
|
|
19
18
|
import { processorFactory } from "@powerhousedao/vetra/processors";
|
|
20
|
-
import { driveDocumentModelModule } from "@powerhousedao/shared/document-drive";
|
|
21
|
-
import { documentModels } from "@powerhousedao/vetra";
|
|
22
19
|
import { Kysely, PostgresDialect } from "kysely";
|
|
23
20
|
import net from "node:net";
|
|
24
21
|
import path$1 from "path";
|
|
25
22
|
import { Pool } from "pg";
|
|
26
23
|
import { Readable } from "node:stream";
|
|
24
|
+
import { driveDocumentModelModule } from "@powerhousedao/shared/document-drive";
|
|
25
|
+
import { documentModels } from "@powerhousedao/vetra";
|
|
27
26
|
import { EnvVarProvider } from "@openfeature/env-var-provider";
|
|
28
27
|
import { OpenFeature } from "@openfeature/server-sdk";
|
|
29
28
|
import { PGliteDialect } from "kysely-pglite-dialect";
|
|
@@ -63,34 +62,6 @@ async function loadPgDump(major) {
|
|
|
63
62
|
return (await import("@electric-sql/pglite-tools/pg_dump")).pgDump;
|
|
64
63
|
}
|
|
65
64
|
//#endregion
|
|
66
|
-
//#region src/builder-defaults.mts
|
|
67
|
-
/**
|
|
68
|
-
* Apply switchboard's standard configuration to a reactor + client builder
|
|
69
|
-
* pair. Each piece is opt-out via the options object; defaults mirror what
|
|
70
|
-
* `startSwitchboard` does when building a reactor itself. Mutates both
|
|
71
|
-
* builders in place.
|
|
72
|
-
*
|
|
73
|
-
* Does NOT touch kysely or read models — callers wire those themselves
|
|
74
|
-
* (see `withKysely` / `withReadModelFactory` on the reactor builder).
|
|
75
|
-
*/
|
|
76
|
-
function applySwitchboardReactorDefaults(reactorBuilder, clientBuilder, options = {}) {
|
|
77
|
-
const baseModels = options.includeBaseModels !== false ? [
|
|
78
|
-
documentModelDocumentModelModule,
|
|
79
|
-
driveDocumentModelModule,
|
|
80
|
-
reactorDriveDocumentModelModule
|
|
81
|
-
] : [];
|
|
82
|
-
const vetraModels = options.includeVetraModels !== false ? documentModels : [];
|
|
83
|
-
const extra = options.documentModels ?? [];
|
|
84
|
-
if (baseModels.length || vetraModels.length || extra.length) reactorBuilder.withDocumentModels(getUniqueDocumentModels(baseModels, vetraModels, extra));
|
|
85
|
-
const scheme = options.channelScheme === void 0 ? ChannelScheme.SWITCHBOARD : options.channelScheme;
|
|
86
|
-
if (scheme !== false) reactorBuilder.withChannelScheme(scheme);
|
|
87
|
-
if (options.signalHandlers !== false) reactorBuilder.withSignalHandlers();
|
|
88
|
-
if (options.executorConfig?.maxSkipThreshold !== void 0) reactorBuilder.withExecutorConfig({ maxSkipThreshold: options.executorConfig.maxSkipThreshold });
|
|
89
|
-
if (options.documentModelLoader) reactorBuilder.withDocumentModelLoader(options.documentModelLoader);
|
|
90
|
-
if (options.logger) reactorBuilder.withLogger(options.logger);
|
|
91
|
-
if (options.signer) clientBuilder.withSigner(options.signer);
|
|
92
|
-
}
|
|
93
|
-
//#endregion
|
|
94
65
|
//#region src/attachments/auth.ts
|
|
95
66
|
/**
|
|
96
67
|
* Wrap a Node-style handler so that, when `authService` is provided and auth is
|
|
@@ -413,6 +384,34 @@ function registerAttachmentRoutes(api) {
|
|
|
413
384
|
mountAuthenticatedNodeRoute(api, "GET", "/attachments/:hash", makeDownloadHandler(attachments));
|
|
414
385
|
}
|
|
415
386
|
//#endregion
|
|
387
|
+
//#region src/builder-defaults.mts
|
|
388
|
+
/**
|
|
389
|
+
* Apply switchboard's standard configuration to a reactor + client builder
|
|
390
|
+
* pair. Each piece is opt-out via the options object; defaults mirror what
|
|
391
|
+
* `startSwitchboard` does when building a reactor itself. Mutates both
|
|
392
|
+
* builders in place.
|
|
393
|
+
*
|
|
394
|
+
* Does NOT touch kysely or read models — callers wire those themselves
|
|
395
|
+
* (see `withKysely` / `withReadModelFactory` on the reactor builder).
|
|
396
|
+
*/
|
|
397
|
+
function applySwitchboardReactorDefaults(reactorBuilder, clientBuilder, options = {}) {
|
|
398
|
+
const baseModels = options.includeBaseModels !== false ? [
|
|
399
|
+
documentModelDocumentModelModule,
|
|
400
|
+
driveDocumentModelModule,
|
|
401
|
+
reactorDriveDocumentModelModule
|
|
402
|
+
] : [];
|
|
403
|
+
const vetraModels = options.includeVetraModels !== false ? documentModels : [];
|
|
404
|
+
const extra = options.documentModels ?? [];
|
|
405
|
+
if (baseModels.length || vetraModels.length || extra.length) reactorBuilder.withDocumentModels(getUniqueDocumentModels(baseModels, vetraModels, extra));
|
|
406
|
+
const scheme = options.channelScheme === void 0 ? ChannelScheme.SWITCHBOARD : options.channelScheme;
|
|
407
|
+
if (scheme !== false) reactorBuilder.withChannelScheme(scheme);
|
|
408
|
+
if (options.signalHandlers !== false) reactorBuilder.withSignalHandlers();
|
|
409
|
+
if (options.executorConfig?.maxSkipThreshold !== void 0) reactorBuilder.withExecutorConfig({ maxSkipThreshold: options.executorConfig.maxSkipThreshold });
|
|
410
|
+
if (options.documentModelLoader) reactorBuilder.withDocumentModelLoader(options.documentModelLoader);
|
|
411
|
+
if (options.logger) reactorBuilder.withLogger(options.logger);
|
|
412
|
+
if (options.signer) clientBuilder.withSigner(options.signer);
|
|
413
|
+
}
|
|
414
|
+
//#endregion
|
|
416
415
|
//#region src/feature-flags.ts
|
|
417
416
|
async function initFeatureFlags() {
|
|
418
417
|
const provider = new EnvVarProvider();
|
|
@@ -769,11 +768,16 @@ async function initServer(serverPort, options, renown) {
|
|
|
769
768
|
};
|
|
770
769
|
let defaultDriveUrl = void 0;
|
|
771
770
|
const basePath = process.cwd();
|
|
772
|
-
|
|
773
|
-
|
|
771
|
+
let vite;
|
|
772
|
+
let viteLoader;
|
|
773
|
+
if (dev) {
|
|
774
|
+
const { VitePackageLoader, createViteLogger, startViteServer } = await import("@powerhousedao/reactor-api/vite");
|
|
775
|
+
vite = await startViteServer(process.cwd(), createViteLogger(logger));
|
|
776
|
+
viteLoader = VitePackageLoader.build(vite);
|
|
777
|
+
}
|
|
774
778
|
if (!options.disableLocalPackages) packages.push(basePath);
|
|
775
779
|
const packageLoaders = [];
|
|
776
|
-
if (
|
|
780
|
+
if (viteLoader) packageLoaders.push(viteLoader);
|
|
777
781
|
else packageLoaders.push(new ImportPackageLoader());
|
|
778
782
|
if (httpLoader) {
|
|
779
783
|
packageLoaders.push(httpLoader);
|
|
@@ -870,7 +874,7 @@ async function initServer(serverPort, options, renown) {
|
|
|
870
874
|
const parsed = parseDriveUrl(remoteDriveUrl);
|
|
871
875
|
driveId = parsed.driveId;
|
|
872
876
|
const remoteName = `remote-drive-${driveId}-${crypto.randomUUID()}`;
|
|
873
|
-
await syncManager.add(remoteName,
|
|
877
|
+
await syncManager.add(remoteName, DriveCollectionId.forDrive(driveId), {
|
|
874
878
|
type: "gql",
|
|
875
879
|
parameters: { url: parsed.graphqlEndpoint }
|
|
876
880
|
});
|
|
@@ -946,5 +950,5 @@ if (import.meta.main) await startSwitchboard();
|
|
|
946
950
|
//#endregion
|
|
947
951
|
export { parseForcePgVersion as a, applySwitchboardReactorDefaults as i, isPortAvailable as n, startSwitchboard as r, deriveAttachmentServiceConfig as t };
|
|
948
952
|
|
|
949
|
-
//# sourceMappingURL=server-
|
|
950
|
-
//# debugId=
|
|
953
|
+
//# sourceMappingURL=server-WpAvDbVc.mjs.map
|
|
954
|
+
//# debugId=70a6d766-8ac5-5798-ab44-6be8c35cb7d5
|