@supalive/core 1.15.0 → 1.17.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.
Files changed (150) hide show
  1. package/dist/index-B2nE1y9E.d.ts +2384 -0
  2. package/dist/index-B2nE1y9E.d.ts.map +1 -0
  3. package/dist/index-B4UxN1ZL.d.ts +2365 -0
  4. package/dist/index-B4UxN1ZL.d.ts.map +1 -0
  5. package/dist/index-B5hAqW3O.d.ts +2402 -0
  6. package/dist/index-B5hAqW3O.d.ts.map +1 -0
  7. package/dist/index-B8JFAyw4.d.ts +2377 -0
  8. package/dist/index-B8JFAyw4.d.ts.map +1 -0
  9. package/dist/index-BI3MfAyI.d.ts +2399 -0
  10. package/dist/index-BI3MfAyI.d.ts.map +1 -0
  11. package/dist/index-BL90lDw7.d.ts +2400 -0
  12. package/dist/index-BL90lDw7.d.ts.map +1 -0
  13. package/dist/index-BvryqYUm.d.ts +2394 -0
  14. package/dist/index-BvryqYUm.d.ts.map +1 -0
  15. package/dist/index-CfcrWLLm.d.ts +2377 -0
  16. package/dist/index-CfcrWLLm.d.ts.map +1 -0
  17. package/dist/index-DYkOBnw_.d.ts +2378 -0
  18. package/dist/index-DYkOBnw_.d.ts.map +1 -0
  19. package/dist/index-DcM2uKpT.d.ts +2393 -0
  20. package/dist/index-DcM2uKpT.d.ts.map +1 -0
  21. package/dist/index-DsvdhOKL.d.ts +2400 -0
  22. package/dist/index-DsvdhOKL.d.ts.map +1 -0
  23. package/dist/index-ggi1DrSG.d.ts +2336 -0
  24. package/dist/index-ggi1DrSG.d.ts.map +1 -0
  25. package/dist/mysql-07ROfbBZ.d.ts +114 -0
  26. package/dist/mysql-07ROfbBZ.d.ts.map +1 -0
  27. package/dist/mysql-BJaQhVGE.d.ts +114 -0
  28. package/dist/mysql-BJaQhVGE.d.ts.map +1 -0
  29. package/dist/mysql-BKs5PTSR.d.ts +114 -0
  30. package/dist/mysql-BKs5PTSR.d.ts.map +1 -0
  31. package/dist/mysql-BTigT6mP.d.ts +114 -0
  32. package/dist/mysql-BTigT6mP.d.ts.map +1 -0
  33. package/dist/mysql-C7Km_lgc.d.ts +114 -0
  34. package/dist/mysql-C7Km_lgc.d.ts.map +1 -0
  35. package/dist/mysql-CXymz1Eu.d.ts +114 -0
  36. package/dist/mysql-CXymz1Eu.d.ts.map +1 -0
  37. package/dist/mysql-ClUK7S2j.js +623 -0
  38. package/dist/mysql-ClUK7S2j.js.map +1 -0
  39. package/dist/mysql-CtpWqOhO.d.ts +114 -0
  40. package/dist/mysql-CtpWqOhO.d.ts.map +1 -0
  41. package/dist/mysql-Cxro4f6T.d.ts +114 -0
  42. package/dist/mysql-Cxro4f6T.d.ts.map +1 -0
  43. package/dist/mysql-DNiYIZCK.d.ts +114 -0
  44. package/dist/mysql-DNiYIZCK.d.ts.map +1 -0
  45. package/dist/mysql-DS1s1c5M.d.ts +114 -0
  46. package/dist/mysql-DS1s1c5M.d.ts.map +1 -0
  47. package/dist/mysql-fTKZ1zaF.js +623 -0
  48. package/dist/mysql-fTKZ1zaF.js.map +1 -0
  49. package/dist/mysql-qTszEKAU.d.ts +114 -0
  50. package/dist/mysql-qTszEKAU.d.ts.map +1 -0
  51. package/dist/mysql-qW13LcGQ.d.ts +114 -0
  52. package/dist/mysql-qW13LcGQ.d.ts.map +1 -0
  53. package/dist/overlap-checker-1TwhkAwy.js +259 -0
  54. package/dist/overlap-checker-1TwhkAwy.js.map +1 -0
  55. package/dist/overlap-checker-CGlxWcyb.js +259 -0
  56. package/dist/overlap-checker-CGlxWcyb.js.map +1 -0
  57. package/dist/postgres-B0fqJe1X.d.ts +118 -0
  58. package/dist/postgres-B0fqJe1X.d.ts.map +1 -0
  59. package/dist/postgres-B5-5vgLJ.d.ts +118 -0
  60. package/dist/postgres-B5-5vgLJ.d.ts.map +1 -0
  61. package/dist/postgres-BATDLPml.d.ts +118 -0
  62. package/dist/postgres-BATDLPml.d.ts.map +1 -0
  63. package/dist/postgres-BUVYjnaT.d.ts +118 -0
  64. package/dist/postgres-BUVYjnaT.d.ts.map +1 -0
  65. package/dist/postgres-Bk_8PFRT.d.ts +118 -0
  66. package/dist/postgres-Bk_8PFRT.d.ts.map +1 -0
  67. package/dist/postgres-Bx79_JEy.d.ts +118 -0
  68. package/dist/postgres-Bx79_JEy.d.ts.map +1 -0
  69. package/dist/postgres-C3w741VH.d.ts +118 -0
  70. package/dist/postgres-C3w741VH.d.ts.map +1 -0
  71. package/dist/postgres-CAZYYgbZ.d.ts +118 -0
  72. package/dist/postgres-CAZYYgbZ.d.ts.map +1 -0
  73. package/dist/postgres-CN03Espg.js +868 -0
  74. package/dist/postgres-CN03Espg.js.map +1 -0
  75. package/dist/postgres-CRPJdZx2.js +868 -0
  76. package/dist/postgres-CRPJdZx2.js.map +1 -0
  77. package/dist/postgres-DCXnKaJr.d.ts +118 -0
  78. package/dist/postgres-DCXnKaJr.d.ts.map +1 -0
  79. package/dist/postgres-DgCYPf0j.d.ts +118 -0
  80. package/dist/postgres-DgCYPf0j.d.ts.map +1 -0
  81. package/dist/postgres-Dnshux6k.d.ts +118 -0
  82. package/dist/postgres-Dnshux6k.d.ts.map +1 -0
  83. package/dist/postgres-bhVzeRf2.d.ts +118 -0
  84. package/dist/postgres-bhVzeRf2.d.ts.map +1 -0
  85. package/dist/query-LJranz0c.js +795 -0
  86. package/dist/query-LJranz0c.js.map +1 -0
  87. package/dist/query-q1ZvKrZO.js +762 -0
  88. package/dist/query-q1ZvKrZO.js.map +1 -0
  89. package/dist/realtime_db-B0Tin87E.js +312 -0
  90. package/dist/realtime_db-B0Tin87E.js.map +1 -0
  91. package/dist/realtime_db-DVhhasJj.js +312 -0
  92. package/dist/realtime_db-DVhhasJj.js.map +1 -0
  93. package/dist/router-Bk0eLfmB.js +285 -0
  94. package/dist/router-Bk0eLfmB.js.map +1 -0
  95. package/dist/router-DP2ThAwh.js.map +1 -1
  96. package/dist/src/client/index.d.ts +2 -2
  97. package/dist/src/client/index.js +95 -20
  98. package/dist/src/client/index.js.map +1 -1
  99. package/dist/src/exports/mysql.d.ts +1 -1
  100. package/dist/src/exports/mysql.js +1 -1
  101. package/dist/src/exports/postgres.d.ts +1 -1
  102. package/dist/src/exports/postgres.js +1 -1
  103. package/dist/src/exports/procedure.d.ts +2 -2
  104. package/dist/src/exports/procedure.js +1 -1
  105. package/dist/src/exports/schema-sql.d.ts +1 -1
  106. package/dist/src/exports/server.d.ts +13 -6
  107. package/dist/src/exports/server.d.ts.map +1 -1
  108. package/dist/src/exports/server.js +802 -252
  109. package/dist/src/exports/server.js.map +1 -1
  110. package/dist/src/exports/sub-manager-worker-entry.js +1 -1
  111. package/dist/src/exports/types.d.ts +2 -2
  112. package/dist/src/exports/types.js +1 -1
  113. package/dist/sub-worker-dispatch-D7jfw6ys.js +826 -0
  114. package/dist/sub-worker-dispatch-D7jfw6ys.js.map +1 -0
  115. package/dist/sub-worker-dispatch-qiSs5Xkm.js +826 -0
  116. package/dist/sub-worker-dispatch-qiSs5Xkm.js.map +1 -0
  117. package/dist/types_client_rpc-CWZmlWmM.js +145 -0
  118. package/dist/types_client_rpc-CWZmlWmM.js.map +1 -0
  119. package/dist/types_client_rpc-D4td5W0Y.js +141 -0
  120. package/dist/types_client_rpc-D4td5W0Y.js.map +1 -0
  121. package/dist/types_server-AXVa_2mF.d.ts +684 -0
  122. package/dist/types_server-AXVa_2mF.d.ts.map +1 -0
  123. package/dist/types_server-BhC19R2a.js.map +1 -1
  124. package/dist/types_server-BmIgDd8d.d.ts +614 -0
  125. package/dist/types_server-BmIgDd8d.d.ts.map +1 -0
  126. package/dist/types_server-C6B9_b1R.d.ts +662 -0
  127. package/dist/types_server-C6B9_b1R.d.ts.map +1 -0
  128. package/dist/types_server-CUPM08l2.d.ts +684 -0
  129. package/dist/types_server-CUPM08l2.d.ts.map +1 -0
  130. package/dist/types_server-CtYRmHdO.d.ts +684 -0
  131. package/dist/types_server-CtYRmHdO.d.ts.map +1 -0
  132. package/dist/types_server-DHRi8bGO.d.ts +684 -0
  133. package/dist/types_server-DHRi8bGO.d.ts.map +1 -0
  134. package/dist/types_server-DRr5yKy1.d.ts +655 -0
  135. package/dist/types_server-DRr5yKy1.d.ts.map +1 -0
  136. package/dist/types_server-DmCbve3w.d.ts +684 -0
  137. package/dist/types_server-DmCbve3w.d.ts.map +1 -0
  138. package/dist/types_server-DoeybfmY.d.ts +684 -0
  139. package/dist/types_server-DoeybfmY.d.ts.map +1 -0
  140. package/dist/types_server-Gs_vSVaS.d.ts +655 -0
  141. package/dist/types_server-Gs_vSVaS.d.ts.map +1 -0
  142. package/dist/types_server-ILUmPFvk.d.ts +614 -0
  143. package/dist/types_server-ILUmPFvk.d.ts.map +1 -0
  144. package/dist/types_server-JK14NE0j.d.ts +684 -0
  145. package/dist/types_server-JK14NE0j.d.ts.map +1 -0
  146. package/dist/types_server-hYv2--1M.d.ts +684 -0
  147. package/dist/types_server-hYv2--1M.d.ts.map +1 -0
  148. package/dist/types_server-kkQJDSjA.d.ts +684 -0
  149. package/dist/types_server-kkQJDSjA.d.ts.map +1 -0
  150. package/package.json +2 -1
@@ -1,11 +1,13 @@
1
1
  import { l as primaryOnlyConn, n as getContextRegistry } from "../../router-DP2ThAwh.js";
2
2
  import { n as stableStringify, r as supaliveStringify, t as groupByToMap } from "../../helper-zdJT5FUc.js";
3
- import { r as evaluateCacheFreshness } from "../../overlap-checker-Kz7B_jsp.js";
4
- import { i as AuthenticationError, o as ClientMessageSchema } from "../../types_client_rpc-ByGwoRCL.js";
3
+ import { s as traceCacheCall } from "../../query-LJranz0c.js";
4
+ import { r as evaluateCacheFreshness } from "../../overlap-checker-1TwhkAwy.js";
5
+ import { i as AuthenticationError, o as ClientMessageSchema } from "../../types_client_rpc-CWZmlWmM.js";
5
6
  import { n as RemoteSubscriptionMessageSchema } from "../../types_server-BhC19R2a.js";
6
- import { n as sleep } from "../../realtime_db-CVze3gM0.js";
7
+ import { n as sleep } from "../../realtime_db-B0Tin87E.js";
8
+ import { C as appInstanceId, D as serializeMapValue, E as logger, S as RetentionWatermark, T as logLevelOf, _ as UnregisterSubscriptionResultSchema, a as bootstrapDbTypes, b as UpdateSubscriptionReadSetResultSchema, c as initCore, d as InvalidateWritesetParamsSchema, f as InvalidateWritesetResultSchema, g as UnregisterSubscriptionParamsSchema, h as RegisterSubscriptionResultSchema, i as DB_QUERY_TIMEOUT_MS, l as initDatabase, m as RegisterSubscriptionParamsSchema, n as SubscriptionWorker, o as createDatabase, p as RegisterSubscriptionBatchResultSchema, r as CORE_VERSION, s as initCacheLayer, t as dispatchWorkerMessage, u as CacheLayer, v as UnregisterSubscriptionsResultSchema, w as isProduction, x as RETENTION_WATERMARK_CHANNEL, y as UpdateSubscriptionReadSetParamsSchema } from "../../sub-worker-dispatch-qiSs5Xkm.js";
7
9
  import { n as resolveReadRouting, t as makeConnRouting } from "../../read-routing-BUcQAwnd.js";
8
- import { C as appInstanceId, D as serializeMapValue, E as logger, S as RetentionWatermark, T as logLevelOf, _ as UnregisterSubscriptionResultSchema, a as bootstrapDbTypes, b as UpdateSubscriptionReadSetResultSchema, c as initCore, d as InvalidateWritesetParamsSchema, f as InvalidateWritesetResultSchema, g as UnregisterSubscriptionParamsSchema, h as RegisterSubscriptionResultSchema, i as DB_QUERY_TIMEOUT_MS, l as initDatabase, m as RegisterSubscriptionParamsSchema, n as SubscriptionWorker, o as createDatabase, p as RegisterSubscriptionBatchResultSchema, r as CORE_VERSION, s as initCacheLayer, t as dispatchWorkerMessage, u as CacheLayer, v as UnregisterSubscriptionsResultSchema, w as isProduction, x as RETENTION_WATERMARK_CHANNEL, y as UpdateSubscriptionReadSetParamsSchema } from "../../sub-worker-dispatch-CC0w_sx0.js";
10
+ import { ROOT_CONTEXT, SpanKind, SpanStatusCode, context, isSpanContextValid, metrics, propagation, trace } from "@opentelemetry/api";
9
11
  import { v4 } from "uuid";
10
12
  import { createServer } from "http";
11
13
  import { WebSocket, WebSocketServer } from "ws";
@@ -72,6 +74,251 @@ async function generateCacheKey(queryName, input, queryIdentity) {
72
74
  return `ck_${await getHashOf(`${queryName}_${typeof input === "string" ? input : stableStringify(input)}_${queryIdentity}`)}`;
73
75
  }
74
76
  //#endregion
77
+ //#region src/observability.ts
78
+ let rootLogger = logger;
79
+ /** Replace the process-wide root logger. Call once at startup, before creating
80
+ * the server, to route Supalive's request/lifecycle logs through your own pino
81
+ * instance. */
82
+ function configureRootLogger(logger) {
83
+ rootLogger = logger;
84
+ }
85
+ /** The current root logger (defaults to the env-configured singleton). */
86
+ function getRootLogger() {
87
+ return rootLogger;
88
+ }
89
+ const CLIENT_ERROR_CODES = /* @__PURE__ */ new Set([
90
+ "NOT_FOUND",
91
+ "BAD_REQUEST",
92
+ "INVALID_MESSAGE",
93
+ "PARSE_ERROR",
94
+ "UNAUTHENTICATED",
95
+ "RATE_LIMITED",
96
+ "METHOD_NOT_ALLOWED",
97
+ "TOO_MANY_CONCURRENT_MUTATIONS",
98
+ "TOO_MANY_CONCURRENT_ACTIONS",
99
+ "SUBSCRIBE_ERROR"
100
+ ]);
101
+ /**
102
+ * The bound observability surface for one server instance: an injectable logger,
103
+ * OTel metric instruments, and the `record()` seam every operation funnels
104
+ * through. Built once in the server constructor and shared with the HTTP RPC
105
+ * handler.
106
+ */
107
+ var Observability = class {
108
+ logger;
109
+ requestLog;
110
+ onEvent;
111
+ requestCounter;
112
+ durationHistogram;
113
+ activeConnections;
114
+ activeSubscriptions;
115
+ tracer;
116
+ constructor(opts = {}) {
117
+ const base = opts.logger ?? getRootLogger();
118
+ this.logger = opts.level ? base.child({}, { level: opts.level }) : base;
119
+ this.requestLog = opts.requestLog ?? "all";
120
+ this.onEvent = opts.onEvent;
121
+ const meterName = opts.meterName ?? "@supalive/core";
122
+ const meter = metrics.getMeter(meterName, opts.meterVersion);
123
+ this.tracer = trace.getTracer(meterName, opts.meterVersion);
124
+ this.requestCounter = meter.createCounter("supalive_requests_total", { description: "Total Supalive operations (WS calls, subscribes, HTTP RPC, jobs)." });
125
+ this.durationHistogram = meter.createHistogram("supalive_request_duration_ms", {
126
+ description: "Operation duration in milliseconds.",
127
+ unit: "ms"
128
+ });
129
+ this.activeConnections = meter.createUpDownCounter("supalive_active_connections", { description: "Currently open WebSocket connections." });
130
+ this.activeSubscriptions = meter.createUpDownCounter("supalive_active_subscriptions", { description: "Currently registered live-query subscriptions on this instance." });
131
+ }
132
+ /** Record one completed operation: metrics + optional onEvent + one log line,
133
+ * and finalize the op's span (status/attributes/exception) when present. */
134
+ record(e) {
135
+ const traceId = this.finalizeSpan(e);
136
+ if (traceId) (e.extra ??= {}).traceId = traceId;
137
+ const attrs = {
138
+ transport: e.transport,
139
+ procedure: e.procedure ?? "",
140
+ kind: e.kind ?? "",
141
+ outcome: e.outcome,
142
+ code: e.code ?? ""
143
+ };
144
+ this.requestCounter.add(1, attrs);
145
+ this.durationHistogram.record(e.durationMs, attrs);
146
+ if (this.onEvent) try {
147
+ this.onEvent(e);
148
+ } catch {}
149
+ if (this.requestLog === "off") return;
150
+ if (e.outcome === "ok") {
151
+ if (this.requestLog !== "all") return;
152
+ this.logger.info(logFields(e), summary(e));
153
+ return;
154
+ }
155
+ const fields = logFields(e);
156
+ if (!e.error && e.code != null && CLIENT_ERROR_CODES.has(e.code)) this.logger.warn(fields, summary(e));
157
+ else this.logger.error({
158
+ ...fields,
159
+ err: e.error
160
+ }, summary(e));
161
+ }
162
+ /**
163
+ * Start a SERVER span for an operation, as a child of `parent` (or the active
164
+ * context). No-op — returns a non-recording span — until a TracerProvider is
165
+ * registered by the app, so this costs nothing when tracing is disabled.
166
+ * Returns the span plus a context that has it active, for `context.with(...)`
167
+ * wrapping so nested handler work / `ctx.span` children attach correctly.
168
+ */
169
+ startSpan(name, opts = {}) {
170
+ const parent = opts.parent ?? context.active();
171
+ const span = this.tracer.startSpan(name, {
172
+ kind: SpanKind.SERVER,
173
+ attributes: {
174
+ ...opts.transport ? { "supalive.transport": opts.transport } : {},
175
+ ...opts.procedure ? { "supalive.procedure": opts.procedure } : {},
176
+ ...opts.kind ? { "supalive.kind": opts.kind } : {}
177
+ }
178
+ }, parent);
179
+ return {
180
+ span,
181
+ context: trace.setSpan(parent, span)
182
+ };
183
+ }
184
+ /**
185
+ * Continue a trace from an inbound carrier (HTTP headers, or a WS message's
186
+ * `trace` field). Returns a context to pass as `startSpan({ parent })`. When
187
+ * no propagator is registered / no trace context is present, returns the root
188
+ * context (a fresh trace).
189
+ */
190
+ extractContext(carrier) {
191
+ return propagation.extract(ROOT_CONTEXT, carrier);
192
+ }
193
+ /** Set the span's final status/attributes from the event, then end it.
194
+ * Returns the (valid) trace id for log correlation, or undefined. */
195
+ finalizeSpan(e) {
196
+ const span = e.span;
197
+ if (!span) return void 0;
198
+ try {
199
+ span.setAttribute("supalive.outcome", e.outcome);
200
+ if (e.code) span.setAttribute("supalive.code", e.code);
201
+ if (e.procedure) span.setAttribute("supalive.procedure", e.procedure);
202
+ if (e.kind) span.setAttribute("supalive.kind", e.kind);
203
+ if (e.outcome === "error") {
204
+ if (e.error instanceof Error) span.recordException(e.error);
205
+ span.setStatus({
206
+ code: SpanStatusCode.ERROR,
207
+ message: e.code
208
+ });
209
+ } else span.setStatus({ code: SpanStatusCode.OK });
210
+ } finally {
211
+ span.end();
212
+ }
213
+ const sc = span.spanContext();
214
+ return isSpanContextValid(sc) ? sc.traceId : void 0;
215
+ }
216
+ /** A request-scoped child logger for `ctx.log`, bound with the given fields
217
+ * (reqId, procedure, and trace id when tracing is on). */
218
+ childLogger(fields) {
219
+ return this.logger.child(fields);
220
+ }
221
+ /**
222
+ * A LAZY request-scoped `ctx.log`. Most handlers never log, and pino's
223
+ * `.child()` (bindings merge + serialization) isn't free — so this defers the
224
+ * child creation until the first `debug/info/warn/error` call and reuses it
225
+ * after. When a handler logs nothing, no child logger is ever allocated; the
226
+ * only per-request cost is this small delegator object. Returns the narrow
227
+ * {@link HandlerLogger} surface the handler context exposes.
228
+ */
229
+ lazyChildLogger(fields) {
230
+ const base = this.logger;
231
+ let child;
232
+ const get = () => child ??= base.child(fields);
233
+ return {
234
+ debug: (...args) => get().debug(...args),
235
+ info: (...args) => get().info(...args),
236
+ warn: (...args) => get().warn(...args),
237
+ error: (...args) => get().error(...args)
238
+ };
239
+ }
240
+ /** The span's trace id, or undefined when tracing is off / the span is
241
+ * non-recording. For binding onto `ctx.log`. */
242
+ traceIdOf(span) {
243
+ if (!span) return void 0;
244
+ const sc = span.spanContext();
245
+ return isSpanContextValid(sc) ? sc.traceId : void 0;
246
+ }
247
+ connectionOpened() {
248
+ this.activeConnections.add(1);
249
+ }
250
+ connectionClosed() {
251
+ this.activeConnections.add(-1);
252
+ }
253
+ subscriptionAdded(n = 1) {
254
+ this.activeSubscriptions.add(n);
255
+ }
256
+ subscriptionRemoved(n = 1) {
257
+ this.activeSubscriptions.add(-n);
258
+ }
259
+ };
260
+ /** Start marker for an operation; pass to {@link elapsedMs} to get its duration. */
261
+ function startTimer() {
262
+ return performance.now();
263
+ }
264
+ function elapsedMs(startedAt) {
265
+ return Math.round((performance.now() - startedAt) * 1e3) / 1e3;
266
+ }
267
+ const coreTracer = trace.getTracer("@supalive/core");
268
+ /**
269
+ * Run a query handler inside a `query <procedureName>` span, nested under
270
+ * `parentContext` (the operation span) or the active context. The span is handed
271
+ * to `run` so it becomes the query handler's `ctx.span` and the parent of any DB
272
+ * spans. Cache HITs never call this (no execution), so a span appearing means a
273
+ * real recompute — including the otherwise-invisible background reactive path,
274
+ * where `parentContext` is omitted and the span is a root.
275
+ *
276
+ * No-op-cheap when tracing is off: the started span is non-recording, so we skip
277
+ * the `context.with` and just run.
278
+ */
279
+ function traceQueryExecution(procedureName, trigger, parentContext, run) {
280
+ const parent = parentContext ?? context.active();
281
+ const span = coreTracer.startSpan(`query ${procedureName}`, { attributes: {
282
+ "supalive.kind": "query",
283
+ "supalive.procedure": procedureName,
284
+ "supalive.trigger": trigger
285
+ } }, parent);
286
+ const exec = async () => {
287
+ try {
288
+ const r = await run(span);
289
+ span.setStatus({ code: SpanStatusCode.OK });
290
+ return r;
291
+ } catch (err) {
292
+ if (err instanceof Error) span.recordException(err);
293
+ span.setStatus({ code: SpanStatusCode.ERROR });
294
+ throw err;
295
+ } finally {
296
+ span.end();
297
+ }
298
+ };
299
+ return span.isRecording() ? context.with(trace.setSpan(parent, span), exec) : exec();
300
+ }
301
+ function logFields(e) {
302
+ const f = {
303
+ op: e.op,
304
+ transport: e.transport,
305
+ outcome: e.outcome,
306
+ durMs: e.durationMs
307
+ };
308
+ if (e.procedure) f.procedure = e.procedure;
309
+ if (e.kind) f.kind = e.kind;
310
+ if (e.code) f.code = e.code;
311
+ if (e.reqId) f.reqId = e.reqId;
312
+ if (e.sessionId) f.sessionId = e.sessionId;
313
+ if (e.userId != null) f.userId = e.userId;
314
+ if (e.extra) Object.assign(f, e.extra);
315
+ return f;
316
+ }
317
+ function summary(e) {
318
+ const label = e.procedure ? `${e.kind ?? e.op} ${e.procedure}` : e.op;
319
+ return `${e.transport} ${label} ${e.outcome}`;
320
+ }
321
+ //#endregion
75
322
  //#region src/server/one-shot-query.ts
76
323
  /**
77
324
  * Execute a one-shot query (WS `call` or HTTP RPC) with the same caching model
@@ -108,14 +355,24 @@ async function generateCacheKey(queryName, input, queryIdentity) {
108
355
  * `ts` the result reflects, which the caller echoes so the client can advance
109
356
  * its high-water.
110
357
  */
111
- async function runCachedQuery(deps, procedure, procedureName, input, serverCtx, lastSnapshotTs, clientMinTs) {
358
+ async function runCachedQuery(deps, handlerObs, procedure, procedureName, input, serverCtx, lastSnapshotTs, clientMinTs) {
112
359
  const queryIdentity = resolveQueryIdentity(procedure, serverCtx, input, deps.getUserId);
113
360
  const cacheKey = await generateCacheKey(procedureName, stableStringify(input), queryIdentity);
114
361
  const routing = await resolveReadRouting(deps.db, lastSnapshotTs, clientMinTs ?? 0n);
115
362
  const readTs = routing.readTs;
116
- const metadata = await deps.cache.getQueryCacheMetaData(cacheKey);
117
- if ((await evaluateCacheFreshness(routing.readImpl, metadata, readTs, deps.getMinRetainedTs())).fresh) {
118
- const cached = await deps.cache.getQueryCacheFor(cacheKey);
363
+ const { metadata, freshness } = await traceCacheCall("freshness", async () => {
364
+ const metadata = await deps.cache.getQueryCacheMetaData(cacheKey);
365
+ return {
366
+ metadata,
367
+ freshness: await evaluateCacheFreshness(routing.readImpl, metadata, readTs, deps.getMinRetainedTs())
368
+ };
369
+ });
370
+ if (freshness.fresh) {
371
+ const cached = await traceCacheCall("get", async (setHit) => {
372
+ const c = await deps.cache.getQueryCacheFor(cacheKey);
373
+ setHit(!!(c && metadata && c.version === metadata.version));
374
+ return c;
375
+ });
119
376
  if (cached && metadata && cached.version === metadata.version) {
120
377
  if (readTs > metadata.lastSnapshotTs) deps.cache.advanceQueryCacheMetadata(cacheKey, {
121
378
  lastSnapshotTs: readTs,
@@ -134,11 +391,16 @@ async function runCachedQuery(deps, procedure, procedureName, input, serverCtx,
134
391
  beginTs: readTs
135
392
  }, async (ctx) => {
136
393
  const fn = procedure.fn;
137
- return fn({
138
- db: ctx,
139
- serverCtx,
140
- ...makeConnRouting(ctx, routing)
141
- }, input);
394
+ return traceQueryExecution(procedureName, "request", void 0, (querySpan) => {
395
+ const qctx = {
396
+ db: ctx,
397
+ serverCtx,
398
+ ...makeConnRouting(ctx, routing),
399
+ log: handlerObs.log,
400
+ span: querySpan
401
+ };
402
+ return fn(qctx, input);
403
+ });
142
404
  });
143
405
  await deps.cache.setQueryCacheAndMetadataFor(cacheKey, result.data, {
144
406
  lastSnapshotTs: result.ts,
@@ -152,7 +414,7 @@ async function runCachedQuery(deps, procedure, procedureName, input, serverCtx,
152
414
  }
153
415
  //#endregion
154
416
  //#region src/server/sub-manager-client.ts
155
- const logger$5 = logger.child({ name: "SUBS_MGR_CLIENT" }, { level: logLevelOf("SUB_MANAGER_CLIENT_LOG_LEVEL") });
417
+ const logger$3 = logger.child({ name: "SUBS_MGR_CLIENT" }, { level: logLevelOf("SUB_MANAGER_CLIENT_LOG_LEVEL") });
156
418
  const subscriptionManagerDataPack = {
157
419
  encode: (data) => supaliveStringify(data),
158
420
  decode: (data) => JSON.parse(data)
@@ -209,7 +471,7 @@ var SubManagerClient = class {
209
471
  this.client.dataPack = subscriptionManagerDataPack;
210
472
  this.client.on("open", this.handleOpen.bind(this));
211
473
  this.client.on("close", this.handleClose.bind(this));
212
- this.client.on("error", (err) => logger$5.warn(err, "sub-manager client socket error"));
474
+ this.client.on("error", (err) => logger$3.warn(err, "sub-manager client socket error"));
213
475
  }
214
476
  /**
215
477
  * Wire up the app-server-driven recovery. Must be called before any
@@ -270,7 +532,7 @@ var SubManagerClient = class {
270
532
  appendToBuffer(writeSet) {
271
533
  if (this.bufferedWriteSets.length + writeSet.length > this.bufferLimit) {
272
534
  if (!this.bufferDroppedDuringOutage) {
273
- logger$5.warn(`Invalidate buffer would exceed ${this.bufferLimit}; dropping buffered entries. Subscriptions will be caught up via commit-log replay on re-register.`);
535
+ logger$3.warn(`Invalidate buffer would exceed ${this.bufferLimit}; dropping buffered entries. Subscriptions will be caught up via commit-log replay on re-register.`);
274
536
  this.bufferDroppedDuringOutage = true;
275
537
  }
276
538
  this.bufferedWriteSets = [];
@@ -287,7 +549,7 @@ var SubManagerClient = class {
287
549
  const result = await this.call("invalidateWriteset", { writeSet }, InvalidateWritesetResultSchema);
288
550
  if (this.affectedHandler && result.affected.length > 0) await this.affectedHandler(result.affected);
289
551
  } catch (err) {
290
- logger$5.error(err, "Failed to flush invalidate buffer after recovery");
552
+ logger$3.error(err, "Failed to flush invalidate buffer after recovery");
291
553
  }
292
554
  }
293
555
  markBusy() {
@@ -303,28 +565,28 @@ var SubManagerClient = class {
303
565
  this.resolveReady();
304
566
  }
305
567
  handleClose() {
306
- logger$5.info("sub-manager client: socket closed");
568
+ logger$3.info("sub-manager client: socket closed");
307
569
  this.markBusy();
308
570
  }
309
571
  handleOpen() {
310
572
  if (!this.hasConnectedOnce) {
311
573
  this.hasConnectedOnce = true;
312
- logger$5.info("sub-manager client: initial connect");
574
+ logger$3.info("sub-manager client: initial connect");
313
575
  this.markReady();
314
576
  return;
315
577
  }
316
- logger$5.info("sub-manager client: reconnected — starting recovery");
578
+ logger$3.info("sub-manager client: reconnected — starting recovery");
317
579
  this.runRecovery();
318
580
  }
319
581
  async runRecovery() {
320
582
  try {
321
583
  if (this.recoveryDriver) await this.recoveryDriver();
322
- else logger$5.warn("sub-manager client: no recoveryDriver configured — skipping recovery step");
584
+ else logger$3.warn("sub-manager client: no recoveryDriver configured — skipping recovery step");
323
585
  await this.flushBuffer();
324
586
  } catch (err) {
325
- logger$5.error(err, "Recovery failed; state will be marked ready anyway");
587
+ logger$3.error(err, "Recovery failed; state will be marked ready anyway");
326
588
  } finally {
327
- logger$5.info("sub-manager client: recovery complete");
589
+ logger$3.info("sub-manager client: recovery complete");
328
590
  this.markReady();
329
591
  }
330
592
  }
@@ -342,7 +604,7 @@ var SubManagerClient = class {
342
604
  };
343
605
  //#endregion
344
606
  //#region src/jobs/scheduler.ts
345
- const logger$4 = logger.child({ name: "SCHEDULER" }, { level: logLevelOf("SCHEDULER_LOG_LEVEL") });
607
+ const logger$2 = logger.child({ name: "SCHEDULER" }, { level: logLevelOf("SCHEDULER_LOG_LEVEL") });
346
608
  function delayMsFrom(opts) {
347
609
  if (opts?.runAt) return Math.max(0, opts.runAt.getTime() - Date.now());
348
610
  if (opts?.delaySeconds && opts.delaySeconds > 0) return opts.delaySeconds * 1e3;
@@ -372,18 +634,18 @@ var DevScheduler = class {
372
634
  for (const job of jobs) {
373
635
  const endpoint = endpointFor(job.name);
374
636
  const timer = setInterval(() => {
375
- this.post(endpoint, {}).catch((err) => logger$4.error(err, `dev recurring job "${job.name}" failed`));
637
+ this.post(endpoint, {}).catch((err) => logger$2.error(err, `dev recurring job "${job.name}" failed`));
376
638
  }, this.intervalMs);
377
639
  timer.unref?.();
378
640
  this.intervals.set(job.name, timer);
379
- logger$4.info(`dev schedule "${job.name}" (cron "${job.cron}") → firing every ${this.intervalMs}ms`);
641
+ logger$2.info(`dev schedule "${job.name}" (cron "${job.cron}") → firing every ${this.intervalMs}ms`);
380
642
  }
381
643
  }
382
644
  async enqueue(name, body, endpoint, opts) {
383
645
  const id = crypto.randomUUID();
384
646
  const timer = setTimeout(() => {
385
647
  this.timeouts.delete(id);
386
- this.post(endpoint, body).catch((err) => logger$4.error(err, `dev one-shot job "${name}" failed`));
648
+ this.post(endpoint, body).catch((err) => logger$2.error(err, `dev one-shot job "${name}" failed`));
387
649
  }, delayMsFrom(opts));
388
650
  timer.unref?.();
389
651
  this.timeouts.set(id, timer);
@@ -441,7 +703,7 @@ var QStashScheduler = class {
441
703
  url: req.url
442
704
  });
443
705
  } catch (err) {
444
- logger$4.warn(err, `signature verification failed for job "${req.name}"`);
706
+ logger$2.warn(err, `signature verification failed for job "${req.name}"`);
445
707
  return false;
446
708
  }
447
709
  }
@@ -451,15 +713,15 @@ var QStashScheduler = class {
451
713
  try {
452
714
  existing = await this.client.schedules.list();
453
715
  } catch (err) {
454
- logger$4.error(err, "failed to list QStash schedules; skipping reconcile of stale schedules");
716
+ logger$2.error(err, "failed to list QStash schedules; skipping reconcile of stale schedules");
455
717
  }
456
718
  for (const schedule of existing) {
457
719
  const id = schedule.scheduleId;
458
720
  if (id?.startsWith(SCHEDULE_ID_PREFIX) && !desired.has(id)) try {
459
721
  await this.client.schedules.delete(id);
460
- logger$4.info(`removed stale QStash schedule ${id}`);
722
+ logger$2.info(`removed stale QStash schedule ${id}`);
461
723
  } catch (err) {
462
- logger$4.error(err, `failed to remove stale schedule ${id}`);
724
+ logger$2.error(err, `failed to remove stale schedule ${id}`);
463
725
  }
464
726
  }
465
727
  for (const [scheduleId, job] of desired) try {
@@ -468,9 +730,9 @@ var QStashScheduler = class {
468
730
  destination: endpointFor(job.name),
469
731
  cron: job.cron
470
732
  });
471
- logger$4.info(`synced QStash schedule "${job.name}" (${job.cron})`);
733
+ logger$2.info(`synced QStash schedule "${job.name}" (${job.cron})`);
472
734
  } catch (err) {
473
- logger$4.error(err, `failed to sync schedule for job "${job.name}"`);
735
+ logger$2.error(err, `failed to sync schedule for job "${job.name}"`);
474
736
  throw err;
475
737
  }
476
738
  }
@@ -557,7 +819,6 @@ var FixedWindowRateLimiter = class {
557
819
  };
558
820
  //#endregion
559
821
  //#region src/server/http-rpc.ts
560
- const logger$3 = logger.child({ name: "HTTP_RPC" }, { level: logLevelOf("SERVER_LOG_LEVEL") });
561
822
  /**
562
823
  * Handles the one-shot HTTP RPC transport: a single `POST {path}` endpoint for
563
824
  * `query`/`mutation`/`action`. The procedure name travels in the request body
@@ -573,44 +834,87 @@ const logger$3 = logger.child({ name: "HTTP_RPC" }, { level: logLevelOf("SERVER_
573
834
  */
574
835
  var HttpRpcHandler = class {
575
836
  deps;
837
+ /** The RPC endpoint path (always set; claimed even when disabled so a call is
838
+ * explicitly rejected rather than silently falling through). */
576
839
  path;
577
840
  rpc;
841
+ rpcEnabled;
578
842
  rateLimiter;
843
+ /** Job webhook prefix (`{jobPath}/`) — a job POST is `{jobPath}/<jobName>`. */
844
+ jobPrefix;
579
845
  constructor(deps) {
580
846
  this.deps = deps;
581
847
  this.rpc = deps.rpc;
848
+ this.rpcEnabled = deps.rpcEnabled;
582
849
  this.path = normalizeRpcPath(deps.rpc.path ?? "/_rpc");
583
- if (deps.rpc.rateLimit !== false) {
850
+ if (this.rpcEnabled && deps.rpc.rateLimit !== false) {
584
851
  const rl = deps.rpc.rateLimit ?? {};
585
852
  this.rateLimiter = new FixedWindowRateLimiter(rl.windowMs ?? 6e4, rl.max ?? 120);
586
853
  }
854
+ this.jobPrefix = `${deps.jobPath}/`;
587
855
  }
588
- /** True when this handler owns the given request path. The procedure name is
589
- * carried in the body now, so a single exact path is the whole surface. */
856
+ /** True when this handler owns the given request path as an RPC call. The
857
+ * procedure name is carried in the body now, so a single exact path is the
858
+ * whole RPC surface. Claims the path even when disabled so {@link handle}
859
+ * rejects the call explicitly. */
590
860
  handlesPath(path) {
591
861
  return path === this.path;
592
862
  }
863
+ /** True when the path is a job webhook (`{jobPath}/<name>`). */
864
+ handlesJobPath(path) {
865
+ return path.startsWith(this.jobPrefix);
866
+ }
593
867
  stop() {
594
868
  this.rateLimiter?.stop();
595
869
  }
596
870
  async handle(req, res, path) {
871
+ if (!this.rpcEnabled) return this.send(res, 404, {
872
+ success: false,
873
+ error: "not found",
874
+ code: "NOT_FOUND"
875
+ });
597
876
  this.applyCors(req, res);
598
877
  if (req.method === "OPTIONS") {
599
878
  res.writeHead(204);
600
879
  res.end();
601
880
  return;
602
881
  }
603
- if (req.method !== "POST") return this.send(res, 405, {
604
- success: false,
605
- error: "method not allowed",
606
- code: "METHOD_NOT_ALLOWED"
607
- });
882
+ const startedAt = startTimer();
883
+ const reqId = crypto.randomUUID().slice(0, 8);
884
+ let procName;
885
+ let kind;
886
+ let userId;
887
+ let span;
888
+ const record = (outcome, opts) => {
889
+ this.deps.obs.record({
890
+ op: "rpc",
891
+ transport: "http",
892
+ procedure: procName,
893
+ kind,
894
+ outcome,
895
+ durationMs: elapsedMs(startedAt),
896
+ code: opts?.code,
897
+ error: opts?.error,
898
+ reqId,
899
+ userId: userId ?? void 0,
900
+ span
901
+ });
902
+ };
903
+ if (req.method !== "POST") {
904
+ record("error", { code: "METHOD_NOT_ALLOWED" });
905
+ return this.send(res, 405, {
906
+ success: false,
907
+ error: "method not allowed",
908
+ code: "METHOD_NOT_ALLOWED"
909
+ });
910
+ }
608
911
  if (this.rateLimiter) {
609
912
  const keyBy = this.rpc.rateLimit ? this.rpc.rateLimit.keyBy : void 0;
610
913
  const key = keyBy ? keyBy(req) : clientIp(req);
611
914
  const rl = this.rateLimiter.check(key);
612
915
  if (!rl.allowed) {
613
916
  res.setHeader("retry-after", String(Math.ceil(rl.retryAfterMs / 1e3)));
917
+ record("error", { code: "RATE_LIMITED" });
614
918
  return this.send(res, 429, {
615
919
  success: false,
616
920
  error: "rate limit exceeded",
@@ -629,44 +933,88 @@ var HttpRpcHandler = class {
629
933
  rawInput = envelope.input ?? {};
630
934
  minTs = typeof envelope.minTs === "string" ? parseMinTs$1(envelope.minTs) : void 0;
631
935
  } catch {
936
+ record("error", { code: "BAD_REQUEST" });
632
937
  return this.send(res, 400, {
633
938
  success: false,
634
939
  error: "invalid request",
635
940
  code: "BAD_REQUEST"
636
941
  });
637
942
  }
943
+ procName = name;
638
944
  const procedure = this.deps.getRegistry()?.getProcedure(name);
639
- if (!procedure || procedure.internal || procedure.type === "job") return this.send(res, 404, {
640
- success: false,
641
- error: "not found",
642
- code: "NOT_FOUND"
643
- });
945
+ if (!procedure || procedure.internal || procedure.type === "job") {
946
+ record("error", { code: "NOT_FOUND" });
947
+ return this.send(res, 404, {
948
+ success: false,
949
+ error: "not found",
950
+ code: "NOT_FOUND"
951
+ });
952
+ }
953
+ kind = procedure.type;
644
954
  const auth = await this.resolveAuth(req);
645
- if (!auth.ok) return this.send(res, 401, {
646
- success: false,
647
- error: auth.message,
648
- code: "UNAUTHENTICATED"
649
- });
955
+ if (!auth.ok) {
956
+ record("error", { code: "UNAUTHENTICATED" });
957
+ return this.send(res, 401, {
958
+ success: false,
959
+ error: auth.message,
960
+ code: "UNAUTHENTICATED"
961
+ });
962
+ }
963
+ userId = this.deps.getUserId?.(auth.ctx);
650
964
  let input;
651
965
  try {
652
- input = procedure.inputSchema.parse(rawInput);
966
+ const parseRes = procedure.inputSchema.safeParse(rawInput);
967
+ if (!parseRes.success) {
968
+ record("error", { code: "BAD_REQUEST" });
969
+ return this.send(res, 400, {
970
+ success: false,
971
+ error: `Invalid input: ${parseRes.error.issues[0]?.message ?? "Unknow error"}`,
972
+ code: "BAD_REQUEST"
973
+ });
974
+ }
975
+ input = parseRes.data;
653
976
  } catch (err) {
654
- logger$3.warn(err, `RPC "${name}" received invalid input`);
977
+ record("error", {
978
+ code: "BAD_REQUEST",
979
+ error: err
980
+ });
655
981
  return this.send(res, 400, {
656
982
  success: false,
657
983
  error: "invalid input",
658
984
  code: "BAD_REQUEST"
659
985
  });
660
986
  }
987
+ const started = this.deps.obs.startSpan(`rpc.${kind} ${name}`, {
988
+ transport: "http",
989
+ procedure: name,
990
+ kind,
991
+ parent: this.deps.obs.extractContext(req.headers)
992
+ });
993
+ span = started.span;
994
+ const traceId = this.deps.obs.traceIdOf(span);
995
+ const handlerObs = {
996
+ log: this.deps.obs.lazyChildLogger({
997
+ reqId,
998
+ procedure: name,
999
+ kind,
1000
+ ...userId != null ? { userId } : {},
1001
+ ...traceId ? { traceId } : {}
1002
+ }),
1003
+ span
1004
+ };
661
1005
  try {
662
- const { data, ts } = await this.runProcedure(procedure, input, auth.ctx, minTs);
1006
+ const { data, ts } = await context.with(started.context, () => this.runProcedure(procedure, input, auth.ctx, minTs, handlerObs));
1007
+ record("ok");
663
1008
  return this.send(res, 200, {
664
1009
  success: true,
665
1010
  data,
666
1011
  ts: ts?.toString()
667
1012
  });
668
1013
  } catch (err) {
669
- logger$3.error(err, `RPC "${name}" failed`);
1014
+ record("error", {
1015
+ code: "EXECUTION_ERROR",
1016
+ error: err
1017
+ });
670
1018
  return this.send(res, 500, {
671
1019
  success: false,
672
1020
  error: err instanceof Error ? err.message : "execution failed",
@@ -674,10 +1022,97 @@ var HttpRpcHandler = class {
674
1022
  });
675
1023
  }
676
1024
  }
677
- async runProcedure(procedure, input, serverCtx, minTs) {
1025
+ /**
1026
+ * Handles a job webhook: `POST {jobPath}/<jobName>`. Kept alongside {@link handle}
1027
+ * so the whole HTTP surface (RPC + jobs) lives in one class. Every job POST is
1028
+ * authenticated by the configured scheduler BEFORE any lookup or work — a forged
1029
+ * POST must not even learn whether a job name exists. Returns 500 on handler
1030
+ * failure so the scheduler retries (jobs must be idempotent).
1031
+ */
1032
+ async handleJob(req, res, path) {
1033
+ if (req.method !== "POST") return this.send(res, 405, { error: "method not allowed" });
1034
+ const name = decodeURIComponent(path.slice(this.jobPrefix.length));
1035
+ if (!name || name.includes("/")) return this.send(res, 404, { error: "unknown job" });
1036
+ if (!this.deps.jobScheduler) return this.send(res, 503, { error: "jobs not enabled" });
1037
+ const startedAt = startTimer();
1038
+ const rawBody = await readBody(req);
1039
+ let verified = false;
1040
+ try {
1041
+ verified = await this.deps.jobScheduler.verify({
1042
+ name,
1043
+ rawBody,
1044
+ headers: lowercaseHeaders(req.headers),
1045
+ url: this.deps.endpointFor(name)
1046
+ });
1047
+ } catch (err) {
1048
+ this.deps.obs.logger.warn(err, `job "${name}" verification threw`);
1049
+ }
1050
+ if (!verified) return this.send(res, 401, { error: "invalid signature" });
1051
+ const procedure = this.deps.getRegistry()?.getProcedure(name);
1052
+ if (!procedure || procedure.type !== "job") return this.send(res, 404, { error: "unknown job" });
1053
+ let input;
1054
+ try {
1055
+ const parsed = rawBody.trim().length > 0 ? JSON.parse(rawBody) : {};
1056
+ input = procedure.inputSchema.parse(parsed);
1057
+ } catch (err) {
1058
+ this.deps.obs.logger.warn(err, `job "${name}" received an invalid payload`);
1059
+ return this.send(res, 400, { error: "invalid payload" });
1060
+ }
1061
+ const started = this.deps.obs.startSpan(`job ${name}`, {
1062
+ transport: "http",
1063
+ procedure: name,
1064
+ kind: "job",
1065
+ parent: this.deps.obs.extractContext(req.headers)
1066
+ });
1067
+ const span = started.span;
1068
+ const traceId = this.deps.obs.traceIdOf(span);
1069
+ const jobLog = this.deps.obs.lazyChildLogger({
1070
+ reqId: span && traceId ? traceId : name,
1071
+ procedure: name,
1072
+ kind: "job",
1073
+ ...traceId ? { traceId } : {}
1074
+ });
1075
+ try {
1076
+ const serverCtx = this.deps.jobContext ? await this.deps.jobContext() : void 0;
1077
+ const fn = procedure.fn;
1078
+ const result = await context.with(started.context, () => fn({
1079
+ db: this.deps.db,
1080
+ serverCtx,
1081
+ log: jobLog,
1082
+ span
1083
+ }, input));
1084
+ this.deps.obs.record({
1085
+ op: "job",
1086
+ transport: "http",
1087
+ procedure: name,
1088
+ kind: "job",
1089
+ outcome: "ok",
1090
+ durationMs: elapsedMs(startedAt),
1091
+ span
1092
+ });
1093
+ return this.send(res, 200, {
1094
+ ok: true,
1095
+ result
1096
+ });
1097
+ } catch (err) {
1098
+ this.deps.obs.record({
1099
+ op: "job",
1100
+ transport: "http",
1101
+ procedure: name,
1102
+ kind: "job",
1103
+ outcome: "error",
1104
+ durationMs: elapsedMs(startedAt),
1105
+ code: "JOB_FAILED",
1106
+ error: err,
1107
+ span
1108
+ });
1109
+ return this.send(res, 500, { error: "job failed" });
1110
+ }
1111
+ }
1112
+ async runProcedure(procedure, input, serverCtx, minTs, handlerObs) {
678
1113
  if (procedure.type === "query") {
679
1114
  const lastSnapshotTs = await this.deps.db.impl.getLatestSnapshotTimestamp();
680
- return runCachedQuery(this.deps.oneShotDeps, procedure, procedure.name, input, serverCtx, lastSnapshotTs, minTs);
1115
+ return runCachedQuery(this.deps.oneShotDeps, handlerObs, procedure, procedure.name, input, serverCtx, lastSnapshotTs, minTs);
681
1116
  }
682
1117
  if (procedure.type === "mutation") {
683
1118
  const res = await this.deps.db.mutation(async (ctx) => {
@@ -685,7 +1120,9 @@ var HttpRpcHandler = class {
685
1120
  return fn({
686
1121
  db: ctx,
687
1122
  serverCtx,
688
- ...primaryOnlyConn
1123
+ ...primaryOnlyConn,
1124
+ log: handlerObs.log,
1125
+ span: handlerObs.span
689
1126
  }, input);
690
1127
  });
691
1128
  return {
@@ -698,7 +1135,9 @@ var HttpRpcHandler = class {
698
1135
  db: this.deps.db,
699
1136
  serverCtx,
700
1137
  storage: this.deps.storage,
701
- scheduler: this.deps.scheduler
1138
+ scheduler: this.deps.scheduler,
1139
+ log: handlerObs.log,
1140
+ span: handlerObs.span
702
1141
  }, input) };
703
1142
  }
704
1143
  async resolveAuth(req) {
@@ -769,6 +1208,11 @@ function defaultHttpAuth(req) {
769
1208
  const match = /^Bearer\s+(.+)$/i.exec(value);
770
1209
  return match ? { token: match[1] } : void 0;
771
1210
  }
1211
+ function lowercaseHeaders(headers) {
1212
+ const out = {};
1213
+ for (const [k, v] of Object.entries(headers)) out[k.toLowerCase()] = Array.isArray(v) ? v.join(",") : v;
1214
+ return out;
1215
+ }
772
1216
  function clientIp(req) {
773
1217
  const xff = req.headers["x-forwarded-for"];
774
1218
  if (xff) {
@@ -840,7 +1284,6 @@ const CLIENT_TIMEOUT_MS = 12e4;
840
1284
  * mutation triggers a new race.
841
1285
  */
842
1286
  const RECOMPUTE_LOCK_TTL_SECONDS = Number(process.env.SUPALIVE_RECOMPUTE_LOCK_TTL_SECONDS ?? 10);
843
- const logger$2 = logger.child({ "name": "SUPALIVE" }, { level: logLevelOf("SERVER_LOG_LEVEL") });
844
1287
  /**
845
1288
  * Supalive WebSocket Server
846
1289
  */
@@ -861,6 +1304,8 @@ var SupaliveWebSocketServer = class {
861
1304
  scheduler;
862
1305
  jobPath;
863
1306
  jobClient;
1307
+ /** Owns the HTTP surface: one-shot RPC (when `config.rpc` is set) AND the job
1308
+ * webhook. Always constructed — jobs work even without RPC enabled. */
864
1309
  rpcHandler;
865
1310
  /** Optional object storage exposed to every procedure as `ctx.storage`. */
866
1311
  storage;
@@ -872,6 +1317,10 @@ var SupaliveWebSocketServer = class {
872
1317
  totalConnections: 0,
873
1318
  activeSubscriptions: 0
874
1319
  };
1320
+ /** Structured logs + OTel metrics + the per-operation `onEvent` seam. */
1321
+ obs;
1322
+ /** The request/lifecycle logger (injectable via `config.observability.logger`). */
1323
+ log;
875
1324
  constructor(config) {
876
1325
  this.config = {
877
1326
  port: 3e3,
@@ -881,6 +1330,15 @@ var SupaliveWebSocketServer = class {
881
1330
  ...config
882
1331
  };
883
1332
  this.instanceId = appInstanceId;
1333
+ this.obs = new Observability({
1334
+ logger: (config.observability?.logger ?? logger).child({ name: "SUPALIVE" }, { level: config.observability?.level ?? logLevelOf("SERVER_LOG_LEVEL") }),
1335
+ level: void 0,
1336
+ requestLog: config.observability?.requestLog,
1337
+ onEvent: config.observability?.onEvent,
1338
+ meterName: config.observability?.meterName,
1339
+ meterVersion: config.observability?.meterVersion
1340
+ });
1341
+ this.log = this.obs.logger;
884
1342
  if (config.subManager) this.subManagerClient = config.subManager;
885
1343
  else if (config.subManagerUrl) this.subManagerClient = new SubManagerClient(config.subManagerUrl);
886
1344
  else throw new Error("either `subManager` (in-process link) or `subManagerUrl` (WebSocket URL) is required");
@@ -890,7 +1348,7 @@ var SupaliveWebSocketServer = class {
890
1348
  this.db = config.database;
891
1349
  this.storage = config.storage;
892
1350
  this.db.setOnAfterCommit((writeSet, commitTs) => {
893
- this.handleMutationCommit(writeSet, commitTs).catch((err) => logger$2.error(err, "post-commit invalidation failed"));
1351
+ this.handleMutationCommit(writeSet, commitTs).catch((err) => this.log.error(err, "post-commit invalidation failed"));
894
1352
  });
895
1353
  this.scheduler = config.scheduler;
896
1354
  this.jobPath = normalizeJobPath(config.jobPath ?? "/_jobs");
@@ -901,20 +1359,27 @@ var SupaliveWebSocketServer = class {
901
1359
  getUserId: config.getUserId,
902
1360
  getMinRetainedTs: () => this.retentionWatermark.get()
903
1361
  };
904
- if (config.rpc) this.rpcHandler = new HttpRpcHandler({
1362
+ this.rpcHandler = new HttpRpcHandler({
905
1363
  getRegistry: () => this.contextRegistry,
906
1364
  db: this.db,
907
1365
  oneShotDeps: this.oneShotDeps,
908
1366
  verifyAuth: config.verifyAuth,
909
- rpc: config.rpc,
1367
+ rpc: config.rpc ?? {},
1368
+ rpcEnabled: config.rpc !== void 0,
910
1369
  storage: this.storage,
911
- scheduler: this.jobClient
1370
+ scheduler: this.jobClient,
1371
+ obs: this.obs,
1372
+ getUserId: config.getUserId,
1373
+ jobScheduler: this.scheduler,
1374
+ jobContext: config.jobContext,
1375
+ jobPath: this.jobPath,
1376
+ endpointFor: (name) => this.endpointFor(name)
912
1377
  });
913
1378
  this.subManagerClient.setRecoveryDriver(this.runSubManagerRecovery.bind(this));
914
1379
  this.subManagerClient.setAffectedHandler(this.runAffectedHandler.bind(this));
915
1380
  this.httpServer = createServer((req, res) => {
916
1381
  this.handleHttpRequest(req, res).catch((err) => {
917
- logger$2.error(err, "HTTP request handler error");
1382
+ this.log.error(err, "HTTP request handler error");
918
1383
  if (!res.headersSent) {
919
1384
  res.writeHead(500, { "content-type": "application/json" });
920
1385
  res.end(JSON.stringify({ error: "internal error" }));
@@ -944,7 +1409,7 @@ var SupaliveWebSocketServer = class {
944
1409
  startSessionHeartbeat(session) {
945
1410
  session.heartbeatTimer = setInterval(() => {
946
1411
  if (Date.now() - session.lastReceivedAt > CLIENT_TIMEOUT_MS) {
947
- logger$2.info(`ws inactivity → terminate session ${session.id}`);
1412
+ this.log.info(`ws inactivity → terminate session ${session.id}`);
948
1413
  try {
949
1414
  session.ws.terminate();
950
1415
  } catch {}
@@ -970,11 +1435,11 @@ var SupaliveWebSocketServer = class {
970
1435
  try {
971
1436
  await this.retentionWatermark.loadFrom(this.db.impl);
972
1437
  } catch (err) {
973
- logger$2.error(err, "failed to load initial retention watermark");
1438
+ this.log.error(err, "failed to load initial retention watermark");
974
1439
  }
975
1440
  await new Promise((resolve) => {
976
1441
  this.httpServer.listen(this.config.port, this.config.host, () => {
977
- logger$2.info(`Server: ${this.instanceId} listening on ${this.config.host}:${this.config.port}`);
1442
+ this.log.info(`Server: ${this.instanceId} listening on ${this.config.host}:${this.config.port}`);
978
1443
  resolve();
979
1444
  });
980
1445
  });
@@ -995,11 +1460,14 @@ var SupaliveWebSocketServer = class {
995
1460
  if (jobs.length === 0) return;
996
1461
  try {
997
1462
  await this.scheduler.syncSchedules(jobs, (name) => this.endpointFor(name));
998
- logger$2.info(`Synced ${jobs.length} recurring job schedule(s)`);
1463
+ this.log.info(`Synced ${jobs.length} recurring job schedule(s)`);
999
1464
  } catch (err) {
1000
- logger$2.error(err, "Failed to sync job schedules");
1465
+ this.log.error(err, "Failed to sync job schedules");
1001
1466
  }
1002
1467
  }
1468
+ getObservability() {
1469
+ return this.obs;
1470
+ }
1003
1471
  /** Absolute URL the scheduler POSTs to for a given job name. */
1004
1472
  endpointFor(name) {
1005
1473
  return `${this.config.publicUrl}${this.jobPath}/${name}`;
@@ -1028,9 +1496,9 @@ var SupaliveWebSocketServer = class {
1028
1496
  async stop() {
1029
1497
  this.wss.close();
1030
1498
  for (const [ws] of this.sessions) ws.close(1001, "Server shutting down");
1031
- this.rpcHandler?.stop();
1499
+ this.rpcHandler.stop();
1032
1500
  this.httpServer.close();
1033
- logger$2.info("Server stopped");
1501
+ this.log.info("Server stopped");
1034
1502
  }
1035
1503
  getStats() {
1036
1504
  return {
@@ -1054,63 +1522,25 @@ var SupaliveWebSocketServer = class {
1054
1522
  status: "ok",
1055
1523
  instanceId: this.instanceId
1056
1524
  });
1057
- if (this.rpcHandler && this.rpcHandler.handlesPath(path)) return this.rpcHandler.handle(req, res, path);
1058
- const jobPrefix = `${this.jobPath}/`;
1059
- if (!path.startsWith(jobPrefix)) return sendJson(res, 404, { error: "not found" });
1060
- if (req.method !== "POST") return sendJson(res, 405, { error: "method not allowed" });
1061
- const name = decodeURIComponent(path.slice(jobPrefix.length));
1062
- if (!name || name.includes("/")) return sendJson(res, 404, { error: "unknown job" });
1063
- if (!this.scheduler) return sendJson(res, 503, { error: "jobs not enabled" });
1064
- const rawBody = await readRequestBody(req);
1065
- let verified = false;
1066
- try {
1067
- verified = await this.scheduler.verify({
1068
- name,
1069
- rawBody,
1070
- headers: lowercaseHeaders(req.headers),
1071
- url: this.endpointFor(name)
1072
- });
1073
- } catch (err) {
1074
- logger$2.warn(err, `job "${name}" verification threw`);
1075
- }
1076
- if (!verified) return sendJson(res, 401, { error: "invalid signature" });
1077
- const procedure = this.contextRegistry?.getProcedure(name);
1078
- if (!procedure || procedure.type !== "job") return sendJson(res, 404, { error: "unknown job" });
1079
- let input;
1080
- try {
1081
- const parsed = rawBody.trim().length > 0 ? JSON.parse(rawBody) : {};
1082
- input = procedure.inputSchema.parse(parsed);
1083
- } catch (err) {
1084
- logger$2.warn(err, `job "${name}" received an invalid payload`);
1085
- return sendJson(res, 400, { error: "invalid payload" });
1086
- }
1087
- try {
1088
- const serverCtx = this.config.jobContext ? await this.config.jobContext() : void 0;
1089
- const fn = procedure.fn;
1090
- const result = await fn({
1091
- db: this.db,
1092
- serverCtx
1093
- }, input);
1094
- logger$2.info(`job "${name}" completed`);
1095
- return sendJson(res, 200, {
1096
- ok: true,
1097
- result
1098
- });
1099
- } catch (err) {
1100
- logger$2.error(err, `job "${name}" failed`);
1101
- return sendJson(res, 500, { error: "job failed" });
1525
+ if (this.rpcHandler.handlesPath(path)) return this.rpcHandler.handle(req, res, path);
1526
+ if (this.rpcHandler.handlesJobPath(path)) return this.rpcHandler.handleJob(req, res, path);
1527
+ if (this.config.onHttpRequest) {
1528
+ if (await this.config.onHttpRequest(req, res)) return;
1102
1529
  }
1530
+ return sendJson(res, 404, { error: "not found" });
1103
1531
  }
1104
1532
  setupWebSocketHandlers() {
1105
1533
  this.wss.on("connection", (ws, req) => {
1106
1534
  this.handleConnection(ws, req).catch((err) => {
1107
- logger$2.error(err, "Connection handler error");
1535
+ this.log.error(err, "Connection handler error");
1108
1536
  });
1109
1537
  });
1110
1538
  }
1111
1539
  async handleConnection(ws, req) {
1112
1540
  this.stats.totalConnections++;
1113
1541
  this.stats.activeConnections++;
1542
+ this.obs.connectionOpened();
1543
+ const connectedAt = startTimer();
1114
1544
  const session = {
1115
1545
  id: v4(),
1116
1546
  ws,
@@ -1123,7 +1553,10 @@ var SupaliveWebSocketServer = class {
1123
1553
  lastSentAt: Date.now(),
1124
1554
  heartbeatTimer: null
1125
1555
  };
1126
- logger$2.info(`New session: ${session.id}`);
1556
+ this.log.info({
1557
+ sessionId: session.id,
1558
+ activeConnections: this.stats.activeConnections
1559
+ }, `New session: ${session.id}`);
1127
1560
  this.sessions.set(ws, session);
1128
1561
  this.sendMessage(session, {
1129
1562
  type: "connected",
@@ -1132,7 +1565,7 @@ var SupaliveWebSocketServer = class {
1132
1565
  ws.on("message", (data) => {
1133
1566
  session.lastReceivedAt = Date.now();
1134
1567
  this.handleMessage(session, data.toString()).catch((err) => {
1135
- logger$2.error(err, "Message handler error");
1568
+ this.log.error(err, "Message handler error");
1136
1569
  });
1137
1570
  });
1138
1571
  ws.on("ping", () => {
@@ -1143,17 +1576,24 @@ var SupaliveWebSocketServer = class {
1143
1576
  });
1144
1577
  this.startSessionHeartbeat(session);
1145
1578
  ws.on("close", (code) => {
1146
- logger$2.info(`Connection closed: ${session.id}, code: ${code}`);
1147
1579
  this.clearSessionTimers(session);
1148
1580
  this.handleDisconnect(session);
1149
1581
  this.sessions.delete(ws);
1150
1582
  this.stats.activeConnections--;
1583
+ this.obs.connectionClosed();
1584
+ this.log.info({
1585
+ sessionId: session.id,
1586
+ code,
1587
+ durMs: elapsedMs(connectedAt),
1588
+ activeConnections: this.stats.activeConnections
1589
+ }, `Connection closed: ${session.id}, code: ${code}`);
1151
1590
  });
1152
1591
  ws.on("error", (err) => {
1153
- logger$2.error(err, "Websocket error");
1592
+ this.log.error(err, "Websocket error");
1154
1593
  });
1155
1594
  }
1156
1595
  async handleMessage(session, message) {
1596
+ const startedAt = startTimer();
1157
1597
  try {
1158
1598
  const rawMsg = JSON.parse(message);
1159
1599
  const parseResult = ClientMessageSchema.safeParse(rawMsg);
@@ -1161,22 +1601,22 @@ var SupaliveWebSocketServer = class {
1161
1601
  this.sendError(session, "", "Invalid message format", "INVALID_MESSAGE");
1162
1602
  return;
1163
1603
  }
1164
- await this.processMessage(session, parseResult.data);
1604
+ await this.processMessage(session, parseResult.data, startedAt);
1165
1605
  } catch (err) {
1166
- logger$2.error(err, "Message handling error");
1606
+ this.log.error(err, "Message handling error");
1167
1607
  this.sendError(session, "", "Parse error", "PARSE_ERROR");
1168
1608
  }
1169
1609
  }
1170
- async processMessage(session, msg) {
1610
+ async processMessage(session, msg, startedAt) {
1171
1611
  switch (msg.type) {
1172
1612
  case "auth":
1173
1613
  await this.handleAuth(session, msg.data);
1174
1614
  break;
1175
1615
  case "call":
1176
- await this.handleCall(session, msg.id, msg.procedure, msg.input, parseMinTs(msg.minTs));
1616
+ await this.handleCall(session, msg.id, msg.procedure, msg.input, startedAt, parseMinTs(msg.minTs), msg.trace);
1177
1617
  break;
1178
1618
  case "subscribe":
1179
- await this.handleSubscribe(session, msg.id, msg.procedure, msg.input);
1619
+ await this.handleSubscribe(session, msg.id, msg.procedure, msg.input, startedAt, msg.trace);
1180
1620
  break;
1181
1621
  case "unsubscribe":
1182
1622
  await this.handleUnsubscribe(session, msg.subId);
@@ -1224,16 +1664,17 @@ var SupaliveWebSocketServer = class {
1224
1664
  * session. Callers (handleCall) own the response/error for their own
1225
1665
  * op via the returned promise.
1226
1666
  */
1227
- enqueueMutation(session, procedure, validatedInput) {
1667
+ enqueueMutation(session, procedure, validatedInput, handlerObs) {
1228
1668
  return new Promise((resolve, reject) => {
1229
1669
  const pending = {
1230
1670
  procedure,
1231
1671
  validatedInput,
1232
1672
  resolve,
1233
- reject
1673
+ reject,
1674
+ handlerObs
1234
1675
  };
1235
1676
  session.mutationQueue.push(pending);
1236
- this.drainMutationQueue(session).catch((err) => logger$2.error(err, "drainMutationQueue crashed"));
1677
+ this.drainMutationQueue(session).catch((err) => this.log.error(err, "drainMutationQueue crashed"));
1237
1678
  });
1238
1679
  }
1239
1680
  /**
@@ -1249,13 +1690,20 @@ var SupaliveWebSocketServer = class {
1249
1690
  const op = session.mutationQueue.shift();
1250
1691
  try {
1251
1692
  const fn = op.procedure.fn;
1252
- const result = await this.db.mutation(async (ctx) => {
1693
+ const runMutation = () => this.db.mutation(async (ctx) => {
1253
1694
  return fn({
1254
1695
  db: ctx,
1255
1696
  serverCtx: session.serverCtx,
1256
- ...primaryOnlyConn
1697
+ ...primaryOnlyConn,
1698
+ log: op.handlerObs?.log ?? this.obs.lazyChildLogger({
1699
+ caller: "internal",
1700
+ procedure: op.procedure.name
1701
+ }),
1702
+ span: op.handlerObs?.span
1257
1703
  }, op.validatedInput);
1258
1704
  });
1705
+ const parentSpan = op.handlerObs?.span;
1706
+ const result = parentSpan?.isRecording() ? await context.with(trace.setSpan(context.active(), parentSpan), runMutation) : await runMutation();
1259
1707
  op.resolve(result);
1260
1708
  } catch (err) {
1261
1709
  op.reject(err);
@@ -1265,81 +1713,181 @@ var SupaliveWebSocketServer = class {
1265
1713
  session.mutationInFlight = false;
1266
1714
  }
1267
1715
  }
1268
- async handleCall(session, id, procedureName, input, minTs) {
1716
+ async handleCall(session, id, procedureName, input, startedAt, minTs, trace) {
1717
+ const userId = this.config.getUserId?.(session.serverCtx) ?? void 0;
1718
+ let span;
1719
+ const record = (outcome, opts) => {
1720
+ this.obs.record({
1721
+ op: "rpc",
1722
+ transport: "ws",
1723
+ procedure: procedureName,
1724
+ kind: opts?.kind,
1725
+ outcome,
1726
+ durationMs: elapsedMs(startedAt),
1727
+ code: opts?.code,
1728
+ error: opts?.error,
1729
+ sessionId: session.id,
1730
+ reqId: id,
1731
+ userId,
1732
+ span
1733
+ });
1734
+ };
1269
1735
  if (!this.router) {
1270
1736
  this.sendError(session, id, "Router not registered", "SERVER_ERROR");
1737
+ record("error", { code: "SERVER_ERROR" });
1271
1738
  return;
1272
1739
  }
1273
1740
  const procedure = this.contextRegistry.getProcedure(procedureName);
1274
1741
  if (!procedure || procedure.type === "job") {
1275
1742
  this.sendError(session, id, `Procedure "${procedureName}" not found`, "NOT_FOUND");
1743
+ record("error", { code: "NOT_FOUND" });
1276
1744
  return;
1277
1745
  }
1278
- try {
1279
- const validatedInput = procedure.inputSchema.parse(input);
1280
- if (procedure.type === "query") {
1281
- const lastSnapshotTs = await this.db.impl.getLatestSnapshotTimestamp();
1282
- const { data, ts } = await runCachedQuery(this.oneShotDeps, procedure, procedureName, validatedInput, session.serverCtx, lastSnapshotTs, minTs);
1283
- this.sendMessage(session, {
1284
- type: "response",
1285
- id,
1286
- success: true,
1287
- data,
1288
- ts: ts.toString()
1289
- });
1290
- } else if (procedure.type === "mutation") {
1291
- if (session.mutationQueue.length >= MAX_MUTATION_QUEUE_BUFFER) {
1292
- this.sendError(session, id, `Too many concurrent mutations on this connection (max ${MAX_MUTATION_QUEUE_BUFFER})`, "TOO_MANY_CONCURRENT_MUTATIONS");
1293
- return;
1294
- }
1295
- const mutationResult = await this.enqueueMutation(session, procedure, validatedInput);
1296
- this.sendMessage(session, {
1297
- type: "response",
1298
- id,
1299
- success: true,
1300
- data: mutationResult.result,
1301
- ts: mutationResult.commitTs.toString()
1302
- });
1303
- } else if (procedure.type === "action") {
1304
- if (session.actionInFlight >= MAX_ACTION_CONCURRENCY) {
1305
- this.sendError(session, id, `Too many concurrent actions on this connection (max ${MAX_ACTION_CONCURRENCY})`, "TOO_MANY_CONCURRENT_ACTIONS");
1306
- return;
1307
- }
1308
- session.actionInFlight++;
1309
- let result;
1310
- try {
1311
- const fn = procedure.fn;
1312
- result = await fn({
1313
- db: this.db,
1314
- serverCtx: session.serverCtx,
1315
- storage: this.storage,
1316
- scheduler: this.jobClient
1317
- }, validatedInput);
1318
- } finally {
1319
- session.actionInFlight--;
1746
+ const kind = procedure.type;
1747
+ const parent = trace ? this.obs.extractContext({ traceparent: trace }) : void 0;
1748
+ const started = this.obs.startSpan(`rpc.${kind} ${procedureName}`, {
1749
+ transport: "ws",
1750
+ procedure: procedureName,
1751
+ kind,
1752
+ parent
1753
+ });
1754
+ span = started.span;
1755
+ const traceId = this.obs.traceIdOf(span);
1756
+ const handlerObs = {
1757
+ log: this.obs.lazyChildLogger({
1758
+ reqId: id,
1759
+ sessionId: session.id,
1760
+ procedure: procedureName,
1761
+ kind,
1762
+ ...userId != null ? { userId } : {},
1763
+ ...traceId ? { traceId } : {}
1764
+ }),
1765
+ span
1766
+ };
1767
+ await context.with(started.context, async () => {
1768
+ try {
1769
+ const validatedInput = procedure.inputSchema.parse(input);
1770
+ if (procedure.type === "query") {
1771
+ const lastSnapshotTs = await this.db.impl.getLatestSnapshotTimestamp();
1772
+ const { data, ts } = await runCachedQuery(this.oneShotDeps, handlerObs, procedure, procedureName, validatedInput, session.serverCtx, lastSnapshotTs, minTs);
1773
+ this.sendMessage(session, {
1774
+ type: "response",
1775
+ id,
1776
+ success: true,
1777
+ data,
1778
+ ts: ts.toString()
1779
+ });
1780
+ record("ok", { kind });
1781
+ } else if (procedure.type === "mutation") {
1782
+ if (session.mutationQueue.length >= MAX_MUTATION_QUEUE_BUFFER) {
1783
+ this.sendError(session, id, `Too many concurrent mutations on this connection (max ${MAX_MUTATION_QUEUE_BUFFER})`, "TOO_MANY_CONCURRENT_MUTATIONS");
1784
+ record("error", {
1785
+ kind,
1786
+ code: "TOO_MANY_CONCURRENT_MUTATIONS"
1787
+ });
1788
+ return;
1789
+ }
1790
+ const mutationResult = await this.enqueueMutation(session, procedure, validatedInput, handlerObs);
1791
+ this.sendMessage(session, {
1792
+ type: "response",
1793
+ id,
1794
+ success: true,
1795
+ data: mutationResult.result,
1796
+ ts: mutationResult.commitTs.toString()
1797
+ });
1798
+ record("ok", { kind });
1799
+ } else if (procedure.type === "action") {
1800
+ if (session.actionInFlight >= MAX_ACTION_CONCURRENCY) {
1801
+ this.sendError(session, id, `Too many concurrent actions on this connection (max ${MAX_ACTION_CONCURRENCY})`, "TOO_MANY_CONCURRENT_ACTIONS");
1802
+ record("error", {
1803
+ kind,
1804
+ code: "TOO_MANY_CONCURRENT_ACTIONS"
1805
+ });
1806
+ return;
1807
+ }
1808
+ session.actionInFlight++;
1809
+ let result;
1810
+ try {
1811
+ const fn = procedure.fn;
1812
+ result = await fn({
1813
+ db: this.db,
1814
+ serverCtx: session.serverCtx,
1815
+ storage: this.storage,
1816
+ scheduler: this.jobClient,
1817
+ log: handlerObs.log,
1818
+ span: handlerObs.span
1819
+ }, validatedInput);
1820
+ } finally {
1821
+ session.actionInFlight--;
1822
+ }
1823
+ this.sendMessage(session, {
1824
+ type: "response",
1825
+ id,
1826
+ success: true,
1827
+ data: result
1828
+ });
1829
+ record("ok", { kind });
1320
1830
  }
1321
- this.sendMessage(session, {
1322
- type: "response",
1323
- id,
1324
- success: true,
1325
- data: result
1831
+ } catch (err) {
1832
+ this.sendError(session, id, err instanceof Error ? err.message : "Execution failed", "EXECUTION_ERROR");
1833
+ record("error", {
1834
+ kind,
1835
+ code: "EXECUTION_ERROR",
1836
+ error: err
1326
1837
  });
1327
1838
  }
1328
- } catch (err) {
1329
- logger$2.error(err, `Error executing "${procedureName}"`);
1330
- this.sendError(session, id, err instanceof Error ? err.message : "Execution failed", "EXECUTION_ERROR");
1331
- }
1839
+ });
1332
1840
  }
1333
- async handleSubscribe(session, id, procedureName, input) {
1841
+ async handleSubscribe(session, id, procedureName, input, startedAt, trace) {
1842
+ const userId = this.config.getUserId?.(session.serverCtx) ?? void 0;
1843
+ let span;
1844
+ const record = (outcome, opts) => {
1845
+ this.obs.record({
1846
+ op: "subscribe",
1847
+ transport: "ws",
1848
+ procedure: procedureName,
1849
+ kind: "subscribe",
1850
+ outcome,
1851
+ durationMs: elapsedMs(startedAt),
1852
+ code: opts?.code,
1853
+ error: opts?.error,
1854
+ sessionId: session.id,
1855
+ reqId: id,
1856
+ userId,
1857
+ span
1858
+ });
1859
+ };
1334
1860
  if (!this.router) {
1335
1861
  this.sendError(session, id, "Router not registered", "SERVER_ERROR");
1862
+ record("error", { code: "SERVER_ERROR" });
1336
1863
  return;
1337
1864
  }
1338
1865
  const procedure = this.contextRegistry.getProcedure(procedureName);
1339
1866
  if (!procedure || procedure.type !== "query") {
1340
1867
  this.sendError(session, id, `Procedure "${procedureName}" not found or not a query`, "NOT_FOUND");
1868
+ record("error", { code: "NOT_FOUND" });
1341
1869
  return;
1342
1870
  }
1871
+ const parent = trace ? this.obs.extractContext({ traceparent: trace }) : void 0;
1872
+ const started = this.obs.startSpan(`subscribe ${procedureName}`, {
1873
+ transport: "ws",
1874
+ procedure: procedureName,
1875
+ kind: "subscribe",
1876
+ parent
1877
+ });
1878
+ span = started.span;
1879
+ const traceId = this.obs.traceIdOf(span);
1880
+ const handlerObs = {
1881
+ log: this.obs.lazyChildLogger({
1882
+ reqId: id,
1883
+ sessionId: session.id,
1884
+ procedure: procedureName,
1885
+ kind: "subscribe",
1886
+ ...userId != null ? { userId } : {},
1887
+ ...traceId ? { traceId } : {}
1888
+ }),
1889
+ span
1890
+ };
1343
1891
  try {
1344
1892
  const validatedInput = procedure.inputSchema.parse(input);
1345
1893
  const queryIdentity = resolveQueryIdentity(procedure, session.serverCtx, validatedInput, this.config.getUserId);
@@ -1359,7 +1907,11 @@ var SupaliveWebSocketServer = class {
1359
1907
  data = cached.data;
1360
1908
  servedTs = cached.lastSnapshotTs;
1361
1909
  } else {
1362
- liveResult = await this.executeLiveQuery(procedureName, validatedInput, session.serverCtx);
1910
+ liveResult = await this.executeLiveQuery(procedureName, validatedInput, session.serverCtx, {
1911
+ log: handlerObs.log,
1912
+ parentContext: started.context,
1913
+ trigger: "subscribe"
1914
+ });
1363
1915
  data = liveResult.data;
1364
1916
  servedTs = liveResult.ts;
1365
1917
  }
@@ -1384,7 +1936,11 @@ var SupaliveWebSocketServer = class {
1384
1936
  }
1385
1937
  }
1386
1938
  if (rebuild) {
1387
- liveResult = await this.executeLiveQuery(procedureName, validatedInput, session.serverCtx);
1939
+ liveResult = await this.executeLiveQuery(procedureName, validatedInput, session.serverCtx, {
1940
+ log: handlerObs.log,
1941
+ parentContext: started.context,
1942
+ trigger: "subscribe"
1943
+ });
1388
1944
  data = liveResult.data;
1389
1945
  servedTs = liveResult.ts;
1390
1946
  }
@@ -1394,6 +1950,7 @@ var SupaliveWebSocketServer = class {
1394
1950
  subscribedTimes: 1
1395
1951
  });
1396
1952
  this.stats.activeSubscriptions++;
1953
+ this.obs.subscriptionAdded();
1397
1954
  }
1398
1955
  if (liveResult) {
1399
1956
  const [updateResult] = await Promise.all([this.subManagerClient.updateSubscriptionReadSet({
@@ -1403,7 +1960,7 @@ var SupaliveWebSocketServer = class {
1403
1960
  readSet: liveResult.readSet,
1404
1961
  instanceName: this.instanceId
1405
1962
  }), this.cache.setQueryCacheFor(cacheKey, data, liveResult.ts)]);
1406
- if (!updateResult.tracked) logger$2.warn(`updateSubscriptionReadSet returned tracked:false immediately after register for ${subId} — possible sub-manager restart mid-subscribe`);
1963
+ if (!updateResult.tracked) this.log.warn(`updateSubscriptionReadSet returned tracked:false immediately after register for ${subId} — possible sub-manager restart mid-subscribe`);
1407
1964
  }
1408
1965
  dataHash = await getHashOf(data);
1409
1966
  this.sendMessage(session, {
@@ -1419,10 +1976,13 @@ var SupaliveWebSocketServer = class {
1419
1976
  dataHash,
1420
1977
  ts: servedTs.toString()
1421
1978
  });
1422
- logger$2.info(`Subscribed: ${subId} for session ${session.id}`);
1979
+ record("ok", { code: void 0 });
1423
1980
  } catch (err) {
1424
- logger$2.error(err, `Error subscribing to "${procedureName}"`);
1425
1981
  this.sendError(session, id, err instanceof Error ? err.message : "Subscribe failed", "SUBSCRIBE_ERROR");
1982
+ record("error", {
1983
+ code: "SUBSCRIBE_ERROR",
1984
+ error: err
1985
+ });
1426
1986
  }
1427
1987
  }
1428
1988
  async handleUnsubscribe(session, subId) {
@@ -1436,19 +1996,24 @@ var SupaliveWebSocketServer = class {
1436
1996
  session.subIds.delete(subId);
1437
1997
  this.serverSubscriptions.delete(subId);
1438
1998
  this.stats.activeSubscriptions--;
1999
+ this.obs.subscriptionRemoved();
1439
2000
  try {
1440
2001
  await this.subManagerClient.unregisterSubscription({
1441
2002
  subId,
1442
2003
  instanceName: this.instanceId
1443
2004
  });
1444
2005
  } catch (err) {
1445
- logger$2.error(err, "Failed to unregister from sub-manager:");
2006
+ this.log.error(err, "Failed to unregister from sub-manager:");
1446
2007
  }
1447
2008
  }
1448
- logger$2.info(`Unsubscribed: ${subId} for session ${session.id}, remaining server subs: ${serverSub.subscribedTimes}`);
2009
+ this.log.debug({
2010
+ sessionId: session.id,
2011
+ subId,
2012
+ remaining: serverSub.subscribedTimes
2013
+ }, `Unsubscribed: ${subId} for session ${session.id}`);
1449
2014
  }
1450
2015
  async handleDisconnect(session) {
1451
- logger$2.info(`Session disconnected: ${session.id}`);
2016
+ this.log.info(`Session disconnected: ${session.id}`);
1452
2017
  let unregisterSubs = void 0;
1453
2018
  for (const subId of session.subIds) {
1454
2019
  const sub = this.serverSubscriptions.get(subId);
@@ -1466,13 +2031,14 @@ var SupaliveWebSocketServer = class {
1466
2031
  instanceName: this.instanceId
1467
2032
  })));
1468
2033
  } catch (err) {
1469
- logger$2.error(err, "Failed to unregister from sub-manager:");
2034
+ this.log.error(err, "Failed to unregister from sub-manager:");
1470
2035
  }
1471
2036
  for (const sub of unregisterSubs) if (sub.subscribedTimes <= 0) {
1472
2037
  this.serverSubscriptions.delete(sub.subId);
1473
2038
  this.stats.activeSubscriptions--;
2039
+ this.obs.subscriptionRemoved();
1474
2040
  }
1475
- logger$2.info(`All Subscription sessions is disconnected: ${session.id}, subs: ${unregisterSubs.length}`);
2041
+ this.log.info(`All Subscription sessions is disconnected: ${session.id}, subs: ${unregisterSubs.length}`);
1476
2042
  }
1477
2043
  /**
1478
2044
  * Drives recovery on a sub-manager reconnect. Iterates this app server's
@@ -1491,10 +2057,10 @@ var SupaliveWebSocketServer = class {
1491
2057
  async runSubManagerRecovery() {
1492
2058
  const snapshot = Array.from(this.serverSubscriptions.values());
1493
2059
  if (snapshot.length === 0) {
1494
- logger$2.info("Sub-manager recovery: no subscriptions to restore");
2060
+ this.log.info("Sub-manager recovery: no subscriptions to restore");
1495
2061
  return;
1496
2062
  }
1497
- logger$2.info(`Sub-manager recovery: restoring ${snapshot.length} subscription(s)`);
2063
+ this.log.info(`Sub-manager recovery: restoring ${snapshot.length} subscription(s)`);
1498
2064
  const lastSnapshotTs = await this.db.impl.getLatestSnapshotTimestamp();
1499
2065
  const subIds = snapshot.map((e) => e.subId);
1500
2066
  const recordsMap = await this.cache.fetchRegistrationRecordsBatch(subIds);
@@ -1536,11 +2102,11 @@ var SupaliveWebSocketServer = class {
1536
2102
  });
1537
2103
  }
1538
2104
  } catch (err) {
1539
- logger$2.error(err, `Sub-manager recovery: failed to batch register ${i}-${i + batch.length}`);
2105
+ this.log.error(err, `Sub-manager recovery: failed to batch register ${i}-${i + batch.length}`);
1540
2106
  }
1541
2107
  }
1542
- await Promise.allSettled(recompute.map((a) => this.reexecuteAffectedQuery(a)));
1543
- logger$2.info(`Sub-manager recovery: done (${recompute.length} reexec'd)`);
2108
+ await Promise.allSettled(recompute.map((a) => this.reexecuteAffectedQuery(a, "recovery")));
2109
+ this.log.info(`Sub-manager recovery: done (${recompute.length} reexec'd)`);
1544
2110
  }
1545
2111
  async runAffectedHandler(affected) {
1546
2112
  await Promise.allSettled(affected.map((a) => this.reexecuteAffectedQuery(a)));
@@ -1557,6 +2123,7 @@ var SupaliveWebSocketServer = class {
1557
2123
  if (serverSub) {
1558
2124
  this.serverSubscriptions.delete(subId);
1559
2125
  this.stats.activeSubscriptions = Math.max(0, this.stats.activeSubscriptions - 1);
2126
+ this.obs.subscriptionRemoved();
1560
2127
  for (const [, session] of this.sessions) if (session.subIds.has(subId)) {
1561
2128
  notified++;
1562
2129
  session.subIds.delete(subId);
@@ -1567,18 +2134,18 @@ var SupaliveWebSocketServer = class {
1567
2134
  });
1568
2135
  }
1569
2136
  }
1570
- logger$2.warn(`Dropped subscription ${subId} locally (${reason}); notified ${notified} session(s)`);
2137
+ this.log.warn(`Dropped subscription ${subId} locally (${reason}); notified ${notified} session(s)`);
1571
2138
  }
1572
2139
  async handleMutationCommit(writeSet, commitTs) {
1573
2140
  if (writeSet.length === 0) return;
1574
2141
  const tables = [...new Set(writeSet.map((w) => w.table))];
1575
- logger$2.debug(`Mutation committed for tables: [${tables.join(", ")}]`);
2142
+ this.log.debug(`Mutation committed for tables: [${tables.join(", ")}]`);
1576
2143
  try {
1577
2144
  const result = await this.subManagerClient.invalidateWriteset({ writeSet });
1578
- logger$2.debug({ affected: result }, "Sub-managers invalidation result");
2145
+ this.log.debug({ affected: result }, "Sub-managers invalidation result");
1579
2146
  if (result?.affected?.length > 0) await Promise.allSettled(result.affected.map((affected) => this.dispatchAffected(affected, commitTs)));
1580
2147
  } catch (err) {
1581
- logger$2.error(err, "Failed to notify sub-managers");
2148
+ this.log.error(err, "Failed to notify sub-managers");
1582
2149
  }
1583
2150
  }
1584
2151
  /**
@@ -1600,7 +2167,7 @@ var SupaliveWebSocketServer = class {
1600
2167
  return;
1601
2168
  }
1602
2169
  if (affected.notifyInstances.length === 0) {
1603
- logger$2.debug(`dispatchAffected: no live instances hold ${affected.subId} — dropping`);
2170
+ this.log.debug(`dispatchAffected: no live instances hold ${affected.subId} — dropping`);
1604
2171
  return;
1605
2172
  }
1606
2173
  await this.publishRecomputeRace(affected, commitTs);
@@ -1626,23 +2193,23 @@ var SupaliveWebSocketServer = class {
1626
2193
  try {
1627
2194
  await redisRetryPolicy.execute(() => this.cache.redis.publish(channel, payload));
1628
2195
  } catch (err) {
1629
- logger$2.error(err, `Failed to publish recompute-race to ${channel} after many retries`);
2196
+ this.log.error(err, `Failed to publish recompute-race to ${channel} after many retries`);
1630
2197
  }
1631
2198
  });
1632
2199
  await Promise.all(publishPromises);
1633
2200
  }
1634
- async reexecuteAffectedQuery(affected) {
2201
+ async reexecuteAffectedQuery(affected, trigger = "recompute") {
1635
2202
  let foundSession = null;
1636
2203
  for (const [, session] of this.sessions) if (session.subIds.has(affected.subId)) {
1637
2204
  foundSession = session;
1638
2205
  break;
1639
2206
  }
1640
2207
  if (!foundSession) {
1641
- logger$2.debug(`Session not found for subId: ${affected.subId}`);
2208
+ this.log.debug(`Session not found for subId: ${affected.subId}`);
1642
2209
  return;
1643
2210
  }
1644
2211
  try {
1645
- const liveResult = await this.executeLiveQuery(affected.queryName, affected.args, foundSession.serverCtx);
2212
+ const liveResult = await this.executeLiveQuery(affected.queryName, affected.args, foundSession.serverCtx, { trigger });
1646
2213
  const dataHash = await getHashOf(liveResult.data);
1647
2214
  const [updateResult] = await Promise.all([this.subManagerClient.updateSubscriptionReadSet({
1648
2215
  cacheKey: affected.cacheKey,
@@ -1664,21 +2231,27 @@ var SupaliveWebSocketServer = class {
1664
2231
  ts
1665
2232
  });
1666
2233
  if (affected.notifyInstances.length > 0) await this.publishSubscriptionUpdate(affected.subId, liveResult.data, dataHash, affected.notifyInstances, ts);
1667
- logger$2.debug(`Re-executed affected query for subId: ${affected.subId}`);
2234
+ this.log.debug(`Re-executed affected query for subId: ${affected.subId}`);
1668
2235
  } catch (err) {
1669
- logger$2.error(err, `Failed to re-execute query for subId ${affected.subId}`);
2236
+ this.log.error(err, `Failed to re-execute query for subId ${affected.subId}`);
1670
2237
  }
1671
2238
  }
1672
- async executeLiveQuery(procedureName, input, serverCtx) {
2239
+ async executeLiveQuery(procedureName, input, serverCtx, opts) {
1673
2240
  const procedure = this.contextRegistry.getProcedure(procedureName);
1674
2241
  if (!procedure || procedure.type !== "query") throw new Error(`Procedure not defined: ${procedureName}`);
1675
2242
  return this.db.liveQuery(async (ctx) => {
1676
2243
  const fn = procedure.fn;
1677
- return fn({
2244
+ const callerLog = () => this.obs.lazyChildLogger({
2245
+ caller: "internal",
2246
+ procedure: procedureName
2247
+ });
2248
+ return traceQueryExecution(procedureName, opts?.trigger ?? "recompute", opts?.parentContext, (querySpan) => fn({
1678
2249
  db: ctx,
1679
2250
  serverCtx,
1680
- ...primaryOnlyConn
1681
- }, input);
2251
+ ...primaryOnlyConn,
2252
+ log: opts?.log ?? callerLog(),
2253
+ span: querySpan
2254
+ }, input));
1682
2255
  });
1683
2256
  }
1684
2257
  sendMessage(session, message) {
@@ -1705,7 +2278,7 @@ var SupaliveWebSocketServer = class {
1705
2278
  const appChannels = [...this.appPubSubChannels];
1706
2279
  await this.redisSub.subscribe(instanceChannel, schemaChannel, watermarkChannel, ...appChannels);
1707
2280
  this.redisSub.on("connect", () => {
1708
- logger$2.info(`Subscribed to Redis channels: ${[
2281
+ this.log.info(`Subscribed to Redis channels: ${[
1709
2282
  instanceChannel,
1710
2283
  schemaChannel,
1711
2284
  watermarkChannel,
@@ -1714,20 +2287,20 @@ var SupaliveWebSocketServer = class {
1714
2287
  });
1715
2288
  this.redisSub.on("message", (channel, message) => {
1716
2289
  if (channel === instanceChannel) this.handleRemoteInstanceMessage(message).catch((err) => {
1717
- logger$2.error(err, "Error handling remote instance message");
2290
+ this.log.error(err, "Error handling remote instance message");
1718
2291
  });
1719
2292
  else if (channel === schemaChannel) this.handleSchemaReload().catch((err) => {
1720
- logger$2.error(err, "Error handling schema reload");
2293
+ this.log.error(err, "Error handling schema reload");
1721
2294
  });
1722
2295
  else if (channel === watermarkChannel) try {
1723
2296
  this.retentionWatermark.set(BigInt(message));
1724
2297
  } catch (err) {
1725
- logger$2.error(err, `Bad retention watermark message: ${message}`);
2298
+ this.log.error(err, `Bad retention watermark message: ${message}`);
1726
2299
  }
1727
- else if (this.appPubSubChannels.has(channel)) Promise.resolve(this.config.appPubSub?.onMessage(channel, message)).catch((err) => logger$2.error(err, `Error in appPubSub handler for channel ${channel}`));
2300
+ else if (this.appPubSubChannels.has(channel)) Promise.resolve(this.config.appPubSub?.onMessage(channel, message)).catch((err) => this.log.error(err, `Error in appPubSub handler for channel ${channel}`));
1728
2301
  });
1729
2302
  this.redisSub.on("error", (err) => {
1730
- logger$2.error(err, `Subscriber error for channels: ${instanceChannel}, ${schemaChannel}`);
2303
+ this.log.error(err, `Subscriber error for channels: ${instanceChannel}, ${schemaChannel}`);
1731
2304
  });
1732
2305
  }
1733
2306
  /**
@@ -1737,13 +2310,13 @@ var SupaliveWebSocketServer = class {
1737
2310
  * messages published by this instance (we already have the new types).
1738
2311
  */
1739
2312
  async handleSchemaReload() {
1740
- logger$2.info(`Schema reload request received in instance ${this.instanceId} — reloading schema...`);
2313
+ this.log.info(`Schema reload request received in instance ${this.instanceId} — reloading schema...`);
1741
2314
  try {
1742
2315
  await sleep(1e3 + Math.random() * 5e3);
1743
2316
  await bootstrapDbTypes(this.db, this.cache, true);
1744
- logger$2.info(`Schema reload applied in instance ${this.instanceId}`);
2317
+ this.log.info(`Schema reload applied in instance ${this.instanceId}`);
1745
2318
  } catch (err) {
1746
- logger$2.error(err, "Failed to reload schema");
2319
+ this.log.error(err, "Failed to reload schema");
1747
2320
  return;
1748
2321
  }
1749
2322
  }
@@ -1777,7 +2350,7 @@ var SupaliveWebSocketServer = class {
1777
2350
  try {
1778
2351
  parsed = RemoteSubscriptionMessageSchema.parse(JSON.parse(message));
1779
2352
  } catch (err) {
1780
- logger$2.error(err, "Failed to parse remote instance message");
2353
+ this.log.error(err, "Failed to parse remote instance message");
1781
2354
  return;
1782
2355
  }
1783
2356
  if (parsed.originInstance === this.instanceId) return;
@@ -1800,7 +2373,7 @@ var SupaliveWebSocketServer = class {
1800
2373
  async handleRemoteRecomputeRace(req) {
1801
2374
  if (!this.hasLocalSessionFor(req.subId)) return;
1802
2375
  if (!await this.cache.tryAcquireRecomputeLock(this.instanceId, req.subId, req.commitTs, RECOMPUTE_LOCK_TTL_SECONDS)) {
1803
- logger$2.debug(`recompute-race: lost lock for ${req.subId}@${req.commitTs}, waiting for winner's update`);
2376
+ this.log.debug(`recompute-race: lost lock for ${req.subId}@${req.commitTs}, waiting for winner's update`);
1804
2377
  return;
1805
2378
  }
1806
2379
  await this.reexecuteAffectedQuery({
@@ -1826,7 +2399,7 @@ var SupaliveWebSocketServer = class {
1826
2399
  try {
1827
2400
  await redisRetryPolicy.execute(() => this.cache.redis.publish(channel, payload));
1828
2401
  } catch (err) {
1829
- logger$2.error(err, `Failed to publish to ${channel} after many retries`);
2402
+ this.log.error(err, `Failed to publish to ${channel} after many retries`);
1830
2403
  }
1831
2404
  });
1832
2405
  await Promise.all(publishPromises);
@@ -1853,29 +2426,6 @@ function sendJson(res, status, body) {
1853
2426
  res.writeHead(status, { "content-type": "application/json" });
1854
2427
  res.end(payload);
1855
2428
  }
1856
- function lowercaseHeaders(headers) {
1857
- const out = {};
1858
- for (const [k, v] of Object.entries(headers)) out[k.toLowerCase()] = Array.isArray(v) ? v.join(",") : v;
1859
- return out;
1860
- }
1861
- /** Buffer an incoming request body to a string, capped to guard against abuse. */
1862
- function readRequestBody(req, maxBytes = 1024 * 1024) {
1863
- return new Promise((resolve, reject) => {
1864
- const chunks = [];
1865
- let size = 0;
1866
- req.on("data", (chunk) => {
1867
- size += chunk.length;
1868
- if (size > maxBytes) {
1869
- reject(/* @__PURE__ */ new Error("request body too large"));
1870
- req.destroy();
1871
- return;
1872
- }
1873
- chunks.push(chunk);
1874
- });
1875
- req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
1876
- req.on("error", reject);
1877
- });
1878
- }
1879
2429
  //#endregion
1880
2430
  //#region src/server/hash.ts
1881
2431
  /**
@@ -2223,6 +2773,6 @@ function resolveMaxConnectionsPerWorker(config, workers) {
2223
2773
  return Math.max(1, Math.floor(total / workers));
2224
2774
  }
2225
2775
  //#endregion
2226
- export { ANONYMOUS_IDENTITY, CORE_VERSION, CacheLayer, DB_QUERY_TIMEOUT_MS, DevScheduler, InvalidateWritesetParamsSchema, JobClient, QStashScheduler, RegisterSubscriptionParamsSchema, SubManagerClient, SubscriptionManager, SupaliveWebSocketServer, UnregisterSubscriptionParamsSchema, UpdateSubscriptionReadSetParamsSchema, appInstanceId, bootstrapDbTypes, createDatabase, generateCacheKey, generateSubscriptionId, getHashOf, initCacheLayer, initCore, initDatabase, isProduction, logLevelOf, logger, serializeMapValue };
2776
+ export { ANONYMOUS_IDENTITY, CORE_VERSION, CacheLayer, DB_QUERY_TIMEOUT_MS, DevScheduler, InvalidateWritesetParamsSchema, JobClient, Observability, QStashScheduler, RegisterSubscriptionParamsSchema, SubManagerClient, SubscriptionManager, SupaliveWebSocketServer, UnregisterSubscriptionParamsSchema, UpdateSubscriptionReadSetParamsSchema, appInstanceId, bootstrapDbTypes, configureRootLogger, createDatabase, elapsedMs, generateCacheKey, generateSubscriptionId, getHashOf, getRootLogger, initCacheLayer, initCore, initDatabase, isProduction, logLevelOf, logger, serializeMapValue, startTimer, traceQueryExecution };
2227
2777
 
2228
2778
  //# sourceMappingURL=server.js.map