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.
Files changed (150) hide show
  1. package/dist/cjs/combineZeroClients.native.js.map +1 -1
  2. package/dist/cjs/createPermissions.native.js.map +1 -1
  3. package/dist/cjs/createSchemaFromDrizzle.native.js.map +1 -1
  4. package/dist/cjs/createUseQuery.native.js.map +1 -1
  5. package/dist/cjs/createUseQueryDirect.native.js.map +1 -1
  6. package/dist/cjs/createZeroClient.authData.test.native.js.map +1 -1
  7. package/dist/cjs/createZeroClient.cjs +6 -8
  8. package/dist/cjs/createZeroClient.connection.test.native.js.map +1 -1
  9. package/dist/cjs/createZeroClient.native.js +11 -8
  10. package/dist/cjs/createZeroClient.native.js.map +1 -1
  11. package/dist/cjs/createZeroClient.recovery.test.native.js.map +1 -1
  12. package/dist/cjs/createZeroServer.native.js.map +1 -1
  13. package/dist/cjs/createZeroServer.test.native.js.map +1 -1
  14. package/dist/cjs/generate-helpers.native.js.map +1 -1
  15. package/dist/cjs/generate-lite.native.js.map +1 -1
  16. package/dist/cjs/generate-lite.test.native.js.map +1 -1
  17. package/dist/cjs/generate.cjs +6 -0
  18. package/dist/cjs/generate.native.js +11 -2
  19. package/dist/cjs/generate.native.js.map +1 -1
  20. package/dist/cjs/generate.test.native.js.map +1 -1
  21. package/dist/cjs/helpers/batchQuery.native.js.map +1 -1
  22. package/dist/cjs/helpers/clearZeroClientData.native.js.map +1 -1
  23. package/dist/cjs/helpers/createMutators.cjs +7 -2
  24. package/dist/cjs/helpers/createMutators.native.js +7 -2
  25. package/dist/cjs/helpers/createMutators.native.js.map +1 -1
  26. package/dist/cjs/helpers/createMutators.test.cjs +43 -0
  27. package/dist/cjs/helpers/createMutators.test.native.js +46 -0
  28. package/dist/cjs/helpers/createMutators.test.native.js.map +1 -0
  29. package/dist/cjs/helpers/didRunPermissionCheck.native.js.map +1 -1
  30. package/dist/cjs/helpers/ensureLoggedIn.native.js.map +1 -1
  31. package/dist/cjs/helpers/getAuth.native.js.map +1 -1
  32. package/dist/cjs/helpers/mutatorContext.native.js.map +1 -1
  33. package/dist/cjs/helpers/prettyFormatZeroQuery.native.js.map +1 -1
  34. package/dist/cjs/helpers/queryContext.native.js.map +1 -1
  35. package/dist/cjs/helpers/recoverZeroClient.native.js.map +1 -1
  36. package/dist/cjs/helpers/recoverZeroClient.test.native.js.map +1 -1
  37. package/dist/cjs/helpers/showZeroClientError.native.js.map +1 -1
  38. package/dist/cjs/helpers/useMutation.native.js.map +1 -1
  39. package/dist/cjs/helpers/useMutation.test.native.js.map +1 -1
  40. package/dist/cjs/helpers/useZeroDebug.native.js.map +1 -1
  41. package/dist/cjs/httpPull/auth.test.native.js.map +1 -1
  42. package/dist/cjs/httpPull/churn.test.native.js.map +1 -1
  43. package/dist/cjs/httpPull/completeAck.test.cjs +69 -0
  44. package/dist/cjs/httpPull/completeAck.test.native.js +76 -0
  45. package/dist/cjs/httpPull/completeAck.test.native.js.map +1 -0
  46. package/dist/cjs/httpPull/fixtureSchema.native.js.map +1 -1
  47. package/dist/cjs/httpPull/fixtureServer.native.js.map +1 -1
  48. package/dist/cjs/httpPull/integration.test.native.js.map +1 -1
  49. package/dist/cjs/httpPull/rebase.test.native.js.map +1 -1
  50. package/dist/cjs/httpPull/relations.test.native.js.map +1 -1
  51. package/dist/cjs/httpPull/testHarness.native.js.map +1 -1
  52. package/dist/cjs/httpPull/transport.test.cjs +177 -1
  53. package/dist/cjs/httpPull/transport.test.native.js +192 -0
  54. package/dist/cjs/httpPull/transport.test.native.js.map +1 -1
  55. package/dist/cjs/httpPullTransport.cjs +138 -12
  56. package/dist/cjs/httpPullTransport.native.js +173 -13
  57. package/dist/cjs/httpPullTransport.native.js.map +1 -1
  58. package/dist/cjs/index.native.js.map +1 -1
  59. package/dist/cjs/instanceRegistry.cjs +18 -1
  60. package/dist/cjs/instanceRegistry.native.js +71 -12
  61. package/dist/cjs/instanceRegistry.native.js.map +1 -1
  62. package/dist/cjs/modelRegistry.native.js.map +1 -1
  63. package/dist/cjs/multi.native.js.map +1 -1
  64. package/dist/cjs/multiInstance.test.cjs +6 -6
  65. package/dist/cjs/multiInstance.test.native.js +7 -7
  66. package/dist/cjs/multiInstance.test.native.js.map +1 -1
  67. package/dist/cjs/multiInstanceNested.test.native.js.map +1 -1
  68. package/dist/cjs/multiPartition.test.native.js.map +1 -1
  69. package/dist/cjs/mutations.native.js.map +1 -1
  70. package/dist/cjs/provideZero.hydration.test.native.js.map +1 -1
  71. package/dist/cjs/queryRegistry.native.js.map +1 -1
  72. package/dist/cjs/resolveQuery.native.js.map +1 -1
  73. package/dist/cjs/run.native.js.map +1 -1
  74. package/dist/cjs/serverWhere.native.js.map +1 -1
  75. package/dist/cjs/serverWhere.test.native.js.map +1 -1
  76. package/dist/cjs/state.native.js.map +1 -1
  77. package/dist/cjs/testSetup.native.js.map +1 -1
  78. package/dist/cjs/types.native.js.map +1 -1
  79. package/dist/cjs/usePermission.test.native.js.map +1 -1
  80. package/dist/cjs/useQuery.empty.test.cjs +10 -3
  81. package/dist/cjs/useQuery.empty.test.native.js +12 -3
  82. package/dist/cjs/useQuery.empty.test.native.js.map +1 -1
  83. package/dist/cjs/useQuery.ssr.test.native.js.map +1 -1
  84. package/dist/cjs/vite-plugin.native.js.map +1 -1
  85. package/dist/cjs/where.native.js.map +1 -1
  86. package/dist/cjs/zeroRunner.native.js.map +1 -1
  87. package/dist/cjs/zql.native.js.map +1 -1
  88. package/dist/esm/createZeroClient.mjs +6 -8
  89. package/dist/esm/createZeroClient.mjs.map +1 -1
  90. package/dist/esm/createZeroClient.native.js +11 -8
  91. package/dist/esm/createZeroClient.native.js.map +1 -1
  92. package/dist/esm/generate.mjs +6 -0
  93. package/dist/esm/generate.mjs.map +1 -1
  94. package/dist/esm/generate.native.js +11 -2
  95. package/dist/esm/generate.native.js.map +1 -1
  96. package/dist/esm/helpers/createMutators.mjs +7 -2
  97. package/dist/esm/helpers/createMutators.mjs.map +1 -1
  98. package/dist/esm/helpers/createMutators.native.js +7 -2
  99. package/dist/esm/helpers/createMutators.native.js.map +1 -1
  100. package/dist/esm/helpers/createMutators.test.mjs +44 -0
  101. package/dist/esm/helpers/createMutators.test.mjs.map +1 -0
  102. package/dist/esm/helpers/createMutators.test.native.js +44 -0
  103. package/dist/esm/helpers/createMutators.test.native.js.map +1 -0
  104. package/dist/esm/httpPull/completeAck.test.mjs +70 -0
  105. package/dist/esm/httpPull/completeAck.test.mjs.map +1 -0
  106. package/dist/esm/httpPull/completeAck.test.native.js +74 -0
  107. package/dist/esm/httpPull/completeAck.test.native.js.map +1 -0
  108. package/dist/esm/httpPull/transport.test.mjs +176 -0
  109. package/dist/esm/httpPull/transport.test.mjs.map +1 -1
  110. package/dist/esm/httpPull/transport.test.native.js +192 -0
  111. package/dist/esm/httpPull/transport.test.native.js.map +1 -1
  112. package/dist/esm/httpPullTransport.mjs +138 -12
  113. package/dist/esm/httpPullTransport.mjs.map +1 -1
  114. package/dist/esm/httpPullTransport.native.js +173 -13
  115. package/dist/esm/httpPullTransport.native.js.map +1 -1
  116. package/dist/esm/instanceRegistry.mjs +18 -1
  117. package/dist/esm/instanceRegistry.mjs.map +1 -1
  118. package/dist/esm/instanceRegistry.native.js +71 -12
  119. package/dist/esm/instanceRegistry.native.js.map +1 -1
  120. package/dist/esm/multiInstance.test.mjs +7 -7
  121. package/dist/esm/multiInstance.test.mjs.map +1 -1
  122. package/dist/esm/multiInstance.test.native.js +8 -8
  123. package/dist/esm/multiInstance.test.native.js.map +1 -1
  124. package/dist/esm/useQuery.empty.test.mjs +7 -0
  125. package/dist/esm/useQuery.empty.test.mjs.map +1 -1
  126. package/dist/esm/useQuery.empty.test.native.js +9 -0
  127. package/dist/esm/useQuery.empty.test.native.js.map +1 -1
  128. package/package.json +2 -2
  129. package/src/createZeroClient.tsx +7 -6
  130. package/src/generate.ts +10 -1
  131. package/src/helpers/createMutators.test.ts +51 -0
  132. package/src/helpers/createMutators.ts +7 -2
  133. package/src/httpPull/completeAck.test.ts +80 -0
  134. package/src/httpPull/transport.test.ts +134 -0
  135. package/src/httpPullTransport.ts +198 -13
  136. package/src/instanceRegistry.ts +27 -2
  137. package/src/multiInstance.test.tsx +11 -9
  138. package/src/useQuery.empty.test.tsx +8 -0
  139. package/types/createZeroClient.d.ts.map +1 -1
  140. package/types/generate.d.ts.map +1 -1
  141. package/types/helpers/createMutators.d.ts.map +1 -1
  142. package/types/helpers/createMutators.test.d.ts +2 -0
  143. package/types/helpers/createMutators.test.d.ts.map +1 -0
  144. package/types/httpPull/completeAck.test.d.ts +2 -0
  145. package/types/httpPull/completeAck.test.d.ts.map +1 -0
  146. package/types/httpPullTransport.d.ts +4 -0
  147. package/types/httpPullTransport.d.ts.map +1 -1
  148. package/types/instanceRegistry.d.ts +2 -1
  149. package/types/instanceRegistry.d.ts.map +1 -1
  150. 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.pendingGotQueriesPatch.push(...gotQueriesPatch(desiredQueriesPatch));
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
- return await this.postJSON("/pull", {
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 nextCookie = toWebSocketCookie(response.cookie);
307
- if (isStaleCookie(this.cookie, response.cookie)) {
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: function queueDesiredQueries(body) {
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.pendingGotQueriesPatch.push(...gotQueriesPatch(desiredQueriesPatch));
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
- return await this.postJSON("/pull", {
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 nextCookie = toWebSocketCookie(response.cookie);
515
- if (isStaleCookie(this.cookie, response.cookie)) {
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
  }