@supalive/core 1.17.0 → 1.19.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/dist/index-B3i2G1gr.d.ts +2402 -0
- package/dist/index-B3i2G1gr.d.ts.map +1 -0
- package/dist/index-BHs0EtJk.d.ts +2402 -0
- package/dist/index-BHs0EtJk.d.ts.map +1 -0
- package/dist/index-BMz7jVbX.d.ts +2402 -0
- package/dist/index-BMz7jVbX.d.ts.map +1 -0
- package/dist/index-BuWa503L.d.ts +2445 -0
- package/dist/index-BuWa503L.d.ts.map +1 -0
- package/dist/index-CBIjmPUS.d.ts +2444 -0
- package/dist/index-CBIjmPUS.d.ts.map +1 -0
- package/dist/index-CKo6HvJc.d.ts +2455 -0
- package/dist/index-CKo6HvJc.d.ts.map +1 -0
- package/dist/index-CjkNEwSc.d.ts +2402 -0
- package/dist/index-CjkNEwSc.d.ts.map +1 -0
- package/dist/index-Cng74DL5.d.ts +2404 -0
- package/dist/index-Cng74DL5.d.ts.map +1 -0
- package/dist/index-DShKjpzF.d.ts +2453 -0
- package/dist/index-DShKjpzF.d.ts.map +1 -0
- package/dist/index-Dcxf_Gm-.d.ts +2454 -0
- package/dist/index-Dcxf_Gm-.d.ts.map +1 -0
- package/dist/index-yXV5AMh9.d.ts +2416 -0
- package/dist/index-yXV5AMh9.d.ts.map +1 -0
- package/dist/logger-DrXccWFZ.js +346 -0
- package/dist/logger-DrXccWFZ.js.map +1 -0
- package/dist/mysql-1ENHI0yX.d.ts +114 -0
- package/dist/mysql-1ENHI0yX.d.ts.map +1 -0
- package/dist/mysql-B2u0Ye4x.d.ts +114 -0
- package/dist/mysql-B2u0Ye4x.d.ts.map +1 -0
- package/dist/mysql-BHW-tAqt.d.ts +114 -0
- package/dist/mysql-BHW-tAqt.d.ts.map +1 -0
- package/dist/mysql-C6jg1Vpr.d.ts +114 -0
- package/dist/mysql-C6jg1Vpr.d.ts.map +1 -0
- package/dist/mysql-C_TTCZ8j.d.ts +114 -0
- package/dist/mysql-C_TTCZ8j.d.ts.map +1 -0
- package/dist/mysql-CyzJqrvO.d.ts +114 -0
- package/dist/mysql-CyzJqrvO.d.ts.map +1 -0
- package/dist/mysql-DKR33wfH.d.ts +114 -0
- package/dist/mysql-DKR33wfH.d.ts.map +1 -0
- package/dist/mysql-DcHgRk9f.d.ts +114 -0
- package/dist/mysql-DcHgRk9f.d.ts.map +1 -0
- package/dist/mysql-DnrIJoWL.d.ts +114 -0
- package/dist/mysql-DnrIJoWL.d.ts.map +1 -0
- package/dist/mysql-GsWYd6fG.d.ts +114 -0
- package/dist/mysql-GsWYd6fG.d.ts.map +1 -0
- package/dist/mysql-ygH2EMGn.js +622 -0
- package/dist/mysql-ygH2EMGn.js.map +1 -0
- package/dist/mysql-z9Jefhd6.d.ts +114 -0
- package/dist/mysql-z9Jefhd6.d.ts.map +1 -0
- package/dist/one-shot-query-9mCboWaB.js +565 -0
- package/dist/one-shot-query-9mCboWaB.js.map +1 -0
- package/dist/one-shot-query-B9SjfcCD.js +550 -0
- package/dist/one-shot-query-B9SjfcCD.js.map +1 -0
- package/dist/one-shot-query-BBiv9LGP.js +565 -0
- package/dist/one-shot-query-BBiv9LGP.js.map +1 -0
- package/dist/one-shot-query-Bxz_N-NE.js +563 -0
- package/dist/one-shot-query-Bxz_N-NE.js.map +1 -0
- package/dist/one-shot-query-DE9oQU40.js +550 -0
- package/dist/one-shot-query-DE9oQU40.js.map +1 -0
- package/dist/one-shot-query-adoWqGLs.js +565 -0
- package/dist/one-shot-query-adoWqGLs.js.map +1 -0
- package/dist/overlap-checker-CCgq_Tpa.js +284 -0
- package/dist/overlap-checker-CCgq_Tpa.js.map +1 -0
- package/dist/postgres-B2FLN1Kw.d.ts +118 -0
- package/dist/postgres-B2FLN1Kw.d.ts.map +1 -0
- package/dist/postgres-C7xHWbij.d.ts +118 -0
- package/dist/postgres-C7xHWbij.d.ts.map +1 -0
- package/dist/postgres-CFarR3cB.d.ts +118 -0
- package/dist/postgres-CFarR3cB.d.ts.map +1 -0
- package/dist/postgres-CWVM-KKP.d.ts +118 -0
- package/dist/postgres-CWVM-KKP.d.ts.map +1 -0
- package/dist/postgres-D50xpaQ-.d.ts +118 -0
- package/dist/postgres-D50xpaQ-.d.ts.map +1 -0
- package/dist/postgres-DBMAv_dq.d.ts +118 -0
- package/dist/postgres-DBMAv_dq.d.ts.map +1 -0
- package/dist/postgres-DBuP9ON_.d.ts +118 -0
- package/dist/postgres-DBuP9ON_.d.ts.map +1 -0
- package/dist/postgres-DwiUT_A6.js +867 -0
- package/dist/postgres-DwiUT_A6.js.map +1 -0
- package/dist/postgres-I4sgdBnG.d.ts +118 -0
- package/dist/postgres-I4sgdBnG.d.ts.map +1 -0
- package/dist/postgres-tVdgYJkj.d.ts +118 -0
- package/dist/postgres-tVdgYJkj.d.ts.map +1 -0
- package/dist/postgres-yAn2CF7j.d.ts +118 -0
- package/dist/postgres-yAn2CF7j.d.ts.map +1 -0
- package/dist/postgres-yTjQ-Nxk.d.ts +118 -0
- package/dist/postgres-yTjQ-Nxk.d.ts.map +1 -0
- package/dist/router-DP2ThAwh.js.map +1 -1
- package/dist/src/client/index.d.ts +1 -1
- package/dist/src/client/index.js +46 -51
- package/dist/src/client/index.js.map +1 -1
- package/dist/src/exports/mysql.d.ts +1 -1
- package/dist/src/exports/mysql.js +1 -1
- package/dist/src/exports/postgres.d.ts +1 -1
- package/dist/src/exports/postgres.js +1 -1
- package/dist/src/exports/procedure.d.ts +1 -1
- package/dist/src/exports/schema-sql.d.ts +1 -1
- package/dist/src/exports/server.d.ts +17 -7
- package/dist/src/exports/server.d.ts.map +1 -1
- package/dist/src/exports/server.js +42 -414
- package/dist/src/exports/server.js.map +1 -1
- package/dist/src/exports/sub-manager-worker-entry.js +1 -1
- package/dist/src/exports/types.d.ts +3 -3
- package/dist/src/exports/types.js +1 -1
- package/dist/sub-worker-dispatch-BLujn13f.js +792 -0
- package/dist/sub-worker-dispatch-BLujn13f.js.map +1 -0
- package/dist/sub-worker-dispatch-Cvqarej5.js +826 -0
- package/dist/sub-worker-dispatch-Cvqarej5.js.map +1 -0
- package/dist/sub-worker-dispatch-D-3VPkd4.js +826 -0
- package/dist/sub-worker-dispatch-D-3VPkd4.js.map +1 -0
- package/dist/sub-worker-dispatch-qiSs5Xkm.js.map +1 -1
- package/dist/types_client_rpc-B8jh6MKl.js +154 -0
- package/dist/types_client_rpc-B8jh6MKl.js.map +1 -0
- package/dist/types_client_rpc-BRCAgeyJ.js +155 -0
- package/dist/types_client_rpc-BRCAgeyJ.js.map +1 -0
- package/dist/types_server-B5KQ6PAO.d.ts +692 -0
- package/dist/types_server-B5KQ6PAO.d.ts.map +1 -0
- package/dist/types_server-BCst4TfZ.d.ts +703 -0
- package/dist/types_server-BCst4TfZ.d.ts.map +1 -0
- package/dist/types_server-BJBeSk6n.d.ts +691 -0
- package/dist/types_server-BJBeSk6n.d.ts.map +1 -0
- package/dist/types_server-BXeiSmd8.d.ts +691 -0
- package/dist/types_server-BXeiSmd8.d.ts.map +1 -0
- package/dist/types_server-BYwLkwbC.d.ts +691 -0
- package/dist/types_server-BYwLkwbC.d.ts.map +1 -0
- package/dist/types_server-BnRmctWG.d.ts +699 -0
- package/dist/types_server-BnRmctWG.d.ts.map +1 -0
- package/dist/types_server-Bvrohyc8.d.ts +691 -0
- package/dist/types_server-Bvrohyc8.d.ts.map +1 -0
- package/dist/types_server-C20ibVGu.d.ts +692 -0
- package/dist/types_server-C20ibVGu.d.ts.map +1 -0
- package/dist/types_server-C6oCm9oC.d.ts +691 -0
- package/dist/types_server-C6oCm9oC.d.ts.map +1 -0
- package/dist/types_server-CHJLfdxb.d.ts +691 -0
- package/dist/types_server-CHJLfdxb.d.ts.map +1 -0
- package/dist/types_server-CjjJIQps.d.ts +691 -0
- package/dist/types_server-CjjJIQps.d.ts.map +1 -0
- package/dist/types_server-CjzRPllt.d.ts +691 -0
- package/dist/types_server-CjzRPllt.d.ts.map +1 -0
- package/dist/types_server-Cy4bvdeN.d.ts +691 -0
- package/dist/types_server-Cy4bvdeN.d.ts.map +1 -0
- package/dist/types_server-DnO_fp-F.d.ts +691 -0
- package/dist/types_server-DnO_fp-F.d.ts.map +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
import { Ht as JobScheduler, Mr as MutationResult, _t as HandlerLogger, ar as InvalidateWritesetParams, cr as RegisterSubscriptionParams, dr as UnregisterSubscriptionParams, gr as UpdateSubscriptionReadSetParams, hr as UnregisterSubscriptionsResult, ir as AffectedSubscription, mr as UnregisterSubscriptionsParams, nr as CacheLayer, pr as UnregisterSubscriptionResult, qn as Database, qt as SupaliveDb, rr as IORedisClient, sr as InvalidateWritesetResult, st as RegisteredProcedure, ur as RegisterSubscriptionResult, vr as UpdateSubscriptionReadSetResult, vt as HandlerObservability } from "./index-BHs0EtJk.js";
|
|
2
|
+
import { r as ObjectStorage } from "./object-storage-354KU6Mj.js";
|
|
3
|
+
import z from "zod";
|
|
4
|
+
import { Context, Span } from "@opentelemetry/api";
|
|
5
|
+
import { IncomingMessage, ServerResponse } from "http";
|
|
6
|
+
import { WebSocket } from "ws";
|
|
7
|
+
import { Level, Logger } from "pino";
|
|
8
|
+
|
|
9
|
+
//#region src/observability.d.ts
|
|
10
|
+
/** Replace the process-wide root logger. Call once at startup, before creating
|
|
11
|
+
* the server, to route Supalive's request/lifecycle logs through your own pino
|
|
12
|
+
* instance. */
|
|
13
|
+
declare function configureRootLogger(logger: Logger): void;
|
|
14
|
+
/** The current root logger (defaults to the env-configured singleton). */
|
|
15
|
+
declare function getRootLogger(): Logger;
|
|
16
|
+
type OperationTransport = "ws" | "http" | "internal";
|
|
17
|
+
type OperationKind = "query" | "mutation" | "action" | "subscribe" | "unsubscribe" | "job" | "connection";
|
|
18
|
+
type OperationOutcome = "ok" | "error";
|
|
19
|
+
/**
|
|
20
|
+
* A single completed operation. Emitted once per WS call / subscribe / HTTP RPC
|
|
21
|
+
* request / job run, and handed to metrics, the log line, and the `onEvent`
|
|
22
|
+
* hook. High-cardinality fields (`reqId`, `userId`, `sessionId`) are logged/
|
|
23
|
+
* forwarded but NEVER used as metric attributes.
|
|
24
|
+
*/
|
|
25
|
+
interface OperationEvent {
|
|
26
|
+
/** Coarse operation family, e.g. "rpc" | "subscribe" | "unsubscribe" | "job" | "connection". */
|
|
27
|
+
op: string;
|
|
28
|
+
transport: OperationTransport;
|
|
29
|
+
procedure?: string;
|
|
30
|
+
kind?: OperationKind;
|
|
31
|
+
outcome: OperationOutcome;
|
|
32
|
+
/** Wall-clock duration in milliseconds. */
|
|
33
|
+
durationMs: number;
|
|
34
|
+
/** Error code for a failed op (e.g. NOT_FOUND, EXECUTION_ERROR, RATE_LIMITED). */
|
|
35
|
+
code?: string;
|
|
36
|
+
userId?: string | null;
|
|
37
|
+
sessionId?: string;
|
|
38
|
+
/** Per-request correlation id (WS request id, or a generated HTTP id). */
|
|
39
|
+
reqId?: string;
|
|
40
|
+
/** The thrown error, when the failure was an unexpected exception (→ error level). */
|
|
41
|
+
error?: unknown;
|
|
42
|
+
/** Extra structured fields to include on the log line only. */
|
|
43
|
+
extra?: Record<string, unknown>;
|
|
44
|
+
/** The operation's OTel span, when one was started. `record()` finalizes and
|
|
45
|
+
* ends it (status + attributes + exception). */
|
|
46
|
+
span?: Span;
|
|
47
|
+
}
|
|
48
|
+
/** Whether to emit a log line per request. Metrics + `onEvent` fire regardless. */
|
|
49
|
+
type RequestLogMode = "all" | "errors" | "off";
|
|
50
|
+
interface ObservabilityOptions {
|
|
51
|
+
/** Base logger for request/lifecycle lines. Defaults to {@link getRootLogger}. */
|
|
52
|
+
logger?: Logger;
|
|
53
|
+
/** Level override applied to the derived request logger. */
|
|
54
|
+
level?: Level;
|
|
55
|
+
/** Per-request log policy. `"all"` logs successes at info + failures; `"errors"`
|
|
56
|
+
* logs only failures; `"off"` logs neither (metrics/onEvent still fire).
|
|
57
|
+
* Default `"all"`. */
|
|
58
|
+
requestLog?: RequestLogMode;
|
|
59
|
+
/** Callback fed every {@link OperationEvent}. Errors thrown by it are swallowed. */
|
|
60
|
+
onEvent?: (e: OperationEvent) => void;
|
|
61
|
+
/** OpenTelemetry meter name. Default `"@supalive/core"`. */
|
|
62
|
+
meterName?: string;
|
|
63
|
+
/** OpenTelemetry meter version. Defaults to the core package version. */
|
|
64
|
+
meterVersion?: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The bound observability surface for one server instance: an injectable logger,
|
|
68
|
+
* OTel metric instruments, and the `record()` seam every operation funnels
|
|
69
|
+
* through. Built once in the server constructor and shared with the HTTP RPC
|
|
70
|
+
* handler.
|
|
71
|
+
*/
|
|
72
|
+
declare class Observability {
|
|
73
|
+
readonly logger: Logger;
|
|
74
|
+
private readonly requestLog;
|
|
75
|
+
private readonly onEvent?;
|
|
76
|
+
private readonly requestCounter;
|
|
77
|
+
private readonly durationHistogram;
|
|
78
|
+
private readonly activeConnections;
|
|
79
|
+
private readonly activeSubscriptions;
|
|
80
|
+
private readonly tracer;
|
|
81
|
+
constructor(opts?: ObservabilityOptions);
|
|
82
|
+
/** Record one completed operation: metrics + optional onEvent + one log line,
|
|
83
|
+
* and finalize the op's span (status/attributes/exception) when present. */
|
|
84
|
+
record(e: OperationEvent): void;
|
|
85
|
+
/**
|
|
86
|
+
* Start a SERVER span for an operation, as a child of `parent` (or the active
|
|
87
|
+
* context). No-op — returns a non-recording span — until a TracerProvider is
|
|
88
|
+
* registered by the app, so this costs nothing when tracing is disabled.
|
|
89
|
+
* Returns the span plus a context that has it active, for `context.with(...)`
|
|
90
|
+
* wrapping so nested handler work / `ctx.span` children attach correctly.
|
|
91
|
+
*/
|
|
92
|
+
startSpan(name: string, opts?: {
|
|
93
|
+
transport?: OperationTransport;
|
|
94
|
+
procedure?: string;
|
|
95
|
+
kind?: OperationKind;
|
|
96
|
+
parent?: Context;
|
|
97
|
+
}): {
|
|
98
|
+
span: Span;
|
|
99
|
+
context: Context;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Continue a trace from an inbound carrier (HTTP headers, or a WS message's
|
|
103
|
+
* `trace` field). Returns a context to pass as `startSpan({ parent })`. When
|
|
104
|
+
* no propagator is registered / no trace context is present, returns the root
|
|
105
|
+
* context (a fresh trace).
|
|
106
|
+
*/
|
|
107
|
+
extractContext(carrier: Record<string, string | string[] | undefined>): Context;
|
|
108
|
+
/** Set the span's final status/attributes from the event, then end it.
|
|
109
|
+
* Returns the (valid) trace id for log correlation, or undefined. */
|
|
110
|
+
private finalizeSpan;
|
|
111
|
+
/** A request-scoped child logger for `ctx.log`, bound with the given fields
|
|
112
|
+
* (reqId, procedure, and trace id when tracing is on). */
|
|
113
|
+
childLogger(fields: Record<string, unknown>): Logger;
|
|
114
|
+
/**
|
|
115
|
+
* A LAZY request-scoped `ctx.log`. Most handlers never log, and pino's
|
|
116
|
+
* `.child()` (bindings merge + serialization) isn't free — so this defers the
|
|
117
|
+
* child creation until the first `debug/info/warn/error` call and reuses it
|
|
118
|
+
* after. When a handler logs nothing, no child logger is ever allocated; the
|
|
119
|
+
* only per-request cost is this small delegator object. Returns the narrow
|
|
120
|
+
* {@link HandlerLogger} surface the handler context exposes.
|
|
121
|
+
*/
|
|
122
|
+
lazyChildLogger(fields: Record<string, unknown>): HandlerLogger;
|
|
123
|
+
/** The span's trace id, or undefined when tracing is off / the span is
|
|
124
|
+
* non-recording. For binding onto `ctx.log`. */
|
|
125
|
+
traceIdOf(span: Span | undefined): string | undefined;
|
|
126
|
+
connectionOpened(): void;
|
|
127
|
+
connectionClosed(): void;
|
|
128
|
+
subscriptionAdded(n?: number): void;
|
|
129
|
+
subscriptionRemoved(n?: number): void;
|
|
130
|
+
}
|
|
131
|
+
/** Start marker for an operation; pass to {@link elapsedMs} to get its duration. */
|
|
132
|
+
declare function startTimer(): number;
|
|
133
|
+
declare function elapsedMs(startedAt: number): number;
|
|
134
|
+
/** What triggered a query execution — a client request/subscribe, or a background
|
|
135
|
+
* reactive recompute (live-query invalidation / sub-manager recovery). */
|
|
136
|
+
type QueryTrigger = "request" | "subscribe" | "recompute" | "recovery";
|
|
137
|
+
/**
|
|
138
|
+
* Run a query handler inside a `query <procedureName>` span, nested under
|
|
139
|
+
* `parentContext` (the operation span) or the active context. The span is handed
|
|
140
|
+
* to `run` so it becomes the query handler's `ctx.span` and the parent of any DB
|
|
141
|
+
* spans. Cache HITs never call this (no execution), so a span appearing means a
|
|
142
|
+
* real recompute — including the otherwise-invisible background reactive path,
|
|
143
|
+
* where `parentContext` is omitted and the span is a root.
|
|
144
|
+
*
|
|
145
|
+
* No-op-cheap when tracing is off: the started span is non-recording, so we skip
|
|
146
|
+
* the `context.with` and just run.
|
|
147
|
+
*/
|
|
148
|
+
declare function traceQueryExecution<T>(procedureName: string, trigger: QueryTrigger, parentContext: Context | undefined, run: (span: Span) => Promise<T>): Promise<T>;
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/server/sub-manager-client.d.ts
|
|
151
|
+
/**
|
|
152
|
+
* Callback the app server registers to process invalidations that were
|
|
153
|
+
* deferred during a sub-manager outage. When the sub-manager reconnects and
|
|
154
|
+
* the buffered writeSets are flushed in one batched RPC, the resulting
|
|
155
|
+
* `affected[]` is handed to this callback — which is expected to do the
|
|
156
|
+
* same work as the normal per-mutation `reexecuteAffectedQuery` path.
|
|
157
|
+
*/
|
|
158
|
+
type AffectedHandler = (affected: AffectedSubscription[]) => Promise<void>;
|
|
159
|
+
/**
|
|
160
|
+
* Callback the app server registers to drive recovery on sub-manager
|
|
161
|
+
* reconnect. Iterates `serverSubscriptions`, reads `sl:reg:<subId>` from
|
|
162
|
+
* Redis, and re-registers each known sub. Resolves when complete; only then
|
|
163
|
+
* does the client's buffer flush and gated calls release.
|
|
164
|
+
*/
|
|
165
|
+
type RecoveryDriver = () => Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* The transport-agnostic contract the app server uses to talk to the
|
|
168
|
+
* subscription manager. Implemented by:
|
|
169
|
+
*
|
|
170
|
+
* • {@link SubManagerClient} — the WebSocket RPC client, for a sub-manager
|
|
171
|
+
* running in its own process (`subManagerUrl`); and
|
|
172
|
+
* • the object returned by `SubscriptionManager.localLink()` — a direct
|
|
173
|
+
* in-process link when the manager is embedded in the app server (no
|
|
174
|
+
* socket, no port, no serialization).
|
|
175
|
+
*
|
|
176
|
+
* Injecting this into `SupaliveServerConfig.subManager` lets a deployment run
|
|
177
|
+
* combined (one process) now and split the sub-manager out later by switching
|
|
178
|
+
* back to `subManagerUrl`, with no other code changes.
|
|
179
|
+
*/
|
|
180
|
+
interface SubManagerLink {
|
|
181
|
+
registerSubscription(params: RegisterSubscriptionParams): Promise<RegisterSubscriptionResult>;
|
|
182
|
+
registerSubscriptionBatch(params: RegisterSubscriptionParams[]): Promise<RegisterSubscriptionResult[]>;
|
|
183
|
+
updateSubscriptionReadSet(params: UpdateSubscriptionReadSetParams): Promise<UpdateSubscriptionReadSetResult>;
|
|
184
|
+
unregisterSubscription(params: UnregisterSubscriptionParams): Promise<UnregisterSubscriptionResult>;
|
|
185
|
+
unregisterSubscriptions(params: UnregisterSubscriptionsParams): Promise<UnregisterSubscriptionsResult>;
|
|
186
|
+
invalidateWriteset(params: InvalidateWritesetParams): Promise<InvalidateWritesetResult>;
|
|
187
|
+
/**
|
|
188
|
+
* Register recovery for the reconnect path. A no-op for the in-process link,
|
|
189
|
+
* which never disconnects.
|
|
190
|
+
*/
|
|
191
|
+
setRecoveryDriver(driver: RecoveryDriver): void;
|
|
192
|
+
/**
|
|
193
|
+
* Register the post-recovery batched-invalidate handler. A no-op for the
|
|
194
|
+
* in-process link.
|
|
195
|
+
*/
|
|
196
|
+
setAffectedHandler(handler: AffectedHandler): void;
|
|
197
|
+
}
|
|
198
|
+
interface SubManagerClientOptions {
|
|
199
|
+
url: string;
|
|
200
|
+
/**
|
|
201
|
+
* Maximum number of buffered WriteEntry records held during an outage.
|
|
202
|
+
* If exceeded, the buffer is dropped — the commit-log replay path during
|
|
203
|
+
* the next register call will catch up any missed invalidations, at the
|
|
204
|
+
* cost of a brief recompute spike. Default 50_000 entries.
|
|
205
|
+
*/
|
|
206
|
+
invalidateBufferLimit?: number;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* RPC client for the (single) sub-manager process.
|
|
210
|
+
*
|
|
211
|
+
* Design (Convex-inspired):
|
|
212
|
+
* • The sub-manager is in-memory and ephemeral. There is no version
|
|
213
|
+
* protocol, no client-side registration registry, no OUT_OF_SYNC
|
|
214
|
+
* replay logic. All recovery is driven from the app server using
|
|
215
|
+
* state it already has (`serverSubscriptions`) plus registration
|
|
216
|
+
* records persisted to Redis at `sl:reg:<subId>`.
|
|
217
|
+
*
|
|
218
|
+
* • Out-of-order `updateSubscriptionReadSet` calls are filtered server
|
|
219
|
+
* side using `lastSnapshotTs` (monotonic DB commit timestamp).
|
|
220
|
+
*
|
|
221
|
+
* • On rpc-websockets reconnect, the client:
|
|
222
|
+
* 1. Transitions to `busy` (any in-flight `invalidateWriteset`
|
|
223
|
+
* buffers its writeSet).
|
|
224
|
+
* 2. Invokes the app server's `recoveryDriver` to re-register all
|
|
225
|
+
* known subs.
|
|
226
|
+
* 3. Flushes the buffer as one batched `invalidateWriteset` RPC and
|
|
227
|
+
* hands the resulting `affected[]` to `affectedHandler`.
|
|
228
|
+
* 4. Transitions to `ready`.
|
|
229
|
+
*
|
|
230
|
+
* • During an extended outage, the buffer is bounded; if exceeded, it
|
|
231
|
+
* is dropped and we rely on the commit-log replay built into
|
|
232
|
+
* `SubscriptionWorker.register()` to catch each sub up on the next
|
|
233
|
+
* re-register.
|
|
234
|
+
*/
|
|
235
|
+
declare class SubManagerClient implements SubManagerLink {
|
|
236
|
+
private client;
|
|
237
|
+
private state;
|
|
238
|
+
private hasConnectedOnce;
|
|
239
|
+
/** Resolves whenever state transitions to 'ready'. Replaced on each busy→ready cycle. */
|
|
240
|
+
private readyPromise;
|
|
241
|
+
private resolveReady;
|
|
242
|
+
/** Writeset entries accumulated while state === 'busy'. Flushed as one RPC after recovery. */
|
|
243
|
+
private bufferedWriteSets;
|
|
244
|
+
private readonly bufferLimit;
|
|
245
|
+
private bufferDroppedDuringOutage;
|
|
246
|
+
private recoveryDriver?;
|
|
247
|
+
private affectedHandler?;
|
|
248
|
+
constructor(urlOrOptions: string | SubManagerClientOptions);
|
|
249
|
+
/**
|
|
250
|
+
* Wire up the app-server-driven recovery. Must be called before any
|
|
251
|
+
* disconnect/reconnect cycle for buffered writeSets to be flushed and
|
|
252
|
+
* subscriptions re-registered.
|
|
253
|
+
*/
|
|
254
|
+
setRecoveryDriver(driver: RecoveryDriver): void;
|
|
255
|
+
/**
|
|
256
|
+
* Wire up the handler invoked when batched invalidate results land
|
|
257
|
+
* after recovery. The handler is expected to drive
|
|
258
|
+
* `reexecuteAffectedQuery` for each entry.
|
|
259
|
+
*/
|
|
260
|
+
setAffectedHandler(handler: AffectedHandler): void;
|
|
261
|
+
registerSubscription(params: RegisterSubscriptionParams): Promise<RegisterSubscriptionResult>;
|
|
262
|
+
registerSubscriptionBatch(params: RegisterSubscriptionParams[]): Promise<RegisterSubscriptionResult[]>;
|
|
263
|
+
updateSubscriptionReadSet(params: UpdateSubscriptionReadSetParams): Promise<UpdateSubscriptionReadSetResult>;
|
|
264
|
+
unregisterSubscription(params: UnregisterSubscriptionParams): Promise<UnregisterSubscriptionResult>;
|
|
265
|
+
unregisterSubscriptions(params: UnregisterSubscriptionsParams): Promise<UnregisterSubscriptionsResult>;
|
|
266
|
+
/**
|
|
267
|
+
* Sends the writeSet to the sub-manager for invalidation, OR buffers it
|
|
268
|
+
* if the client is currently disconnected / recovering. Returns
|
|
269
|
+
* `{ affected: [] }` while buffering — the actual `affected` from the
|
|
270
|
+
* buffered batch is delivered via the registered `affectedHandler`
|
|
271
|
+
* after recovery completes.
|
|
272
|
+
*/
|
|
273
|
+
invalidateWriteset(params: InvalidateWritesetParams): Promise<InvalidateWritesetResult>;
|
|
274
|
+
/**
|
|
275
|
+
* Single point through which RPCs go. Throws on transport / server error.
|
|
276
|
+
* `registerSubscription`, `updateSubscriptionReadSet`, and
|
|
277
|
+
* `unregisterSubscription` are NOT gated on the ready state: during a
|
|
278
|
+
* disconnect they will fail at the socket layer, and during recovery
|
|
279
|
+
* (which itself uses these methods) gating would deadlock.
|
|
280
|
+
*/
|
|
281
|
+
private call;
|
|
282
|
+
private appendToBuffer;
|
|
283
|
+
private flushBuffer;
|
|
284
|
+
private markBusy;
|
|
285
|
+
private markReady;
|
|
286
|
+
private handleClose;
|
|
287
|
+
private handleOpen;
|
|
288
|
+
private runRecovery;
|
|
289
|
+
/**
|
|
290
|
+
* Test/inspection helpers.
|
|
291
|
+
*/
|
|
292
|
+
/** @internal */
|
|
293
|
+
isReady(): boolean;
|
|
294
|
+
/** @internal */
|
|
295
|
+
bufferSize(): number;
|
|
296
|
+
}
|
|
297
|
+
//#endregion
|
|
298
|
+
//#region src/config.d.ts
|
|
299
|
+
/** CORS policy for the HTTP RPC endpoint. */
|
|
300
|
+
interface RpcCorsConfig {
|
|
301
|
+
/**
|
|
302
|
+
* Allowed origins. `"*"` sends `Access-Control-Allow-Origin: *`; a list
|
|
303
|
+
* echoes back a request's `Origin` only when it matches (and sets `Vary`).
|
|
304
|
+
* Omit `cors` entirely to send no CORS headers (same-origin only).
|
|
305
|
+
*/
|
|
306
|
+
origins: string[] | "*";
|
|
307
|
+
}
|
|
308
|
+
/** Rate-limit policy for the HTTP RPC endpoint (fixed window, per instance). */
|
|
309
|
+
interface RpcRateLimitConfig {
|
|
310
|
+
/** Window length in ms. Default 60000. */
|
|
311
|
+
windowMs?: number;
|
|
312
|
+
/** Max requests per window per key. Default 120. */
|
|
313
|
+
max?: number;
|
|
314
|
+
/**
|
|
315
|
+
* Derive the limiter key from the request. Default: client IP
|
|
316
|
+
* (`X-Forwarded-For` first hop, else socket address). Return a stable string.
|
|
317
|
+
*/
|
|
318
|
+
keyBy?: (req: IncomingMessage) => string;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Enables a one-shot HTTP RPC transport for `query`/`mutation`/`action`
|
|
322
|
+
* (no subscriptions — those stay WebSocket-only). Lets clients that don't need
|
|
323
|
+
* live queries call procedures without holding a socket open. Omit to disable
|
|
324
|
+
* the endpoint entirely.
|
|
325
|
+
*/
|
|
326
|
+
interface RpcConfig<TContext = unknown> {
|
|
327
|
+
/**
|
|
328
|
+
* The single endpoint path. Default `/_rpc`. Every procedure is called as
|
|
329
|
+
* `POST {path}` with `{ name, input }` in the body — the name is never in the
|
|
330
|
+
* URL. Keep this distinct from `jobPath`.
|
|
331
|
+
*/
|
|
332
|
+
path?: string;
|
|
333
|
+
/**
|
|
334
|
+
* Map an HTTP request to the auth `data` handed to `verifyAuth` (the same
|
|
335
|
+
* callback the WebSocket path uses). Default: `Authorization: Bearer <token>`
|
|
336
|
+
* → `{ token }`. Return `undefined` for an anonymous request.
|
|
337
|
+
*/
|
|
338
|
+
auth?: (req: IncomingMessage) => Record<string, unknown> | undefined;
|
|
339
|
+
/** CORS policy. Omit for same-origin only (no CORS headers). */
|
|
340
|
+
cors?: RpcCorsConfig;
|
|
341
|
+
/** Rate limiting. Omit for defaults (120/min per IP); set `false` to disable. */
|
|
342
|
+
rateLimit?: RpcRateLimitConfig | false;
|
|
343
|
+
/**
|
|
344
|
+
* Extra static response headers attached to EVERY RPC response — success and
|
|
345
|
+
* error alike. Their names are also added to `Access-Control-Expose-Headers`
|
|
346
|
+
* so browser clients can read them cross-origin. Use for app-level metadata a
|
|
347
|
+
* client should observe on every call (e.g. a build/version tag). Header names
|
|
348
|
+
* should be lower-case; values are sent verbatim.
|
|
349
|
+
*/
|
|
350
|
+
responseHeaders?: Record<string, string>;
|
|
351
|
+
}
|
|
352
|
+
interface UpstashConfig {
|
|
353
|
+
url: string;
|
|
354
|
+
token: string;
|
|
355
|
+
redisUrl: string;
|
|
356
|
+
}
|
|
357
|
+
/** Database configuration for PostgreSQL */
|
|
358
|
+
interface PostgresConfig {
|
|
359
|
+
type: "postgres";
|
|
360
|
+
connectionString: string;
|
|
361
|
+
/** Optional max connections for the Postgres connection pool. Defaults to 10.*/
|
|
362
|
+
maxConnections?: number;
|
|
363
|
+
/** Optional min connections for the Postgres connection pool. Defaults to 1.*/
|
|
364
|
+
minConnections?: number;
|
|
365
|
+
/** Optional idle timeout for Postgres connections in milliseconds. Defaults to 30000 (30 seconds). */
|
|
366
|
+
idleTimeoutMillis?: number;
|
|
367
|
+
/** Optional connection timeout for acquiring Postgres connections in milliseconds. Defaults to 7000 (7 seconds). */
|
|
368
|
+
connectionTimeoutMillis?: number;
|
|
369
|
+
}
|
|
370
|
+
/** Database configuration for MySQL */
|
|
371
|
+
interface MySQLConfig {
|
|
372
|
+
type: "mysql";
|
|
373
|
+
connectionString: string;
|
|
374
|
+
/** Optional max connections for the MySQL connection pool. Defaults to 20.*/
|
|
375
|
+
maxConnections?: number;
|
|
376
|
+
/** Optional min connections for the MySQL connection pool. Defaults to 1.*/
|
|
377
|
+
minConnections?: number;
|
|
378
|
+
/** Optional idle timeout for MySQL connections in milliseconds. Defaults to 30000 (30 seconds). */
|
|
379
|
+
idleTimeoutMillis?: number;
|
|
380
|
+
/** Optional connection timeout for acquiring MySQL connections in milliseconds. Defaults to 7000 (7 seconds). */
|
|
381
|
+
connectionTimeoutMillis?: number;
|
|
382
|
+
/** Optional flag to queue connection requests when pool is exhausted. Defaults to true (queue requests). */
|
|
383
|
+
queueLimit?: number;
|
|
384
|
+
}
|
|
385
|
+
type DatabaseConfig = PostgresConfig | MySQLConfig;
|
|
386
|
+
/**
|
|
387
|
+
* Configuration for the Supalive WebSocket server.
|
|
388
|
+
*/
|
|
389
|
+
interface SupaliveServerConfig<TContext = Record<string, unknown>> {
|
|
390
|
+
/** Server port (default: 3000) */
|
|
391
|
+
port?: number;
|
|
392
|
+
/** Server host (default: "0.0.0.0") */
|
|
393
|
+
host?: string;
|
|
394
|
+
cacheLayer: CacheLayer;
|
|
395
|
+
redisSubClient: IORedisClient;
|
|
396
|
+
/**
|
|
397
|
+
* Subscription Manager WebSocket URL for RPC communication.
|
|
398
|
+
* A single sub-manager process owns all subscription state for this
|
|
399
|
+
* deployment. Internally it spawns N logical workers (configured on the
|
|
400
|
+
* sub-manager side) and routes by hash(subId) to spread CPU across them.
|
|
401
|
+
*
|
|
402
|
+
* Provide EITHER this OR {@link subManager} (an in-process link). Exactly
|
|
403
|
+
* one is required.
|
|
404
|
+
*/
|
|
405
|
+
subManagerUrl?: string;
|
|
406
|
+
/**
|
|
407
|
+
* In-process subscription-manager link, as an alternative to
|
|
408
|
+
* {@link subManagerUrl}. Use this to embed the sub-manager in the same
|
|
409
|
+
* process as the app server (obtain it from
|
|
410
|
+
* `new SubscriptionManager({ listen: false, inline: true, ... }).localLink()`).
|
|
411
|
+
* The server then talks to the manager via direct method calls — no socket,
|
|
412
|
+
* no port, no serialization. Split the sub-manager into its own process
|
|
413
|
+
* later by switching back to `subManagerUrl` with no other changes.
|
|
414
|
+
*/
|
|
415
|
+
subManager?: SubManagerLink;
|
|
416
|
+
/** Database instance */
|
|
417
|
+
database: SupaliveDb;
|
|
418
|
+
/**
|
|
419
|
+
* Optional authentication callback.
|
|
420
|
+
* Called when client sends auth message.
|
|
421
|
+
* Return null to reject authentication.
|
|
422
|
+
*/
|
|
423
|
+
verifyAuth?: (data: Record<string, unknown>, sessionId: string) => Promise<TContext | null>;
|
|
424
|
+
/**
|
|
425
|
+
* Server context name for procedure routing.
|
|
426
|
+
* Must match the router's contextName.
|
|
427
|
+
*/
|
|
428
|
+
contextName?: string;
|
|
429
|
+
/**
|
|
430
|
+
* Session TTL in seconds (default: 3600 = 1 hour).
|
|
431
|
+
* Sessions are kept alive while connected and expire after disconnect.
|
|
432
|
+
*/
|
|
433
|
+
sessionTtlSeconds?: number;
|
|
434
|
+
/**
|
|
435
|
+
* Observability: structured request logging, OpenTelemetry metrics, and an
|
|
436
|
+
* optional per-operation `onEvent` hook (the seam for app-owned metrics /
|
|
437
|
+
* distributed tracing). The OTel API is a no-op until the host app registers
|
|
438
|
+
* an SDK, so leaving this unset costs nothing. See {@link ObservabilityOptions}.
|
|
439
|
+
*/
|
|
440
|
+
observability?: ObservabilityOptions;
|
|
441
|
+
/**
|
|
442
|
+
* Extract user ID from context for cache key generation.
|
|
443
|
+
* If not provided, caching is disabled (each sub recomputes independently).
|
|
444
|
+
*/
|
|
445
|
+
getUserId?: (ctx: TContext | undefined) => string | null;
|
|
446
|
+
/**
|
|
447
|
+
* Cache TTL in seconds (default: 3600).
|
|
448
|
+
* How long query results are cached in Redis.
|
|
449
|
+
*/
|
|
450
|
+
cacheTtlSeconds?: number;
|
|
451
|
+
/**
|
|
452
|
+
* Scheduler backing the job system. When provided, the server exposes an
|
|
453
|
+
* HTTP endpoint (`{jobPath}/<jobName>`) that the scheduler POSTs to, and
|
|
454
|
+
* syncs every registered recurring (cron) job at startup. Omit to disable
|
|
455
|
+
* jobs entirely. Use `DevScheduler` locally and `QStashScheduler` in prod.
|
|
456
|
+
*/
|
|
457
|
+
scheduler: JobScheduler;
|
|
458
|
+
/**
|
|
459
|
+
* Builds the system server context handed to a job handler's `serverCtx`.
|
|
460
|
+
* Jobs run without a client, so this is where a deployment injects its
|
|
461
|
+
* "system"/service identity. Called per job invocation.
|
|
462
|
+
*/
|
|
463
|
+
jobContext?: () => TContext | Promise<TContext>;
|
|
464
|
+
/**
|
|
465
|
+
* Absolute, externally-reachable base URL of this server (no trailing
|
|
466
|
+
* slash), e.g. `https://api.example.com` in prod or `http://127.0.0.1:3000`
|
|
467
|
+
* in dev. Used to build the job endpoint the scheduler calls.
|
|
468
|
+
*/
|
|
469
|
+
publicUrl: string;
|
|
470
|
+
/**
|
|
471
|
+
* URL path prefix for the job webhook endpoint. Default `/_jobs`. The full
|
|
472
|
+
* endpoint for a job is `{publicUrl}{jobPath}/<jobName>`.
|
|
473
|
+
*/
|
|
474
|
+
jobPath?: string;
|
|
475
|
+
/**
|
|
476
|
+
* Enable the one-shot HTTP RPC transport (query/mutation/action over a
|
|
477
|
+
* single `POST {rpc.path}` endpoint, with `{ name, input }` in the body).
|
|
478
|
+
* Uses the same `verifyAuth` as the WebSocket path. Omit to disable. See
|
|
479
|
+
* {@link RpcConfig}.
|
|
480
|
+
*/
|
|
481
|
+
rpc?: RpcConfig<TContext>;
|
|
482
|
+
/**
|
|
483
|
+
* App-supplied HTTP handler mounted on the SAME server/port as the WebSocket
|
|
484
|
+
* upgrade, the RPC endpoint, and the job webhook. Checked AFTER the built-in
|
|
485
|
+
* routes (`/_health`, the RPC path, `{jobPath}/…`) and BEFORE the 404, so it
|
|
486
|
+
* can add routes without shadowing them.
|
|
487
|
+
*
|
|
488
|
+
* Return `true` (or a promise of it) when the handler has written the
|
|
489
|
+
* response — the server then stops. Return `false`/`undefined` to fall
|
|
490
|
+
* through to the 404. The primary use is exposing an app-owned scrape/health
|
|
491
|
+
* endpoint (e.g. a Prometheus `/metrics`) on this port instead of standing up
|
|
492
|
+
* a second HTTP server — the handler wires the exporter's request handler.
|
|
493
|
+
*/
|
|
494
|
+
onHttpRequest?: (req: IncomingMessage, res: ServerResponse) => boolean | void | Promise<boolean | void>;
|
|
495
|
+
/**
|
|
496
|
+
* Object storage, exposed to every procedure as `ctx.storage`. When set,
|
|
497
|
+
* query/mutation/action/job handlers can presign uploads/downloads and
|
|
498
|
+
* resolve public URLs without reaching for a module-level singleton. Omit
|
|
499
|
+
* to leave `ctx.storage` undefined (handlers should treat uploads as
|
|
500
|
+
* unconfigured). Build one with `S3CompatibleStorage` from
|
|
501
|
+
* `@supalive/core/storage`.
|
|
502
|
+
*/
|
|
503
|
+
storage: ObjectStorage;
|
|
504
|
+
/**
|
|
505
|
+
* Optional application-level Redis pub/sub for cross-instance app messages
|
|
506
|
+
* that are NOT part of the subscription/cache machinery — e.g. auth-token
|
|
507
|
+
* revocations, feature-flag flips, targeted cache busts.
|
|
508
|
+
*
|
|
509
|
+
* The server subscribes to `channels` on its EXISTING Redis subscribe
|
|
510
|
+
* connection (no extra connection) and hands each received message to
|
|
511
|
+
* `onMessage`. Publish from anywhere with
|
|
512
|
+
* `server.publishAppMessage(channel, message)`, which rides the shared
|
|
513
|
+
* command connection. Messages are opaque strings — the app owns encoding.
|
|
514
|
+
*/
|
|
515
|
+
appPubSub?: AppPubSubConfig;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* App-level pub/sub wiring (see {@link SupaliveServerConfig.appPubSub}). A thin,
|
|
519
|
+
* generic cross-instance message bus layered on the server's Redis connections.
|
|
520
|
+
*/
|
|
521
|
+
interface AppPubSubConfig {
|
|
522
|
+
/** Channels this instance subscribes to. */
|
|
523
|
+
channels: string[];
|
|
524
|
+
/**
|
|
525
|
+
* Handler for a message received on one of `channels`. Errors are caught and
|
|
526
|
+
* logged by the server, so a throwing handler can't crash the subscriber.
|
|
527
|
+
*/
|
|
528
|
+
onMessage: (channel: string, message: string) => void | Promise<void>;
|
|
529
|
+
}
|
|
530
|
+
interface SubscriptionManagerConfig {
|
|
531
|
+
/**
|
|
532
|
+
* Port for the rpc-websockets listener. Required unless {@link listen} is
|
|
533
|
+
* `false` (embedded/in-process mode, where no socket is opened).
|
|
534
|
+
*/
|
|
535
|
+
port?: number;
|
|
536
|
+
/**
|
|
537
|
+
* Upstash/Redis connection for the worker's own cache layer. Required in
|
|
538
|
+
* the default (own-pool) mode; omit when reusing an existing {@link cache}
|
|
539
|
+
* via {@link inline}.
|
|
540
|
+
*/
|
|
541
|
+
upstash?: {
|
|
542
|
+
url: string;
|
|
543
|
+
token: string;
|
|
544
|
+
redisUrl: string;
|
|
545
|
+
};
|
|
546
|
+
/**
|
|
547
|
+
* Database connection the worker builds its own pool from. Required in the
|
|
548
|
+
* default mode; omit when reusing an existing {@link db} via {@link inline}.
|
|
549
|
+
*/
|
|
550
|
+
database?: DatabaseConfig;
|
|
551
|
+
cacheTTLSeconds: number;
|
|
552
|
+
/**
|
|
553
|
+
* Open the rpc-websockets listener (default `true`). Set to `false` to
|
|
554
|
+
* embed the manager in another process and drive it via
|
|
555
|
+
* {@link SubscriptionManager.localLink}.
|
|
556
|
+
*/
|
|
557
|
+
listen?: boolean;
|
|
558
|
+
/**
|
|
559
|
+
* Run the subscription worker inline on the main event loop instead of a
|
|
560
|
+
* `worker_threads` Worker (default `false`). Eliminates the postMessage /
|
|
561
|
+
* structured-clone hop, at the cost of no cross-core parallelism — always a
|
|
562
|
+
* single worker. Intended for embedded (`listen: false`) deployments with
|
|
563
|
+
* light realtime load. Provide {@link db} + {@link cache} to reuse the host
|
|
564
|
+
* server's pool/cache rather than opening new ones.
|
|
565
|
+
*/
|
|
566
|
+
inline?: boolean;
|
|
567
|
+
/** In-process `Database` to reuse in {@link inline} mode (borrowed; not closed on stop). */
|
|
568
|
+
db?: Database;
|
|
569
|
+
/** In-process `CacheLayer` to reuse in {@link inline} mode. */
|
|
570
|
+
cache?: CacheLayer;
|
|
571
|
+
/**
|
|
572
|
+
* Number of logical workers inside the sub-manager process.
|
|
573
|
+
*
|
|
574
|
+
* Subscriptions are routed by FNV-1a(subId) % workers so that all operations
|
|
575
|
+
* for a given subId always land on the same worker (preserving the existing
|
|
576
|
+
* `instances: Set<string>` dedup). Each worker owns an independent
|
|
577
|
+
* subscriptions Map; on invalidate, all workers scan their own slice in
|
|
578
|
+
* parallel (logically — JS still runs one at a time inside one event loop,
|
|
579
|
+
* but per-worker scans are smaller so total work is split N ways).
|
|
580
|
+
*
|
|
581
|
+
* Defaults to `SUPALIVE_SUB_MANAGER_WORKERS` env var if set, else 1.
|
|
582
|
+
*/
|
|
583
|
+
workers?: number;
|
|
584
|
+
/**
|
|
585
|
+
* Connection pool size *per worker*. If omitted, falls back to
|
|
586
|
+
* `floor(database.maxConnections / workers)` (min 1) so the total pool
|
|
587
|
+
* size stays close to the legacy single-worker configuration.
|
|
588
|
+
*/
|
|
589
|
+
maxConnectionsPerWorker?: number;
|
|
590
|
+
}
|
|
591
|
+
//#endregion
|
|
592
|
+
//#region src/server/types_server.d.ts
|
|
593
|
+
type InstanceId = string;
|
|
594
|
+
type SubId = string;
|
|
595
|
+
/** Local tracking of an active subscription (per-session) */
|
|
596
|
+
interface SubscriptionEntry {
|
|
597
|
+
subId: SubId;
|
|
598
|
+
cacheKey: string;
|
|
599
|
+
subscribedTimes: number;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* A mutation that has been validated and parked on the per-session FIFO
|
|
603
|
+
* queue, waiting for the in-flight mutation on this socket to finish. Each
|
|
604
|
+
* `handleCall` for a mutation owns one of these and awaits its own
|
|
605
|
+
* resolve/reject so it can send the response.
|
|
606
|
+
*/
|
|
607
|
+
interface PendingMutation<TContext = Record<string, unknown>> {
|
|
608
|
+
procedure: RegisteredProcedure<TContext>;
|
|
609
|
+
validatedInput: unknown;
|
|
610
|
+
resolve: (result: MutationResult<unknown>) => void;
|
|
611
|
+
reject: (err: unknown) => void;
|
|
612
|
+
/** Request-scoped `ctx.log`/`ctx.span`, carried from `handleCall` so the
|
|
613
|
+
* mutation handler (which runs later on the drain loop) still sees them. */
|
|
614
|
+
handlerObs?: HandlerObservability;
|
|
615
|
+
}
|
|
616
|
+
/** WebSocket session (runtime, with WebSocket) */
|
|
617
|
+
interface Session<TContext = Record<string, unknown>> {
|
|
618
|
+
id: string;
|
|
619
|
+
ws: WebSocket;
|
|
620
|
+
/** Auth context from verifyAuth callback */
|
|
621
|
+
serverCtx?: TContext;
|
|
622
|
+
subIds: Set<string>;
|
|
623
|
+
/**
|
|
624
|
+
* Per-socket FIFO mutation queue. at most one mutation
|
|
625
|
+
* drains at a time so a single client cannot saturate the OCC retry
|
|
626
|
+
* loop or fan out concurrent commits that race the same rows.
|
|
627
|
+
*/
|
|
628
|
+
mutationQueue: PendingMutation<TContext>[];
|
|
629
|
+
mutationInFlight: boolean;
|
|
630
|
+
/** Per-socket count of currently executing actions. */
|
|
631
|
+
actionInFlight: number;
|
|
632
|
+
/**
|
|
633
|
+
* Wall-clock of the last inbound frame (text message, ws-ping, or ws-pong).
|
|
634
|
+
* Used by the per-session heartbeat to terminate sockets that have gone
|
|
635
|
+
* silent past `CLIENT_TIMEOUT_MS`.
|
|
636
|
+
*/
|
|
637
|
+
lastReceivedAt: number;
|
|
638
|
+
/**
|
|
639
|
+
* Wall-clock of the last outbound frame. Gates the idle-only app-level
|
|
640
|
+
* ping so a busy session (frequent sub:updates etc.) doesn't emit a
|
|
641
|
+
* redundant `{type:"ping"}` on every heartbeat tick.
|
|
642
|
+
*/
|
|
643
|
+
lastSentAt: number;
|
|
644
|
+
/**
|
|
645
|
+
* Per-session combined heartbeat. Single `setInterval` that handles
|
|
646
|
+
* both dead-TCP detection (ws-ping or terminate) and client-watchdog
|
|
647
|
+
* refresh (idle-only app-level ping) on each tick.
|
|
648
|
+
*/
|
|
649
|
+
heartbeatTimer: ReturnType<typeof setInterval> | null;
|
|
650
|
+
}
|
|
651
|
+
declare const RemoteSubscriptionUpdateSchema: z.ZodObject<{
|
|
652
|
+
type: z.ZodLiteral<"update">;
|
|
653
|
+
subId: z.ZodString;
|
|
654
|
+
data: z.ZodUnknown;
|
|
655
|
+
dataHash: z.ZodString;
|
|
656
|
+
originInstance: z.ZodString;
|
|
657
|
+
ts: z.ZodOptional<z.ZodString>;
|
|
658
|
+
}, z.core.$strip>;
|
|
659
|
+
type RemoteSubscriptionUpdate = z.infer<typeof RemoteSubscriptionUpdateSchema>;
|
|
660
|
+
declare const RemoteRecomputeRaceSchema: z.ZodObject<{
|
|
661
|
+
type: z.ZodLiteral<"recompute-race">;
|
|
662
|
+
subId: z.ZodString;
|
|
663
|
+
cacheKey: z.ZodString;
|
|
664
|
+
queryName: z.ZodString;
|
|
665
|
+
args: z.ZodUnknown;
|
|
666
|
+
notifyInstances: z.ZodArray<z.ZodString>;
|
|
667
|
+
commitTs: z.ZodString;
|
|
668
|
+
originInstance: z.ZodString;
|
|
669
|
+
}, z.core.$strip>;
|
|
670
|
+
type RemoteRecomputeRace = z.infer<typeof RemoteRecomputeRaceSchema>;
|
|
671
|
+
declare const RemoteSubscriptionMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
672
|
+
type: z.ZodLiteral<"update">;
|
|
673
|
+
subId: z.ZodString;
|
|
674
|
+
data: z.ZodUnknown;
|
|
675
|
+
dataHash: z.ZodString;
|
|
676
|
+
originInstance: z.ZodString;
|
|
677
|
+
ts: z.ZodOptional<z.ZodString>;
|
|
678
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
679
|
+
type: z.ZodLiteral<"recompute-race">;
|
|
680
|
+
subId: z.ZodString;
|
|
681
|
+
cacheKey: z.ZodString;
|
|
682
|
+
queryName: z.ZodString;
|
|
683
|
+
args: z.ZodUnknown;
|
|
684
|
+
notifyInstances: z.ZodArray<z.ZodString>;
|
|
685
|
+
commitTs: z.ZodString;
|
|
686
|
+
originInstance: z.ZodString;
|
|
687
|
+
}, z.core.$strip>], "type">;
|
|
688
|
+
type RemoteSubscriptionMessage = z.infer<typeof RemoteSubscriptionMessageSchema>;
|
|
689
|
+
//#endregion
|
|
690
|
+
export { QueryTrigger as A, SubManagerLink as C, OperationKind as D, OperationEvent as E, startTimer as F, traceQueryExecution as I, configureRootLogger as M, elapsedMs as N, OperationOutcome as O, getRootLogger as P, SubManagerClient as S, ObservabilityOptions as T, RpcCorsConfig as _, RemoteSubscriptionMessage as a, SupaliveServerConfig as b, RemoteSubscriptionUpdateSchema as c, SubscriptionEntry as d, AppPubSubConfig as f, RpcConfig as g, PostgresConfig as h, RemoteRecomputeRaceSchema as i, RequestLogMode as j, OperationTransport as k, Session as l, MySQLConfig as m, PendingMutation as n, RemoteSubscriptionMessageSchema as o, DatabaseConfig as p, RemoteRecomputeRace as r, RemoteSubscriptionUpdate as s, InstanceId as t, SubId as u, RpcRateLimitConfig as v, Observability as w, UpstashConfig as x, SubscriptionManagerConfig as y };
|
|
691
|
+
//# sourceMappingURL=types_server-BXeiSmd8.d.ts.map
|