@supalive/core 1.12.0 → 1.13.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/db-ClCicssQ.js.map +1 -1
- package/dist/helper-zdJT5FUc.js +58 -0
- package/dist/helper-zdJT5FUc.js.map +1 -0
- package/dist/index-BIIcgvu8.d.ts +2300 -0
- package/dist/index-BIIcgvu8.d.ts.map +1 -0
- package/dist/index-BedlCLem.d.ts +2292 -0
- package/dist/index-BedlCLem.d.ts.map +1 -0
- package/dist/index-BnV8LnOf.d.ts +2286 -0
- package/dist/index-BnV8LnOf.d.ts.map +1 -0
- package/dist/index-BziK63TZ.d.ts +2271 -0
- package/dist/index-BziK63TZ.d.ts.map +1 -0
- package/dist/index-D31hMBDE.d.ts +2288 -0
- package/dist/index-D31hMBDE.d.ts.map +1 -0
- package/dist/index-DZhReQWB.d.ts +2288 -0
- package/dist/index-DZhReQWB.d.ts.map +1 -0
- package/dist/index-DlG8HDCO.d.ts +2215 -0
- package/dist/index-DlG8HDCO.d.ts.map +1 -0
- package/dist/index-Wgi_dGzj.d.ts +2215 -0
- package/dist/index-Wgi_dGzj.d.ts.map +1 -0
- package/dist/index-j3YTMNlF.d.ts +2223 -0
- package/dist/index-j3YTMNlF.d.ts.map +1 -0
- package/dist/mysql-BBbilD4I.js +614 -0
- package/dist/mysql-BBbilD4I.js.map +1 -0
- package/dist/mysql-BHJ2mrwZ.d.ts +113 -0
- package/dist/mysql-BHJ2mrwZ.d.ts.map +1 -0
- package/dist/mysql-BQbHJ_rK.js +617 -0
- package/dist/mysql-BQbHJ_rK.js.map +1 -0
- package/dist/mysql-BXoAON7c.d.ts +113 -0
- package/dist/mysql-BXoAON7c.d.ts.map +1 -0
- package/dist/mysql-BgfCUYGt.d.ts +113 -0
- package/dist/mysql-BgfCUYGt.d.ts.map +1 -0
- package/dist/mysql-Br7MqUXg.js +620 -0
- package/dist/mysql-Br7MqUXg.js.map +1 -0
- package/dist/mysql-BrueWtO9.d.ts +113 -0
- package/dist/mysql-BrueWtO9.d.ts.map +1 -0
- package/dist/mysql-CCo--z6n.d.ts +113 -0
- package/dist/mysql-CCo--z6n.d.ts.map +1 -0
- package/dist/mysql-CQcbcZB8.d.ts +113 -0
- package/dist/mysql-CQcbcZB8.d.ts.map +1 -0
- package/dist/mysql-CbgCY89y.d.ts +113 -0
- package/dist/mysql-CbgCY89y.d.ts.map +1 -0
- package/dist/mysql-CqD1ZnNY.js +619 -0
- package/dist/mysql-CqD1ZnNY.js.map +1 -0
- package/dist/mysql-D3Db2q3_.js +619 -0
- package/dist/mysql-D3Db2q3_.js.map +1 -0
- package/dist/mysql-DBrhTmYm.js +619 -0
- package/dist/mysql-DBrhTmYm.js.map +1 -0
- package/dist/mysql-DX7YA7IS.d.ts +113 -0
- package/dist/mysql-DX7YA7IS.d.ts.map +1 -0
- package/dist/mysql-DZ0_Rsjc.js +614 -0
- package/dist/mysql-DZ0_Rsjc.js.map +1 -0
- package/dist/mysql-Dko1MJqQ.js +619 -0
- package/dist/mysql-Dko1MJqQ.js.map +1 -0
- package/dist/mysql-F831ycnw.d.ts +113 -0
- package/dist/mysql-F831ycnw.d.ts.map +1 -0
- package/dist/mysql-TLPG62tv.js +618 -0
- package/dist/mysql-TLPG62tv.js.map +1 -0
- package/dist/mysql-e-ii74yG.js +613 -0
- package/dist/mysql-e-ii74yG.js.map +1 -0
- package/dist/overlap-checker-BBkqRdcs.js +259 -0
- package/dist/overlap-checker-BBkqRdcs.js.map +1 -0
- package/dist/overlap-checker-DHUJe-Yy.js +259 -0
- package/dist/overlap-checker-DHUJe-Yy.js.map +1 -0
- package/dist/overlap-checker-DWeQLpSD.js +259 -0
- package/dist/overlap-checker-DWeQLpSD.js.map +1 -0
- package/dist/overlap-checker-Kz7B_jsp.js +259 -0
- package/dist/overlap-checker-Kz7B_jsp.js.map +1 -0
- package/dist/postgres-7hJuQX4Y.d.ts +117 -0
- package/dist/postgres-7hJuQX4Y.d.ts.map +1 -0
- package/dist/postgres-B4H78IkW.js +864 -0
- package/dist/postgres-B4H78IkW.js.map +1 -0
- package/dist/postgres-B4h8D5U2.d.ts +117 -0
- package/dist/postgres-B4h8D5U2.d.ts.map +1 -0
- package/dist/postgres-BXFPamkP.js +864 -0
- package/dist/postgres-BXFPamkP.js.map +1 -0
- package/dist/postgres-CoR3TUMd.js +865 -0
- package/dist/postgres-CoR3TUMd.js.map +1 -0
- package/dist/postgres-D-RsG12a.d.ts +117 -0
- package/dist/postgres-D-RsG12a.d.ts.map +1 -0
- package/dist/postgres-D5Z1S3J9.d.ts +117 -0
- package/dist/postgres-D5Z1S3J9.d.ts.map +1 -0
- package/dist/postgres-DHs89iWT.js +864 -0
- package/dist/postgres-DHs89iWT.js.map +1 -0
- package/dist/postgres-DvBIRnYA.d.ts +117 -0
- package/dist/postgres-DvBIRnYA.d.ts.map +1 -0
- package/dist/postgres-Eo-lpUiX.d.ts +117 -0
- package/dist/postgres-Eo-lpUiX.d.ts.map +1 -0
- package/dist/postgres-VLAGIFLR.d.ts +117 -0
- package/dist/postgres-VLAGIFLR.d.ts.map +1 -0
- package/dist/postgres-hrQmDPUp.d.ts +117 -0
- package/dist/postgres-hrQmDPUp.d.ts.map +1 -0
- package/dist/postgres-pXrP9Fx0.d.ts +117 -0
- package/dist/postgres-pXrP9Fx0.d.ts.map +1 -0
- package/dist/query-BuOs9w-U.js +730 -0
- package/dist/query-BuOs9w-U.js.map +1 -0
- package/dist/query-Xw2hi6TB.js +730 -0
- package/dist/query-Xw2hi6TB.js.map +1 -0
- package/dist/read-routing-BUcQAwnd.js +62 -0
- package/dist/read-routing-BUcQAwnd.js.map +1 -0
- package/dist/read-routing-BXflWzQI.js +61 -0
- package/dist/read-routing-BXflWzQI.js.map +1 -0
- package/dist/read-routing-CcsLp0ow.js +61 -0
- package/dist/read-routing-CcsLp0ow.js.map +1 -0
- package/dist/realtime_db-B70vqtRZ.js +272 -0
- package/dist/realtime_db-B70vqtRZ.js.map +1 -0
- package/dist/realtime_db-BMysrWym.js +312 -0
- package/dist/realtime_db-BMysrWym.js.map +1 -0
- package/dist/realtime_db-Bi7S938D.js +312 -0
- package/dist/realtime_db-Bi7S938D.js.map +1 -0
- package/dist/realtime_db-CVze3gM0.js +312 -0
- package/dist/realtime_db-CVze3gM0.js.map +1 -0
- package/dist/realtime_db-DMipkUmD.js +312 -0
- package/dist/realtime_db-DMipkUmD.js.map +1 -0
- package/dist/realtime_db-XWzuq2JS.js +272 -0
- package/dist/realtime_db-XWzuq2JS.js.map +1 -0
- package/dist/router-Kgrlci8X.js +337 -0
- package/dist/router-Kgrlci8X.js.map +1 -0
- package/dist/router-g8qSQb8m.js +314 -0
- package/dist/router-g8qSQb8m.js.map +1 -0
- package/dist/schema-DLue3K2s.js +134 -0
- package/dist/schema-DLue3K2s.js.map +1 -0
- package/dist/schema-DfowI0ip.js +134 -0
- package/dist/schema-DfowI0ip.js.map +1 -0
- package/dist/schema-fKldWzwc.js +134 -0
- package/dist/schema-fKldWzwc.js.map +1 -0
- package/dist/src/client/index.d.ts +1 -1
- package/dist/src/client/index.js +69 -29
- 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/procedure.js +5 -6
- package/dist/src/exports/procedure.js.map +1 -1
- package/dist/src/exports/schema-sql.d.ts +18 -1
- package/dist/src/exports/schema-sql.d.ts.map +1 -1
- package/dist/src/exports/schema-sql.js +91 -15
- package/dist/src/exports/schema-sql.js.map +1 -1
- package/dist/src/exports/server.d.ts +11 -4
- package/dist/src/exports/server.d.ts.map +1 -1
- package/dist/src/exports/server.js +136 -59
- 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 +2 -2
- package/dist/src/exports/types.js +4 -4
- package/dist/sub-worker-dispatch-CO9nVdSG.js +813 -0
- package/dist/sub-worker-dispatch-CO9nVdSG.js.map +1 -0
- package/dist/sub-worker-dispatch-CQBknEL7.js +813 -0
- package/dist/sub-worker-dispatch-CQBknEL7.js.map +1 -0
- package/dist/sub-worker-dispatch-CQHeit6D.js +813 -0
- package/dist/sub-worker-dispatch-CQHeit6D.js.map +1 -0
- package/dist/sub-worker-dispatch-CmEW10it.js +813 -0
- package/dist/sub-worker-dispatch-CmEW10it.js.map +1 -0
- package/dist/sub-worker-dispatch-CunxuOPT.js +823 -0
- package/dist/sub-worker-dispatch-CunxuOPT.js.map +1 -0
- package/dist/sub-worker-dispatch-CvvFm40r.js +817 -0
- package/dist/sub-worker-dispatch-CvvFm40r.js.map +1 -0
- package/dist/sub-worker-dispatch-D15UHo1g.js +813 -0
- package/dist/sub-worker-dispatch-D15UHo1g.js.map +1 -0
- package/dist/sub-worker-dispatch-D8DY56ry.js +817 -0
- package/dist/sub-worker-dispatch-D8DY56ry.js.map +1 -0
- package/dist/sub-worker-dispatch-DJgWxtSQ.js +823 -0
- package/dist/sub-worker-dispatch-DJgWxtSQ.js.map +1 -0
- package/dist/sub-worker-dispatch-Dplqeubd.js +813 -0
- package/dist/sub-worker-dispatch-Dplqeubd.js.map +1 -0
- package/dist/sub-worker-dispatch-dXjUmSMN.js +813 -0
- package/dist/sub-worker-dispatch-dXjUmSMN.js.map +1 -0
- package/dist/table-id-BJdmctEQ.js +91 -0
- package/dist/table-id-BJdmctEQ.js.map +1 -0
- package/dist/table-id-DyrJBKAQ.js +105 -0
- package/dist/table-id-DyrJBKAQ.js.map +1 -0
- package/dist/types_client_rpc-BGMwPJst.js +123 -0
- package/dist/types_client_rpc-BGMwPJst.js.map +1 -0
- package/dist/types_client_rpc-ByGwoRCL.js +132 -0
- package/dist/types_client_rpc-ByGwoRCL.js.map +1 -0
- package/dist/types_db-BsR02Sph.js +171 -0
- package/dist/types_db-BsR02Sph.js.map +1 -0
- package/dist/types_db-CgGlOMmr.js +187 -0
- package/dist/types_db-CgGlOMmr.js.map +1 -0
- package/dist/types_db-OUou3o2Z.js +172 -0
- package/dist/types_db-OUou3o2Z.js.map +1 -0
- package/dist/types_server-B3z8hi-Z.d.ts +523 -0
- package/dist/types_server-B3z8hi-Z.d.ts.map +1 -0
- package/dist/types_server-BXBvz4T8.d.ts +523 -0
- package/dist/types_server-BXBvz4T8.d.ts.map +1 -0
- package/dist/types_server-BhC19R2a.js +27 -0
- package/dist/types_server-BhC19R2a.js.map +1 -0
- package/dist/types_server-CClUPZn1.d.ts +521 -0
- package/dist/types_server-CClUPZn1.d.ts.map +1 -0
- package/dist/types_server-CnYTSPhM.d.ts +523 -0
- package/dist/types_server-CnYTSPhM.d.ts.map +1 -0
- package/dist/types_server-DHpGUHB_.d.ts +521 -0
- package/dist/types_server-DHpGUHB_.d.ts.map +1 -0
- package/dist/types_server-Ded1u3bL.d.ts +521 -0
- package/dist/types_server-Ded1u3bL.d.ts.map +1 -0
- package/dist/types_server-fons7nmo.d.ts +523 -0
- package/dist/types_server-fons7nmo.d.ts.map +1 -0
- package/dist/types_server-okvBG4v_.d.ts +521 -0
- package/dist/types_server-okvBG4v_.d.ts.map +1 -0
- package/dist/types_server-pD8DiI7s.d.ts +521 -0
- package/dist/types_server-pD8DiI7s.d.ts.map +1 -0
- package/package.json +1 -1
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { n as getContextRegistry } from "../../router-
|
|
2
|
-
import { n as stableStringify, r as supaliveStringify, t as groupByToMap } from "../../helper-
|
|
3
|
-
import { r as evaluateCacheFreshness } from "../../overlap-checker-
|
|
4
|
-
import { i as AuthenticationError, o as ClientMessageSchema } from "../../types_client_rpc-
|
|
5
|
-
import { n as RemoteSubscriptionMessageSchema } from "../../types_server-
|
|
6
|
-
import { n as sleep } from "../../realtime_db-
|
|
7
|
-
import {
|
|
1
|
+
import { n as getContextRegistry, u as primaryOnlyConn } from "../../router-Kgrlci8X.js";
|
|
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";
|
|
5
|
+
import { n as RemoteSubscriptionMessageSchema } from "../../types_server-BhC19R2a.js";
|
|
6
|
+
import { n as sleep } from "../../realtime_db-CVze3gM0.js";
|
|
7
|
+
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-DJgWxtSQ.js";
|
|
9
|
+
import { v4 } from "uuid";
|
|
8
10
|
import { createServer } from "http";
|
|
9
11
|
import { WebSocket, WebSocketServer } from "ws";
|
|
10
|
-
import { v4 } from "uuid";
|
|
11
12
|
import { ExponentialBackoff, handleAll, retry } from "cockatiel";
|
|
12
13
|
import { sha1 } from "hash-wasm";
|
|
13
14
|
import { Client, Server } from "rpc-websockets";
|
|
@@ -78,43 +79,65 @@ async function generateCacheKey(queryName, input, queryIdentity) {
|
|
|
78
79
|
*
|
|
79
80
|
* Flow (mirrors `handleSubscribe` / worker `register`):
|
|
80
81
|
* 1. Resolve `queryIdentity` → `cacheKey` (identical segmentation to subs).
|
|
81
|
-
* 2.
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
82
|
+
* 2. Pick the read connection ONCE (replica when it's caught up to the client's
|
|
83
|
+
* `clientMinTs`, else primary) and use it for BOTH the freshness scan and
|
|
84
|
+
* the row reads, so a cache HIT offloads the primary too — not just a miss.
|
|
85
|
+
* 3. Freshness check via {@link evaluateCacheFreshness} on that connection:
|
|
86
|
+
* cached metadata + the retention watermark + a between-ts commit-log scan.
|
|
87
|
+
* 4. Fresh → return the cached data.
|
|
88
|
+
* Stale/miss → recompute via `queryInternalOn` (on the chosen connection)
|
|
89
|
+
* and write BOTH the data and the metadata (readSet + snapshotTs) back, so
|
|
90
|
+
* a later one-shot or a fresh subscription can reuse it.
|
|
87
91
|
*
|
|
88
92
|
* The retention guard is what makes an untracked one-shot safe: nothing holds
|
|
89
93
|
* the prune watermark down for it, so an entry whose snapshot has fallen below
|
|
90
94
|
* the oldest retained commit log is treated as a miss (its freshness scan would
|
|
91
95
|
* be incomplete).
|
|
92
96
|
*
|
|
93
|
-
* `lastSnapshotTs` is the freshness upper bound — the latest snapshot ts
|
|
94
|
-
* this call. The caller loads it (via `db.getLatestSnapshotTimestamp()`)
|
|
95
|
-
* passes it in, exactly like the subscribe path loads it before `register`,
|
|
96
|
-
* the snapshot read lives at the caller seam rather than being hidden here.
|
|
97
|
+
* `lastSnapshotTs` is the primary freshness upper bound — the latest snapshot ts
|
|
98
|
+
* as of this call. The caller loads it (via `db.getLatestSnapshotTimestamp()`)
|
|
99
|
+
* and passes it in, exactly like the subscribe path loads it before `register`,
|
|
100
|
+
* so the snapshot read lives at the caller seam rather than being hidden here.
|
|
101
|
+
*
|
|
102
|
+
* `clientMinTs` is the caller's read high-water (the largest commit/snapshot ts
|
|
103
|
+
* it has already observed). It gates replica routing: a query only runs on the
|
|
104
|
+
* replica when the replica has applied at least `clientMinTs` — otherwise reads
|
|
105
|
+
* (and the freshness scan) stay on the primary, preserving read-your-writes and
|
|
106
|
+
* monotonic reads. It is clamped to `lastSnapshotTs` here, so a client can at
|
|
107
|
+
* most force the primary (never a value beyond it). Returns the data plus the
|
|
108
|
+
* `ts` the result reflects, which the caller echoes so the client can advance
|
|
109
|
+
* its high-water.
|
|
97
110
|
*/
|
|
98
|
-
async function runCachedQuery(deps, procedure, procedureName, input, serverCtx, lastSnapshotTs) {
|
|
111
|
+
async function runCachedQuery(deps, procedure, procedureName, input, serverCtx, lastSnapshotTs, clientMinTs) {
|
|
99
112
|
const queryIdentity = resolveQueryIdentity(procedure, serverCtx, input, deps.getUserId);
|
|
100
113
|
const cacheKey = await generateCacheKey(procedureName, stableStringify(input), queryIdentity);
|
|
114
|
+
const routing = await resolveReadRouting(deps.db, lastSnapshotTs, clientMinTs ?? 0n);
|
|
115
|
+
const readTs = routing.readTs;
|
|
101
116
|
const metadata = await deps.cache.getQueryCacheMetaData(cacheKey);
|
|
102
|
-
if ((await evaluateCacheFreshness(
|
|
117
|
+
if ((await evaluateCacheFreshness(routing.readImpl, metadata, readTs, deps.getMinRetainedTs())).fresh) {
|
|
103
118
|
const cached = await deps.cache.getQueryCacheFor(cacheKey);
|
|
104
119
|
if (cached && metadata && cached.version === metadata.version) {
|
|
105
|
-
if (
|
|
106
|
-
lastSnapshotTs,
|
|
120
|
+
if (readTs > metadata.lastSnapshotTs) deps.cache.advanceQueryCacheMetadata(cacheKey, {
|
|
121
|
+
lastSnapshotTs: readTs,
|
|
107
122
|
version: metadata.version,
|
|
108
123
|
readSet: metadata.readSet
|
|
109
124
|
}).catch((err) => logger.error(err, "one-shot horizon advance failed"));
|
|
110
|
-
|
|
125
|
+
const servedTs = readTs > metadata.lastSnapshotTs ? readTs : metadata.lastSnapshotTs;
|
|
126
|
+
return {
|
|
127
|
+
data: cached.data,
|
|
128
|
+
ts: servedTs
|
|
129
|
+
};
|
|
111
130
|
}
|
|
112
131
|
}
|
|
113
|
-
const result = await deps.db.
|
|
132
|
+
const result = await deps.db.queryInternalOn({
|
|
133
|
+
impl: routing.readImpl,
|
|
134
|
+
beginTs: readTs
|
|
135
|
+
}, async (ctx) => {
|
|
114
136
|
const fn = procedure.fn;
|
|
115
137
|
return fn({
|
|
116
138
|
db: ctx,
|
|
117
|
-
serverCtx
|
|
139
|
+
serverCtx,
|
|
140
|
+
...makeConnRouting(ctx, routing)
|
|
118
141
|
}, input);
|
|
119
142
|
});
|
|
120
143
|
await deps.cache.setQueryCacheAndMetadataFor(cacheKey, result.data, {
|
|
@@ -122,7 +145,10 @@ async function runCachedQuery(deps, procedure, procedureName, input, serverCtx,
|
|
|
122
145
|
version: String(result.ts),
|
|
123
146
|
readSet: result.readSet
|
|
124
147
|
}).catch((err) => logger.error(err, "one-shot query cache write failed"));
|
|
125
|
-
return
|
|
148
|
+
return {
|
|
149
|
+
data: result.data,
|
|
150
|
+
ts: result.ts
|
|
151
|
+
};
|
|
126
152
|
}
|
|
127
153
|
//#endregion
|
|
128
154
|
//#region src/server/sub-manager-client.ts
|
|
@@ -595,11 +621,13 @@ var HttpRpcHandler = class {
|
|
|
595
621
|
const rawBody = await readBody(req);
|
|
596
622
|
let name;
|
|
597
623
|
let rawInput;
|
|
624
|
+
let minTs;
|
|
598
625
|
try {
|
|
599
626
|
const envelope = rawBody.trim().length > 0 ? JSON.parse(rawBody) : {};
|
|
600
627
|
if (!envelope || typeof envelope !== "object" || typeof envelope.name !== "string") throw new Error("missing procedure name");
|
|
601
628
|
name = envelope.name;
|
|
602
629
|
rawInput = envelope.input ?? {};
|
|
630
|
+
minTs = typeof envelope.minTs === "string" ? parseMinTs$1(envelope.minTs) : void 0;
|
|
603
631
|
} catch {
|
|
604
632
|
return this.send(res, 400, {
|
|
605
633
|
success: false,
|
|
@@ -631,10 +659,11 @@ var HttpRpcHandler = class {
|
|
|
631
659
|
});
|
|
632
660
|
}
|
|
633
661
|
try {
|
|
634
|
-
const data = await this.runProcedure(procedure, input, auth.ctx);
|
|
662
|
+
const { data, ts } = await this.runProcedure(procedure, input, auth.ctx, minTs);
|
|
635
663
|
return this.send(res, 200, {
|
|
636
664
|
success: true,
|
|
637
|
-
data
|
|
665
|
+
data,
|
|
666
|
+
ts: ts?.toString()
|
|
638
667
|
});
|
|
639
668
|
} catch (err) {
|
|
640
669
|
logger$3.error(err, `RPC "${name}" failed`);
|
|
@@ -645,25 +674,32 @@ var HttpRpcHandler = class {
|
|
|
645
674
|
});
|
|
646
675
|
}
|
|
647
676
|
}
|
|
648
|
-
async runProcedure(procedure, input, serverCtx) {
|
|
677
|
+
async runProcedure(procedure, input, serverCtx, minTs) {
|
|
649
678
|
if (procedure.type === "query") {
|
|
650
679
|
const lastSnapshotTs = await this.deps.db.impl.getLatestSnapshotTimestamp();
|
|
651
|
-
return runCachedQuery(this.deps.oneShotDeps, procedure, procedure.name, input, serverCtx, lastSnapshotTs);
|
|
680
|
+
return runCachedQuery(this.deps.oneShotDeps, procedure, procedure.name, input, serverCtx, lastSnapshotTs, minTs);
|
|
681
|
+
}
|
|
682
|
+
if (procedure.type === "mutation") {
|
|
683
|
+
const res = await this.deps.db.mutation(async (ctx) => {
|
|
684
|
+
const fn = procedure.fn;
|
|
685
|
+
return fn({
|
|
686
|
+
db: ctx,
|
|
687
|
+
serverCtx,
|
|
688
|
+
...primaryOnlyConn
|
|
689
|
+
}, input);
|
|
690
|
+
});
|
|
691
|
+
return {
|
|
692
|
+
data: res.result,
|
|
693
|
+
ts: res.commitTs
|
|
694
|
+
};
|
|
652
695
|
}
|
|
653
|
-
if (procedure.type === "mutation") return (await this.deps.db.mutation(async (ctx) => {
|
|
654
|
-
const fn = procedure.fn;
|
|
655
|
-
return fn({
|
|
656
|
-
db: ctx,
|
|
657
|
-
serverCtx
|
|
658
|
-
}, input);
|
|
659
|
-
})).result;
|
|
660
696
|
const fn = procedure.fn;
|
|
661
|
-
return fn({
|
|
697
|
+
return { data: await fn({
|
|
662
698
|
db: this.deps.db,
|
|
663
699
|
serverCtx,
|
|
664
700
|
storage: this.deps.storage,
|
|
665
701
|
scheduler: this.deps.scheduler
|
|
666
|
-
}, input);
|
|
702
|
+
}, input) };
|
|
667
703
|
}
|
|
668
704
|
async resolveAuth(req) {
|
|
669
705
|
const data = (this.rpc.auth ?? defaultHttpAuth)(req);
|
|
@@ -711,6 +747,16 @@ var HttpRpcHandler = class {
|
|
|
711
747
|
res.end(supaliveStringify(body));
|
|
712
748
|
}
|
|
713
749
|
};
|
|
750
|
+
/** Parse a wire `minTs` (decimal bigint string) to a bigint, or undefined when
|
|
751
|
+
* absent/malformed — a bad value falls back to primary-safe routing. */
|
|
752
|
+
function parseMinTs$1(minTs) {
|
|
753
|
+
if (minTs === void 0) return void 0;
|
|
754
|
+
try {
|
|
755
|
+
return BigInt(minTs);
|
|
756
|
+
} catch {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
714
760
|
function normalizeRpcPath(p) {
|
|
715
761
|
let s = p.startsWith("/") ? p : `/${p}`;
|
|
716
762
|
if (s.length > 1 && s.endsWith("/")) s = s.slice(0, -1);
|
|
@@ -1127,7 +1173,7 @@ var SupaliveWebSocketServer = class {
|
|
|
1127
1173
|
await this.handleAuth(session, msg.data);
|
|
1128
1174
|
break;
|
|
1129
1175
|
case "call":
|
|
1130
|
-
await this.handleCall(session, msg.id, msg.procedure, msg.input);
|
|
1176
|
+
await this.handleCall(session, msg.id, msg.procedure, msg.input, parseMinTs(msg.minTs));
|
|
1131
1177
|
break;
|
|
1132
1178
|
case "subscribe":
|
|
1133
1179
|
await this.handleSubscribe(session, msg.id, msg.procedure, msg.input);
|
|
@@ -1206,7 +1252,8 @@ var SupaliveWebSocketServer = class {
|
|
|
1206
1252
|
const result = await this.db.mutation(async (ctx) => {
|
|
1207
1253
|
return fn({
|
|
1208
1254
|
db: ctx,
|
|
1209
|
-
serverCtx: session.serverCtx
|
|
1255
|
+
serverCtx: session.serverCtx,
|
|
1256
|
+
...primaryOnlyConn
|
|
1210
1257
|
}, op.validatedInput);
|
|
1211
1258
|
});
|
|
1212
1259
|
op.resolve(result);
|
|
@@ -1218,7 +1265,7 @@ var SupaliveWebSocketServer = class {
|
|
|
1218
1265
|
session.mutationInFlight = false;
|
|
1219
1266
|
}
|
|
1220
1267
|
}
|
|
1221
|
-
async handleCall(session, id, procedureName, input) {
|
|
1268
|
+
async handleCall(session, id, procedureName, input, minTs) {
|
|
1222
1269
|
if (!this.router) {
|
|
1223
1270
|
this.sendError(session, id, "Router not registered", "SERVER_ERROR");
|
|
1224
1271
|
return;
|
|
@@ -1232,12 +1279,13 @@ var SupaliveWebSocketServer = class {
|
|
|
1232
1279
|
const validatedInput = procedure.inputSchema.parse(input);
|
|
1233
1280
|
if (procedure.type === "query") {
|
|
1234
1281
|
const lastSnapshotTs = await this.db.impl.getLatestSnapshotTimestamp();
|
|
1235
|
-
const
|
|
1282
|
+
const { data, ts } = await runCachedQuery(this.oneShotDeps, procedure, procedureName, validatedInput, session.serverCtx, lastSnapshotTs, minTs);
|
|
1236
1283
|
this.sendMessage(session, {
|
|
1237
1284
|
type: "response",
|
|
1238
1285
|
id,
|
|
1239
1286
|
success: true,
|
|
1240
|
-
data
|
|
1287
|
+
data,
|
|
1288
|
+
ts: ts.toString()
|
|
1241
1289
|
});
|
|
1242
1290
|
} else if (procedure.type === "mutation") {
|
|
1243
1291
|
if (session.mutationQueue.length >= MAX_MUTATION_QUEUE_BUFFER) {
|
|
@@ -1249,7 +1297,8 @@ var SupaliveWebSocketServer = class {
|
|
|
1249
1297
|
type: "response",
|
|
1250
1298
|
id,
|
|
1251
1299
|
success: true,
|
|
1252
|
-
data: mutationResult.result
|
|
1300
|
+
data: mutationResult.result,
|
|
1301
|
+
ts: mutationResult.commitTs.toString()
|
|
1253
1302
|
});
|
|
1254
1303
|
} else if (procedure.type === "action") {
|
|
1255
1304
|
if (session.actionInFlight >= MAX_ACTION_CONCURRENCY) {
|
|
@@ -1301,14 +1350,18 @@ var SupaliveWebSocketServer = class {
|
|
|
1301
1350
|
let data;
|
|
1302
1351
|
let dataHash;
|
|
1303
1352
|
let liveResult = null;
|
|
1353
|
+
let servedTs;
|
|
1304
1354
|
session.subIds.add(subId);
|
|
1305
1355
|
if (existingServerSub) {
|
|
1306
1356
|
existingServerSub.subscribedTimes += 1;
|
|
1307
1357
|
const cached = await this.cache.getQueryCacheFor(existingServerSub.cacheKey);
|
|
1308
|
-
if (cached)
|
|
1309
|
-
|
|
1358
|
+
if (cached) {
|
|
1359
|
+
data = cached.data;
|
|
1360
|
+
servedTs = cached.lastSnapshotTs;
|
|
1361
|
+
} else {
|
|
1310
1362
|
liveResult = await this.executeLiveQuery(procedureName, validatedInput, session.serverCtx);
|
|
1311
1363
|
data = liveResult.data;
|
|
1364
|
+
servedTs = liveResult.ts;
|
|
1312
1365
|
}
|
|
1313
1366
|
} else {
|
|
1314
1367
|
const lastSnapshotTs = await this.db.impl.getLatestSnapshotTimestamp();
|
|
@@ -1321,11 +1374,19 @@ var SupaliveWebSocketServer = class {
|
|
|
1321
1374
|
instanceName: this.instanceId,
|
|
1322
1375
|
lastSnapshotTs
|
|
1323
1376
|
});
|
|
1324
|
-
|
|
1325
|
-
if (
|
|
1326
|
-
|
|
1377
|
+
let rebuild = true;
|
|
1378
|
+
if (!subManagerResult.recompute) {
|
|
1379
|
+
const cached = await this.cache.getQueryCacheFor(subManagerResult.cacheKey);
|
|
1380
|
+
if (cached && cached.version === subManagerResult.version) {
|
|
1381
|
+
data = cached.data;
|
|
1382
|
+
servedTs = cached.lastSnapshotTs;
|
|
1383
|
+
rebuild = false;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
if (rebuild) {
|
|
1327
1387
|
liveResult = await this.executeLiveQuery(procedureName, validatedInput, session.serverCtx);
|
|
1328
1388
|
data = liveResult.data;
|
|
1389
|
+
servedTs = liveResult.ts;
|
|
1329
1390
|
}
|
|
1330
1391
|
this.serverSubscriptions.set(subId, {
|
|
1331
1392
|
subId,
|
|
@@ -1341,7 +1402,7 @@ var SupaliveWebSocketServer = class {
|
|
|
1341
1402
|
lastSnapshotTs: liveResult.ts,
|
|
1342
1403
|
readSet: liveResult.readSet,
|
|
1343
1404
|
instanceName: this.instanceId
|
|
1344
|
-
}), this.cache.setQueryCacheFor(cacheKey, data,
|
|
1405
|
+
}), this.cache.setQueryCacheFor(cacheKey, data, liveResult.ts)]);
|
|
1345
1406
|
if (!updateResult.tracked) logger$2.warn(`updateSubscriptionReadSet returned tracked:false immediately after register for ${subId} — possible sub-manager restart mid-subscribe`);
|
|
1346
1407
|
}
|
|
1347
1408
|
dataHash = await getHashOf(data);
|
|
@@ -1355,7 +1416,8 @@ var SupaliveWebSocketServer = class {
|
|
|
1355
1416
|
type: "sub:update",
|
|
1356
1417
|
subId,
|
|
1357
1418
|
data,
|
|
1358
|
-
dataHash
|
|
1419
|
+
dataHash,
|
|
1420
|
+
ts: servedTs.toString()
|
|
1359
1421
|
});
|
|
1360
1422
|
logger$2.info(`Subscribed: ${subId} for session ${session.id}`);
|
|
1361
1423
|
} catch (err) {
|
|
@@ -1588,18 +1650,20 @@ var SupaliveWebSocketServer = class {
|
|
|
1588
1650
|
lastSnapshotTs: liveResult.ts,
|
|
1589
1651
|
readSet: liveResult.readSet,
|
|
1590
1652
|
instanceName: this.instanceId
|
|
1591
|
-
}), this.cache.setQueryCacheFor(affected.cacheKey, liveResult.data,
|
|
1653
|
+
}), this.cache.setQueryCacheFor(affected.cacheKey, liveResult.data, liveResult.ts)]);
|
|
1592
1654
|
if (!updateResult.tracked) {
|
|
1593
1655
|
this.dropSubscriptionLocally(affected.subId, "no longer tracked by sub-manager");
|
|
1594
1656
|
return;
|
|
1595
1657
|
}
|
|
1658
|
+
const ts = liveResult.ts.toString();
|
|
1596
1659
|
for (const [, session] of this.sessions) if (session.subIds.has(affected.subId)) this.sendMessage(session, {
|
|
1597
1660
|
type: "sub:update",
|
|
1598
1661
|
subId: affected.subId,
|
|
1599
1662
|
data: liveResult.data,
|
|
1600
|
-
dataHash
|
|
1663
|
+
dataHash,
|
|
1664
|
+
ts
|
|
1601
1665
|
});
|
|
1602
|
-
if (affected.notifyInstances.length > 0) await this.publishSubscriptionUpdate(affected.subId, liveResult.data, dataHash, affected.notifyInstances);
|
|
1666
|
+
if (affected.notifyInstances.length > 0) await this.publishSubscriptionUpdate(affected.subId, liveResult.data, dataHash, affected.notifyInstances, ts);
|
|
1603
1667
|
logger$2.debug(`Re-executed affected query for subId: ${affected.subId}`);
|
|
1604
1668
|
} catch (err) {
|
|
1605
1669
|
logger$2.error(err, `Failed to re-execute query for subId ${affected.subId}`);
|
|
@@ -1612,7 +1676,8 @@ var SupaliveWebSocketServer = class {
|
|
|
1612
1676
|
const fn = procedure.fn;
|
|
1613
1677
|
return fn({
|
|
1614
1678
|
db: ctx,
|
|
1615
|
-
serverCtx
|
|
1679
|
+
serverCtx,
|
|
1680
|
+
...primaryOnlyConn
|
|
1616
1681
|
}, input);
|
|
1617
1682
|
});
|
|
1618
1683
|
}
|
|
@@ -1724,7 +1789,8 @@ var SupaliveWebSocketServer = class {
|
|
|
1724
1789
|
type: "sub:update",
|
|
1725
1790
|
subId: parsed.subId,
|
|
1726
1791
|
data: parsed.data,
|
|
1727
|
-
dataHash: parsed.dataHash
|
|
1792
|
+
dataHash: parsed.dataHash,
|
|
1793
|
+
ts: parsed.ts
|
|
1728
1794
|
});
|
|
1729
1795
|
}
|
|
1730
1796
|
return;
|
|
@@ -1745,13 +1811,14 @@ var SupaliveWebSocketServer = class {
|
|
|
1745
1811
|
notifyInstances: req.notifyInstances
|
|
1746
1812
|
});
|
|
1747
1813
|
}
|
|
1748
|
-
async publishSubscriptionUpdate(subId, data, dataHash, notifyInstances) {
|
|
1814
|
+
async publishSubscriptionUpdate(subId, data, dataHash, notifyInstances, ts) {
|
|
1749
1815
|
const payload = supaliveStringify({
|
|
1750
1816
|
type: "update",
|
|
1751
1817
|
subId,
|
|
1752
1818
|
data,
|
|
1753
1819
|
dataHash,
|
|
1754
|
-
originInstance: this.instanceId
|
|
1820
|
+
originInstance: this.instanceId,
|
|
1821
|
+
ts
|
|
1755
1822
|
});
|
|
1756
1823
|
const publishPromises = notifyInstances.map(async (instance) => {
|
|
1757
1824
|
if (instance === this.instanceId) return;
|
|
@@ -1765,6 +1832,16 @@ var SupaliveWebSocketServer = class {
|
|
|
1765
1832
|
await Promise.all(publishPromises);
|
|
1766
1833
|
}
|
|
1767
1834
|
};
|
|
1835
|
+
/** Parse a wire `minTs` (decimal bigint string) to a bigint, or undefined when
|
|
1836
|
+
* absent/malformed — a bad value simply falls back to primary-safe routing. */
|
|
1837
|
+
function parseMinTs(minTs) {
|
|
1838
|
+
if (minTs === void 0) return void 0;
|
|
1839
|
+
try {
|
|
1840
|
+
return BigInt(minTs);
|
|
1841
|
+
} catch {
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1768
1845
|
/** Normalize a job path to a leading-slash, no-trailing-slash prefix. */
|
|
1769
1846
|
function normalizeJobPath(p) {
|
|
1770
1847
|
let s = p.startsWith("/") ? p : `/${p}`;
|