on-zero 0.6.7 → 0.6.9
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/cjs/combineZeroClients.native.js.map +1 -1
- package/dist/cjs/createPermissions.native.js.map +1 -1
- package/dist/cjs/createSchemaFromDrizzle.native.js.map +1 -1
- package/dist/cjs/createUseQuery.native.js.map +1 -1
- package/dist/cjs/createUseQueryDirect.native.js.map +1 -1
- package/dist/cjs/createZeroClient.authData.test.native.js.map +1 -1
- package/dist/cjs/createZeroClient.cjs +6 -8
- package/dist/cjs/createZeroClient.connection.test.native.js.map +1 -1
- package/dist/cjs/createZeroClient.native.js +11 -8
- package/dist/cjs/createZeroClient.native.js.map +1 -1
- package/dist/cjs/createZeroClient.recovery.test.native.js.map +1 -1
- package/dist/cjs/createZeroServer.native.js.map +1 -1
- package/dist/cjs/createZeroServer.test.native.js.map +1 -1
- package/dist/cjs/generate-helpers.native.js.map +1 -1
- package/dist/cjs/generate-lite.native.js.map +1 -1
- package/dist/cjs/generate-lite.test.native.js.map +1 -1
- package/dist/cjs/generate.cjs +6 -0
- package/dist/cjs/generate.native.js +11 -2
- package/dist/cjs/generate.native.js.map +1 -1
- package/dist/cjs/generate.test.native.js.map +1 -1
- package/dist/cjs/helpers/batchQuery.native.js.map +1 -1
- package/dist/cjs/helpers/clearZeroClientData.native.js.map +1 -1
- package/dist/cjs/helpers/createMutators.cjs +7 -2
- package/dist/cjs/helpers/createMutators.native.js +7 -2
- package/dist/cjs/helpers/createMutators.native.js.map +1 -1
- package/dist/cjs/helpers/createMutators.test.cjs +43 -0
- package/dist/cjs/helpers/createMutators.test.native.js +46 -0
- package/dist/cjs/helpers/createMutators.test.native.js.map +1 -0
- package/dist/cjs/helpers/didRunPermissionCheck.native.js.map +1 -1
- package/dist/cjs/helpers/ensureLoggedIn.native.js.map +1 -1
- package/dist/cjs/helpers/getAuth.native.js.map +1 -1
- package/dist/cjs/helpers/mutatorContext.native.js.map +1 -1
- package/dist/cjs/helpers/prettyFormatZeroQuery.native.js.map +1 -1
- package/dist/cjs/helpers/queryContext.native.js.map +1 -1
- package/dist/cjs/helpers/recoverZeroClient.native.js.map +1 -1
- package/dist/cjs/helpers/recoverZeroClient.test.native.js.map +1 -1
- package/dist/cjs/helpers/showZeroClientError.native.js.map +1 -1
- package/dist/cjs/helpers/useMutation.native.js.map +1 -1
- package/dist/cjs/helpers/useMutation.test.native.js.map +1 -1
- package/dist/cjs/helpers/useZeroDebug.native.js.map +1 -1
- package/dist/cjs/httpPull/auth.test.native.js.map +1 -1
- package/dist/cjs/httpPull/churn.test.native.js.map +1 -1
- package/dist/cjs/httpPull/completeAck.test.cjs +69 -0
- package/dist/cjs/httpPull/completeAck.test.native.js +76 -0
- package/dist/cjs/httpPull/completeAck.test.native.js.map +1 -0
- package/dist/cjs/httpPull/fixtureSchema.native.js.map +1 -1
- package/dist/cjs/httpPull/fixtureServer.native.js.map +1 -1
- package/dist/cjs/httpPull/integration.test.native.js.map +1 -1
- package/dist/cjs/httpPull/rebase.test.native.js.map +1 -1
- package/dist/cjs/httpPull/relations.test.native.js.map +1 -1
- package/dist/cjs/httpPull/testHarness.native.js.map +1 -1
- package/dist/cjs/httpPull/transport.test.cjs +177 -1
- package/dist/cjs/httpPull/transport.test.native.js +192 -0
- package/dist/cjs/httpPull/transport.test.native.js.map +1 -1
- package/dist/cjs/httpPullTransport.cjs +138 -12
- package/dist/cjs/httpPullTransport.native.js +173 -13
- package/dist/cjs/httpPullTransport.native.js.map +1 -1
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/instanceRegistry.cjs +18 -1
- package/dist/cjs/instanceRegistry.native.js +71 -12
- package/dist/cjs/instanceRegistry.native.js.map +1 -1
- package/dist/cjs/modelRegistry.native.js.map +1 -1
- package/dist/cjs/multi.native.js.map +1 -1
- package/dist/cjs/multiInstance.test.cjs +6 -6
- package/dist/cjs/multiInstance.test.native.js +7 -7
- package/dist/cjs/multiInstance.test.native.js.map +1 -1
- package/dist/cjs/multiInstanceNested.test.native.js.map +1 -1
- package/dist/cjs/multiPartition.test.native.js.map +1 -1
- package/dist/cjs/mutations.native.js.map +1 -1
- package/dist/cjs/provideZero.hydration.test.native.js.map +1 -1
- package/dist/cjs/queryRegistry.native.js.map +1 -1
- package/dist/cjs/resolveQuery.native.js.map +1 -1
- package/dist/cjs/run.native.js.map +1 -1
- package/dist/cjs/serverWhere.native.js.map +1 -1
- package/dist/cjs/serverWhere.test.native.js.map +1 -1
- package/dist/cjs/state.native.js.map +1 -1
- package/dist/cjs/testSetup.native.js.map +1 -1
- package/dist/cjs/types.native.js.map +1 -1
- package/dist/cjs/usePermission.test.native.js.map +1 -1
- package/dist/cjs/useQuery.empty.test.cjs +10 -3
- package/dist/cjs/useQuery.empty.test.native.js +12 -3
- package/dist/cjs/useQuery.empty.test.native.js.map +1 -1
- package/dist/cjs/useQuery.ssr.test.native.js.map +1 -1
- package/dist/cjs/vite-plugin.native.js.map +1 -1
- package/dist/cjs/where.native.js.map +1 -1
- package/dist/cjs/zeroRunner.native.js.map +1 -1
- package/dist/cjs/zql.native.js.map +1 -1
- package/dist/esm/createZeroClient.mjs +6 -8
- package/dist/esm/createZeroClient.mjs.map +1 -1
- package/dist/esm/createZeroClient.native.js +11 -8
- package/dist/esm/createZeroClient.native.js.map +1 -1
- package/dist/esm/generate.mjs +6 -0
- package/dist/esm/generate.mjs.map +1 -1
- package/dist/esm/generate.native.js +11 -2
- package/dist/esm/generate.native.js.map +1 -1
- package/dist/esm/helpers/createMutators.mjs +7 -2
- package/dist/esm/helpers/createMutators.mjs.map +1 -1
- package/dist/esm/helpers/createMutators.native.js +7 -2
- package/dist/esm/helpers/createMutators.native.js.map +1 -1
- package/dist/esm/helpers/createMutators.test.mjs +44 -0
- package/dist/esm/helpers/createMutators.test.mjs.map +1 -0
- package/dist/esm/helpers/createMutators.test.native.js +44 -0
- package/dist/esm/helpers/createMutators.test.native.js.map +1 -0
- package/dist/esm/httpPull/completeAck.test.mjs +70 -0
- package/dist/esm/httpPull/completeAck.test.mjs.map +1 -0
- package/dist/esm/httpPull/completeAck.test.native.js +74 -0
- package/dist/esm/httpPull/completeAck.test.native.js.map +1 -0
- package/dist/esm/httpPull/transport.test.mjs +176 -0
- package/dist/esm/httpPull/transport.test.mjs.map +1 -1
- package/dist/esm/httpPull/transport.test.native.js +192 -0
- package/dist/esm/httpPull/transport.test.native.js.map +1 -1
- package/dist/esm/httpPullTransport.mjs +138 -12
- package/dist/esm/httpPullTransport.mjs.map +1 -1
- package/dist/esm/httpPullTransport.native.js +173 -13
- package/dist/esm/httpPullTransport.native.js.map +1 -1
- package/dist/esm/instanceRegistry.mjs +18 -1
- package/dist/esm/instanceRegistry.mjs.map +1 -1
- package/dist/esm/instanceRegistry.native.js +71 -12
- package/dist/esm/instanceRegistry.native.js.map +1 -1
- package/dist/esm/multiInstance.test.mjs +7 -7
- package/dist/esm/multiInstance.test.mjs.map +1 -1
- package/dist/esm/multiInstance.test.native.js +8 -8
- package/dist/esm/multiInstance.test.native.js.map +1 -1
- package/dist/esm/useQuery.empty.test.mjs +7 -0
- package/dist/esm/useQuery.empty.test.mjs.map +1 -1
- package/dist/esm/useQuery.empty.test.native.js +9 -0
- package/dist/esm/useQuery.empty.test.native.js.map +1 -1
- package/package.json +2 -2
- package/src/createZeroClient.tsx +7 -6
- package/src/generate.ts +10 -1
- package/src/helpers/createMutators.test.ts +51 -0
- package/src/helpers/createMutators.ts +7 -2
- package/src/httpPull/completeAck.test.ts +80 -0
- package/src/httpPull/transport.test.ts +134 -0
- package/src/httpPullTransport.ts +198 -13
- package/src/instanceRegistry.ts +27 -2
- package/src/multiInstance.test.tsx +11 -9
- package/src/useQuery.empty.test.tsx +8 -0
- package/types/createZeroClient.d.ts.map +1 -1
- package/types/generate.d.ts.map +1 -1
- package/types/helpers/createMutators.d.ts.map +1 -1
- package/types/helpers/createMutators.test.d.ts +2 -0
- package/types/helpers/createMutators.test.d.ts.map +1 -0
- package/types/httpPull/completeAck.test.d.ts +2 -0
- package/types/httpPull/completeAck.test.d.ts.map +1 -0
- package/types/httpPullTransport.d.ts +4 -0
- package/types/httpPullTransport.d.ts.map +1 -1
- package/types/instanceRegistry.d.ts +2 -1
- package/types/instanceRegistry.d.ts.map +1 -1
- package/types/useQuery.empty.test.d.ts.map +1 -1
|
@@ -45,6 +45,10 @@ function installHttpPullTransport(opts) {
|
|
|
45
45
|
nativeWebSocket: previousWebSocket,
|
|
46
46
|
sockets: /* @__PURE__ */new Set(),
|
|
47
47
|
pullIntervalMs: opts.pullIntervalMs,
|
|
48
|
+
wakeEnabled: opts.wake ?? false,
|
|
49
|
+
queryTransform: opts.queryTransform,
|
|
50
|
+
queryForward: opts.queryForward === true,
|
|
51
|
+
queryAware: opts.queryTransform !== void 0 || opts.queryForward === true,
|
|
48
52
|
nextPokeID: 0,
|
|
49
53
|
transientFailureCount: 0
|
|
50
54
|
};
|
|
@@ -124,12 +128,22 @@ class ZeroHttpSocket {
|
|
|
124
128
|
wsid;
|
|
125
129
|
cookie;
|
|
126
130
|
pendingGotQueriesPatch = [];
|
|
131
|
+
// query-aware extension state: the accumulated un-acked desired-query delta
|
|
132
|
+
// to ship, a client-side query-state version that bumps on each change, and
|
|
133
|
+
// the version/length of the delta the in-flight pull sent (to clear the
|
|
134
|
+
// acked prefix on the server's ack).
|
|
135
|
+
desiredQueryPatch = [];
|
|
136
|
+
queryVersion = 0;
|
|
137
|
+
sentQueryVersion;
|
|
138
|
+
sentQueryPatchLen = 0;
|
|
127
139
|
pullInFlight;
|
|
128
140
|
pullAfterCurrent = false;
|
|
129
141
|
pushChain = Promise.resolve();
|
|
130
142
|
nextLocalCookieID;
|
|
131
143
|
openTimer;
|
|
132
144
|
pullTimer;
|
|
145
|
+
wakeSocket;
|
|
146
|
+
wakeReconnectTimer;
|
|
133
147
|
addEventListener(type, listener) {
|
|
134
148
|
if (listener) this.listeners[type]?.add(listener);
|
|
135
149
|
}
|
|
@@ -174,6 +188,7 @@ class ZeroHttpSocket {
|
|
|
174
188
|
if (this.readyState === this.CLOSED) return;
|
|
175
189
|
if (this.openTimer) clearTimeout(this.openTimer);
|
|
176
190
|
if (this.pullTimer) clearInterval(this.pullTimer);
|
|
191
|
+
this.closeWakeChannel();
|
|
177
192
|
this.readyState = this.CLOSED;
|
|
178
193
|
this.state.sockets.delete(this);
|
|
179
194
|
this.emit("close", {
|
|
@@ -185,7 +200,8 @@ class ZeroHttpSocket {
|
|
|
185
200
|
pull() {
|
|
186
201
|
if (this.readyState !== this.OPEN) return Promise.resolve();
|
|
187
202
|
if (this.pullInFlight) return this.pullInFlight;
|
|
188
|
-
this.pullInFlight = this.fetchPull(this.clientGroupID, this.cookie).then(response => {
|
|
203
|
+
this.pullInFlight = this.fetchPull(this.clientGroupID, this.cookie, true).then(response => {
|
|
204
|
+
if (this.state.queryAware) this.applyServerGotQueries(response);
|
|
189
205
|
if (response.unchanged) {
|
|
190
206
|
this.emitGotQueriesPatch(response.cookie);
|
|
191
207
|
return;
|
|
@@ -216,11 +232,96 @@ class ZeroHttpSocket {
|
|
|
216
232
|
this.run(this.pull());
|
|
217
233
|
}, this.state.pullIntervalMs);
|
|
218
234
|
}
|
|
235
|
+
if (this.state.wakeEnabled) this.openWakeChannel();
|
|
236
|
+
}
|
|
237
|
+
// notification-only wake channel: a real WebSocket to <origin>/wake that
|
|
238
|
+
// carries no data. any frame means "pull now", so a wake triggers an
|
|
239
|
+
// immediate (coalesced) pull — push-shaped propagation without waiting on
|
|
240
|
+
// the poll interval. advisory only: if it drops we reconnect, and the
|
|
241
|
+
// interval poll remains the safety net that guarantees convergence.
|
|
242
|
+
openWakeChannel() {
|
|
243
|
+
const Native = this.state.nativeWebSocket;
|
|
244
|
+
if (!Native || this.wakeSocket || this.readyState !== this.OPEN) return;
|
|
245
|
+
const wsBase = this.state.originString.replace(/^http/, "ws");
|
|
246
|
+
const url = `${wsBase}/wake?clientID=${encodeURIComponent(this.clientID)}`;
|
|
247
|
+
let socket;
|
|
248
|
+
try {
|
|
249
|
+
socket = new Native(url);
|
|
250
|
+
} catch {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
this.wakeSocket = socket;
|
|
254
|
+
const reconnect = () => {
|
|
255
|
+
if (this.wakeSocket !== socket) return;
|
|
256
|
+
this.wakeSocket = void 0;
|
|
257
|
+
if (this.readyState !== this.OPEN || this.wakeReconnectTimer) return;
|
|
258
|
+
this.wakeReconnectTimer = setTimeout(() => {
|
|
259
|
+
this.wakeReconnectTimer = void 0;
|
|
260
|
+
this.openWakeChannel();
|
|
261
|
+
}, 500);
|
|
262
|
+
};
|
|
263
|
+
socket.onmessage = () => this.requestPullAfterCurrent();
|
|
264
|
+
socket.onclose = reconnect;
|
|
265
|
+
socket.onerror = reconnect;
|
|
266
|
+
}
|
|
267
|
+
closeWakeChannel() {
|
|
268
|
+
if (this.wakeReconnectTimer) {
|
|
269
|
+
clearTimeout(this.wakeReconnectTimer);
|
|
270
|
+
this.wakeReconnectTimer = void 0;
|
|
271
|
+
}
|
|
272
|
+
const socket = this.wakeSocket;
|
|
273
|
+
this.wakeSocket = void 0;
|
|
274
|
+
if (socket) {
|
|
275
|
+
try {
|
|
276
|
+
socket.close();
|
|
277
|
+
} catch {}
|
|
278
|
+
}
|
|
219
279
|
}
|
|
220
280
|
queueDesiredQueries(body) {
|
|
221
281
|
const desiredQueriesPatch = body?.desiredQueriesPatch;
|
|
222
282
|
if (!Array.isArray(desiredQueriesPatch)) return;
|
|
223
|
-
this.
|
|
283
|
+
if (!this.state.queryAware) {
|
|
284
|
+
this.pendingGotQueriesPatch.push(...gotQueriesPatch(desiredQueriesPatch));
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const transform = this.state.queryTransform;
|
|
288
|
+
for (const op of desiredQueriesPatch) {
|
|
289
|
+
if (op.op === "clear") {
|
|
290
|
+
this.desiredQueryPatch.push({
|
|
291
|
+
op: "clear"
|
|
292
|
+
});
|
|
293
|
+
} else if (op.op === "del") {
|
|
294
|
+
this.desiredQueryPatch.push({
|
|
295
|
+
op: "del",
|
|
296
|
+
hash: op.hash
|
|
297
|
+
});
|
|
298
|
+
} else if (op.op === "put") {
|
|
299
|
+
const inline = op.ast;
|
|
300
|
+
const name = op.name ?? "";
|
|
301
|
+
const args = op.args ?? [];
|
|
302
|
+
if (this.state.queryForward) {
|
|
303
|
+
this.desiredQueryPatch.push({
|
|
304
|
+
op: "put",
|
|
305
|
+
hash: op.hash,
|
|
306
|
+
name,
|
|
307
|
+
args
|
|
308
|
+
});
|
|
309
|
+
} else if (inline !== void 0) {
|
|
310
|
+
this.desiredQueryPatch.push({
|
|
311
|
+
op: "put",
|
|
312
|
+
hash: op.hash,
|
|
313
|
+
ast: inline
|
|
314
|
+
});
|
|
315
|
+
} else if (transform) {
|
|
316
|
+
this.desiredQueryPatch.push({
|
|
317
|
+
op: "put",
|
|
318
|
+
hash: op.hash,
|
|
319
|
+
ast: transform(name, args)
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
this.queryVersion++;
|
|
224
325
|
}
|
|
225
326
|
async push(body) {
|
|
226
327
|
const response = await this.postJSON("/push", body);
|
|
@@ -242,8 +343,21 @@ class ZeroHttpSocket {
|
|
|
242
343
|
}
|
|
243
344
|
this.run(this.pull());
|
|
244
345
|
}
|
|
346
|
+
// in query-aware mode the got-query ack is authoritative from the server:
|
|
347
|
+
// take the server's gotQueries.patch as the got patch to emit (replacing
|
|
348
|
+
// local synthesis), and clear the acked prefix of the shipped desired delta
|
|
349
|
+
// once the server acks that version (the ack never leads its row effects —
|
|
350
|
+
// invariant 13 — so the client marks a query got only after its rows land).
|
|
351
|
+
applyServerGotQueries(response) {
|
|
352
|
+
const got = response.gotQueries;
|
|
353
|
+
this.pendingGotQueriesPatch = got ? got.patch : [];
|
|
354
|
+
if (got && this.sentQueryVersion !== void 0 && got.version >= this.sentQueryVersion) {
|
|
355
|
+
this.desiredQueryPatch.splice(0, this.sentQueryPatchLen);
|
|
356
|
+
this.sentQueryVersion = void 0;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
245
359
|
async answerMutationRecoveryPull(body) {
|
|
246
|
-
const response = await this.fetchPull(body.clientGroupID, body.cookie);
|
|
360
|
+
const response = await this.fetchPull(body.clientGroupID, body.cookie, false);
|
|
247
361
|
const cookie = toWebSocketCookie(response.cookie);
|
|
248
362
|
this.emitMessage(["pull", {
|
|
249
363
|
requestID: body.requestID,
|
|
@@ -251,12 +365,23 @@ class ZeroHttpSocket {
|
|
|
251
365
|
lastMutationIDChanges: response.unchanged ? {} : response.lastMutationIDChanges
|
|
252
366
|
}]);
|
|
253
367
|
}
|
|
254
|
-
async fetchPull(clientGroupID, cookie) {
|
|
255
|
-
|
|
368
|
+
async fetchPull(clientGroupID, cookie, includeQueries) {
|
|
369
|
+
const body = {
|
|
256
370
|
clientID: this.clientID,
|
|
257
371
|
clientGroupID,
|
|
258
372
|
cookie: toHttpCookie(cookie)
|
|
259
|
-
}
|
|
373
|
+
};
|
|
374
|
+
if (includeQueries && this.state.queryAware && this.desiredQueryPatch.length > 0) {
|
|
375
|
+
this.sentQueryVersion = this.queryVersion;
|
|
376
|
+
this.sentQueryPatchLen = this.desiredQueryPatch.length;
|
|
377
|
+
body.queries = {
|
|
378
|
+
version: this.queryVersion,
|
|
379
|
+
patch: [...this.desiredQueryPatch]
|
|
380
|
+
};
|
|
381
|
+
} else {
|
|
382
|
+
this.sentQueryVersion = void 0;
|
|
383
|
+
}
|
|
384
|
+
return await this.postJSON("/pull", body);
|
|
260
385
|
}
|
|
261
386
|
async postJSON(path, body) {
|
|
262
387
|
const response = await this.state.fetch(`${this.state.originString}${path}`, {
|
|
@@ -303,9 +428,14 @@ class ZeroHttpSocket {
|
|
|
303
428
|
this.close(1011, errorMessage(error));
|
|
304
429
|
}
|
|
305
430
|
emitPoke(response) {
|
|
306
|
-
const
|
|
307
|
-
|
|
431
|
+
const currentServer = toHttpCookie(this.cookie);
|
|
432
|
+
let nextCookie;
|
|
433
|
+
if (currentServer !== null && response.cookie < currentServer) {
|
|
308
434
|
throw new Error(`zero-http pull returned stale cookie ${response.cookie} for ${this.cookie}`);
|
|
435
|
+
} else if (currentServer !== null && response.cookie === currentServer) {
|
|
436
|
+
nextCookie = toLocalWebSocketCookie(response.cookie, ++this.nextLocalCookieID);
|
|
437
|
+
} else {
|
|
438
|
+
nextCookie = toWebSocketCookie(response.cookie);
|
|
309
439
|
}
|
|
310
440
|
const pokeID = `zero-http-${++this.state.nextPokeID}`;
|
|
311
441
|
const gotQueries = this.pendingGotQueriesPatch;
|
|
@@ -432,10 +562,6 @@ function toWebSocketCookie(cookie) {
|
|
|
432
562
|
function toLocalWebSocketCookie(cookie, localID) {
|
|
433
563
|
return `${String(cookie).padStart(COOKIE_WIDTH, "0")}#${String(localID).padStart(6, "0")}`;
|
|
434
564
|
}
|
|
435
|
-
function isStaleCookie(current, next) {
|
|
436
|
-
const currentNumber = toHttpCookie(current);
|
|
437
|
-
return currentNumber !== null && next <= currentNumber;
|
|
438
|
-
}
|
|
439
565
|
function errorMessage(error) {
|
|
440
566
|
return error instanceof Error ? error.message : String(error);
|
|
441
567
|
}
|
|
@@ -177,6 +177,7 @@ function installHttpPullTransport(opts) {
|
|
|
177
177
|
if (!fetchImpl) {
|
|
178
178
|
throw new Error("installHttpPullTransport requires a fetch implementation");
|
|
179
179
|
}
|
|
180
|
+
var _opts_wake;
|
|
180
181
|
var state = {
|
|
181
182
|
origin: new URL(opts.origin),
|
|
182
183
|
originString: trimTrailingSlash(new URL(opts.origin).toString()),
|
|
@@ -187,6 +188,10 @@ function installHttpPullTransport(opts) {
|
|
|
187
188
|
nativeWebSocket: previousWebSocket,
|
|
188
189
|
sockets: /* @__PURE__ */new Set(),
|
|
189
190
|
pullIntervalMs: opts.pullIntervalMs,
|
|
191
|
+
wakeEnabled: (_opts_wake = opts.wake) !== null && _opts_wake !== void 0 ? _opts_wake : false,
|
|
192
|
+
queryTransform: opts.queryTransform,
|
|
193
|
+
queryForward: opts.queryForward === true,
|
|
194
|
+
queryAware: opts.queryTransform !== void 0 || opts.queryForward === true,
|
|
190
195
|
nextPokeID: 0,
|
|
191
196
|
transientFailureCount: 0
|
|
192
197
|
};
|
|
@@ -250,12 +255,18 @@ var ZeroHttpSocket = /* @__PURE__ */function () {
|
|
|
250
255
|
_define_property(this, "wsid", void 0);
|
|
251
256
|
_define_property(this, "cookie", void 0);
|
|
252
257
|
_define_property(this, "pendingGotQueriesPatch", void 0);
|
|
258
|
+
_define_property(this, "desiredQueryPatch", void 0);
|
|
259
|
+
_define_property(this, "queryVersion", void 0);
|
|
260
|
+
_define_property(this, "sentQueryVersion", void 0);
|
|
261
|
+
_define_property(this, "sentQueryPatchLen", void 0);
|
|
253
262
|
_define_property(this, "pullInFlight", void 0);
|
|
254
263
|
_define_property(this, "pullAfterCurrent", void 0);
|
|
255
264
|
_define_property(this, "pushChain", void 0);
|
|
256
265
|
_define_property(this, "nextLocalCookieID", void 0);
|
|
257
266
|
_define_property(this, "openTimer", void 0);
|
|
258
267
|
_define_property(this, "pullTimer", void 0);
|
|
268
|
+
_define_property(this, "wakeSocket", void 0);
|
|
269
|
+
_define_property(this, "wakeReconnectTimer", void 0);
|
|
259
270
|
this.state = state;
|
|
260
271
|
this.CONNECTING = 0;
|
|
261
272
|
this.OPEN = 1;
|
|
@@ -269,6 +280,9 @@ var ZeroHttpSocket = /* @__PURE__ */function () {
|
|
|
269
280
|
error: /* @__PURE__ */new Set()
|
|
270
281
|
};
|
|
271
282
|
this.pendingGotQueriesPatch = [];
|
|
283
|
+
this.desiredQueryPatch = [];
|
|
284
|
+
this.queryVersion = 0;
|
|
285
|
+
this.sentQueryPatchLen = 0;
|
|
272
286
|
this.pullAfterCurrent = false;
|
|
273
287
|
this.pushChain = Promise.resolve();
|
|
274
288
|
this.connectURL = toHttpURL(url);
|
|
@@ -349,6 +363,7 @@ var ZeroHttpSocket = /* @__PURE__ */function () {
|
|
|
349
363
|
if (this.readyState === this.CLOSED) return;
|
|
350
364
|
if (this.openTimer) clearTimeout(this.openTimer);
|
|
351
365
|
if (this.pullTimer) clearInterval(this.pullTimer);
|
|
366
|
+
this.closeWakeChannel();
|
|
352
367
|
this.readyState = this.CLOSED;
|
|
353
368
|
this.state.sockets.delete(this);
|
|
354
369
|
this.emit("close", {
|
|
@@ -363,7 +378,8 @@ var ZeroHttpSocket = /* @__PURE__ */function () {
|
|
|
363
378
|
var _this = this;
|
|
364
379
|
if (this.readyState !== this.OPEN) return Promise.resolve();
|
|
365
380
|
if (this.pullInFlight) return this.pullInFlight;
|
|
366
|
-
this.pullInFlight = this.fetchPull(this.clientGroupID, this.cookie).then(function (response) {
|
|
381
|
+
this.pullInFlight = this.fetchPull(this.clientGroupID, this.cookie, true).then(function (response) {
|
|
382
|
+
if (_this.state.queryAware) _this.applyServerGotQueries(response);
|
|
367
383
|
if (response.unchanged) {
|
|
368
384
|
_this.emitGotQueriesPatch(response.cookie);
|
|
369
385
|
return;
|
|
@@ -399,13 +415,129 @@ var ZeroHttpSocket = /* @__PURE__ */function () {
|
|
|
399
415
|
_this.run(_this.pull());
|
|
400
416
|
}, this.state.pullIntervalMs);
|
|
401
417
|
}
|
|
418
|
+
if (this.state.wakeEnabled) this.openWakeChannel();
|
|
419
|
+
}
|
|
420
|
+
}, {
|
|
421
|
+
key: "openWakeChannel",
|
|
422
|
+
value:
|
|
423
|
+
// notification-only wake channel: a real WebSocket to <origin>/wake that
|
|
424
|
+
// carries no data. any frame means "pull now", so a wake triggers an
|
|
425
|
+
// immediate (coalesced) pull — push-shaped propagation without waiting on
|
|
426
|
+
// the poll interval. advisory only: if it drops we reconnect, and the
|
|
427
|
+
// interval poll remains the safety net that guarantees convergence.
|
|
428
|
+
function openWakeChannel() {
|
|
429
|
+
var _this = this;
|
|
430
|
+
var Native = this.state.nativeWebSocket;
|
|
431
|
+
if (!Native || this.wakeSocket || this.readyState !== this.OPEN) return;
|
|
432
|
+
var wsBase = this.state.originString.replace(/^http/, "ws");
|
|
433
|
+
var url = `${wsBase}/wake?clientID=${encodeURIComponent(this.clientID)}`;
|
|
434
|
+
var socket;
|
|
435
|
+
try {
|
|
436
|
+
socket = new Native(url);
|
|
437
|
+
} catch (e) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
this.wakeSocket = socket;
|
|
441
|
+
var reconnect = function () {
|
|
442
|
+
if (_this.wakeSocket !== socket) return;
|
|
443
|
+
_this.wakeSocket = void 0;
|
|
444
|
+
if (_this.readyState !== _this.OPEN || _this.wakeReconnectTimer) return;
|
|
445
|
+
_this.wakeReconnectTimer = setTimeout(function () {
|
|
446
|
+
_this.wakeReconnectTimer = void 0;
|
|
447
|
+
_this.openWakeChannel();
|
|
448
|
+
}, 500);
|
|
449
|
+
};
|
|
450
|
+
socket.onmessage = function () {
|
|
451
|
+
return _this.requestPullAfterCurrent();
|
|
452
|
+
};
|
|
453
|
+
socket.onclose = reconnect;
|
|
454
|
+
socket.onerror = reconnect;
|
|
455
|
+
}
|
|
456
|
+
}, {
|
|
457
|
+
key: "closeWakeChannel",
|
|
458
|
+
value: function closeWakeChannel() {
|
|
459
|
+
if (this.wakeReconnectTimer) {
|
|
460
|
+
clearTimeout(this.wakeReconnectTimer);
|
|
461
|
+
this.wakeReconnectTimer = void 0;
|
|
462
|
+
}
|
|
463
|
+
var socket = this.wakeSocket;
|
|
464
|
+
this.wakeSocket = void 0;
|
|
465
|
+
if (socket) {
|
|
466
|
+
try {
|
|
467
|
+
socket.close();
|
|
468
|
+
} catch (e) {}
|
|
469
|
+
}
|
|
402
470
|
}
|
|
403
471
|
}, {
|
|
404
472
|
key: "queueDesiredQueries",
|
|
405
|
-
value:
|
|
473
|
+
value:
|
|
474
|
+
// best effort: an already-closing wake socket is harmless
|
|
475
|
+
function queueDesiredQueries(body) {
|
|
406
476
|
var desiredQueriesPatch = body === null || body === void 0 ? void 0 : body.desiredQueriesPatch;
|
|
407
477
|
if (!Array.isArray(desiredQueriesPatch)) return;
|
|
408
|
-
this.
|
|
478
|
+
if (!this.state.queryAware) {
|
|
479
|
+
this.pendingGotQueriesPatch.push(...gotQueriesPatch(desiredQueriesPatch));
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
var transform = this.state.queryTransform;
|
|
483
|
+
var _iteratorNormalCompletion = true,
|
|
484
|
+
_didIteratorError = false,
|
|
485
|
+
_iteratorError = void 0;
|
|
486
|
+
try {
|
|
487
|
+
for (var _iterator = desiredQueriesPatch[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
|
488
|
+
var op = _step.value;
|
|
489
|
+
if (op.op === "clear") {
|
|
490
|
+
this.desiredQueryPatch.push({
|
|
491
|
+
op: "clear"
|
|
492
|
+
});
|
|
493
|
+
} else if (op.op === "del") {
|
|
494
|
+
this.desiredQueryPatch.push({
|
|
495
|
+
op: "del",
|
|
496
|
+
hash: op.hash
|
|
497
|
+
});
|
|
498
|
+
} else if (op.op === "put") {
|
|
499
|
+
var inline = op.ast;
|
|
500
|
+
var _op_name;
|
|
501
|
+
var name = (_op_name = op.name) !== null && _op_name !== void 0 ? _op_name : "";
|
|
502
|
+
var _op_args;
|
|
503
|
+
var args = (_op_args = op.args) !== null && _op_args !== void 0 ? _op_args : [];
|
|
504
|
+
if (this.state.queryForward) {
|
|
505
|
+
this.desiredQueryPatch.push({
|
|
506
|
+
op: "put",
|
|
507
|
+
hash: op.hash,
|
|
508
|
+
name,
|
|
509
|
+
args
|
|
510
|
+
});
|
|
511
|
+
} else if (inline !== void 0) {
|
|
512
|
+
this.desiredQueryPatch.push({
|
|
513
|
+
op: "put",
|
|
514
|
+
hash: op.hash,
|
|
515
|
+
ast: inline
|
|
516
|
+
});
|
|
517
|
+
} else if (transform) {
|
|
518
|
+
this.desiredQueryPatch.push({
|
|
519
|
+
op: "put",
|
|
520
|
+
hash: op.hash,
|
|
521
|
+
ast: transform(name, args)
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
} catch (err) {
|
|
527
|
+
_didIteratorError = true;
|
|
528
|
+
_iteratorError = err;
|
|
529
|
+
} finally {
|
|
530
|
+
try {
|
|
531
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
532
|
+
_iterator.return();
|
|
533
|
+
}
|
|
534
|
+
} finally {
|
|
535
|
+
if (_didIteratorError) {
|
|
536
|
+
throw _iteratorError;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
this.queryVersion++;
|
|
409
541
|
}
|
|
410
542
|
}, {
|
|
411
543
|
key: "push",
|
|
@@ -434,10 +566,26 @@ var ZeroHttpSocket = /* @__PURE__ */function () {
|
|
|
434
566
|
}
|
|
435
567
|
this.run(this.pull());
|
|
436
568
|
}
|
|
569
|
+
}, {
|
|
570
|
+
key: "applyServerGotQueries",
|
|
571
|
+
value:
|
|
572
|
+
// in query-aware mode the got-query ack is authoritative from the server:
|
|
573
|
+
// take the server's gotQueries.patch as the got patch to emit (replacing
|
|
574
|
+
// local synthesis), and clear the acked prefix of the shipped desired delta
|
|
575
|
+
// once the server acks that version (the ack never leads its row effects —
|
|
576
|
+
// invariant 13 — so the client marks a query got only after its rows land).
|
|
577
|
+
function applyServerGotQueries(response) {
|
|
578
|
+
var got = response.gotQueries;
|
|
579
|
+
this.pendingGotQueriesPatch = got ? got.patch : [];
|
|
580
|
+
if (got && this.sentQueryVersion !== void 0 && got.version >= this.sentQueryVersion) {
|
|
581
|
+
this.desiredQueryPatch.splice(0, this.sentQueryPatchLen);
|
|
582
|
+
this.sentQueryVersion = void 0;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
437
585
|
}, {
|
|
438
586
|
key: "answerMutationRecoveryPull",
|
|
439
587
|
value: async function answerMutationRecoveryPull(body) {
|
|
440
|
-
var response = await this.fetchPull(body.clientGroupID, body.cookie);
|
|
588
|
+
var response = await this.fetchPull(body.clientGroupID, body.cookie, false);
|
|
441
589
|
var cookie = toWebSocketCookie(response.cookie);
|
|
442
590
|
var _ref;
|
|
443
591
|
this.emitMessage(["pull", {
|
|
@@ -448,12 +596,23 @@ var ZeroHttpSocket = /* @__PURE__ */function () {
|
|
|
448
596
|
}
|
|
449
597
|
}, {
|
|
450
598
|
key: "fetchPull",
|
|
451
|
-
value: async function fetchPull(clientGroupID, cookie) {
|
|
452
|
-
|
|
599
|
+
value: async function fetchPull(clientGroupID, cookie, includeQueries) {
|
|
600
|
+
var body = {
|
|
453
601
|
clientID: this.clientID,
|
|
454
602
|
clientGroupID,
|
|
455
603
|
cookie: toHttpCookie(cookie)
|
|
456
|
-
}
|
|
604
|
+
};
|
|
605
|
+
if (includeQueries && this.state.queryAware && this.desiredQueryPatch.length > 0) {
|
|
606
|
+
this.sentQueryVersion = this.queryVersion;
|
|
607
|
+
this.sentQueryPatchLen = this.desiredQueryPatch.length;
|
|
608
|
+
body.queries = {
|
|
609
|
+
version: this.queryVersion,
|
|
610
|
+
patch: [...this.desiredQueryPatch]
|
|
611
|
+
};
|
|
612
|
+
} else {
|
|
613
|
+
this.sentQueryVersion = void 0;
|
|
614
|
+
}
|
|
615
|
+
return await this.postJSON("/pull", body);
|
|
457
616
|
}
|
|
458
617
|
}, {
|
|
459
618
|
key: "postJSON",
|
|
@@ -511,9 +670,14 @@ var ZeroHttpSocket = /* @__PURE__ */function () {
|
|
|
511
670
|
}, {
|
|
512
671
|
key: "emitPoke",
|
|
513
672
|
value: function emitPoke(response) {
|
|
514
|
-
var
|
|
515
|
-
|
|
673
|
+
var currentServer = toHttpCookie(this.cookie);
|
|
674
|
+
var nextCookie;
|
|
675
|
+
if (currentServer !== null && response.cookie < currentServer) {
|
|
516
676
|
throw new Error(`zero-http pull returned stale cookie ${response.cookie} for ${this.cookie}`);
|
|
677
|
+
} else if (currentServer !== null && response.cookie === currentServer) {
|
|
678
|
+
nextCookie = toLocalWebSocketCookie(response.cookie, ++this.nextLocalCookieID);
|
|
679
|
+
} else {
|
|
680
|
+
nextCookie = toWebSocketCookie(response.cookie);
|
|
517
681
|
}
|
|
518
682
|
var pokeID = `zero-http-${++this.state.nextPokeID}`;
|
|
519
683
|
var gotQueries = this.pendingGotQueriesPatch;
|
|
@@ -688,10 +852,6 @@ function toWebSocketCookie(cookie) {
|
|
|
688
852
|
function toLocalWebSocketCookie(cookie, localID) {
|
|
689
853
|
return `${String(cookie).padStart(COOKIE_WIDTH, "0")}#${String(localID).padStart(6, "0")}`;
|
|
690
854
|
}
|
|
691
|
-
function isStaleCookie(current, next) {
|
|
692
|
-
var currentNumber = toHttpCookie(current);
|
|
693
|
-
return currentNumber !== null && next <= currentNumber;
|
|
694
|
-
}
|
|
695
855
|
function errorMessage(error) {
|
|
696
856
|
return _instanceof(error, Error) ? error.message : String(error);
|
|
697
857
|
}
|