statewire 0.20.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/dist/client/changes.d.ts +3 -6
  2. package/dist/client/changes.d.ts.map +1 -1
  3. package/dist/client/changes.js +1 -2
  4. package/dist/client/connection.d.ts +80 -83
  5. package/dist/client/connection.d.ts.map +1 -1
  6. package/dist/client/connection.js +20 -7
  7. package/dist/client/connection.js.map +1 -1
  8. package/dist/client/core.d.ts +39 -41
  9. package/dist/client/core.d.ts.map +1 -1
  10. package/dist/client/core.js +2 -3
  11. package/dist/client/session.d.ts +93 -93
  12. package/dist/client/session.d.ts.map +1 -1
  13. package/dist/client/session.js +47 -23
  14. package/dist/client/session.js.map +1 -1
  15. package/dist/client/sessionStore.d.ts +11 -14
  16. package/dist/client/sessionStore.d.ts.map +1 -1
  17. package/dist/client/sessionStore.js +1 -2
  18. package/dist/client/storageError.d.ts +2 -5
  19. package/dist/client/storageError.d.ts.map +1 -1
  20. package/dist/client/storageError.js +1 -2
  21. package/dist/client/timing.d.ts +11 -14
  22. package/dist/client/timing.d.ts.map +1 -1
  23. package/dist/client/timing.js +1 -2
  24. package/dist/document.d.ts +17 -20
  25. package/dist/document.d.ts.map +1 -1
  26. package/dist/document.js +1 -2
  27. package/dist/host/adapter.d.ts +4 -7
  28. package/dist/host/adapter.d.ts.map +1 -1
  29. package/dist/host/adapter.js +1 -2
  30. package/dist/host/draft.d.ts +13 -16
  31. package/dist/host/draft.d.ts.map +1 -1
  32. package/dist/host/draft.js +1 -2
  33. package/dist/host/engine.d.ts +7 -10
  34. package/dist/host/engine.d.ts.map +1 -1
  35. package/dist/host/engine.js +1 -2
  36. package/dist/host/host.d.ts +42 -45
  37. package/dist/host/host.d.ts.map +1 -1
  38. package/dist/host/host.js +15 -13
  39. package/dist/host/host.js.map +1 -1
  40. package/dist/host/http.d.ts +4 -7
  41. package/dist/host/http.d.ts.map +1 -1
  42. package/dist/host/http.js +15 -11
  43. package/dist/host/http.js.map +1 -1
  44. package/dist/host/socket.d.ts +145 -140
  45. package/dist/host/socket.d.ts.map +1 -1
  46. package/dist/host/socket.js +99 -17
  47. package/dist/host/socket.js.map +1 -1
  48. package/dist/host/synced.d.ts +3 -6
  49. package/dist/host/synced.d.ts.map +1 -1
  50. package/dist/host/synced.js +1 -2
  51. package/dist/host/work.d.ts +2 -5
  52. package/dist/host/work.d.ts.map +1 -1
  53. package/dist/host/work.js +1 -2
  54. package/dist/host-internal.d.ts +99 -90
  55. package/dist/host-internal.d.ts.map +1 -1
  56. package/dist/host.d.ts +90 -91
  57. package/dist/host.d.ts.map +1 -1
  58. package/dist/index.d.ts +11 -8
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +1 -1
  61. package/dist/statewire.d.ts +265 -268
  62. package/dist/statewire.d.ts.map +1 -1
  63. package/dist/statewire.js +7 -7
  64. package/dist/statewire.js.map +1 -1
  65. package/dist/storage.d.ts +23 -26
  66. package/dist/storage.d.ts.map +1 -1
  67. package/dist/storage.js +1 -2
  68. package/dist/transports/fake-sse.d.ts +7 -10
  69. package/dist/transports/fake-sse.d.ts.map +1 -1
  70. package/dist/transports/fake-sse.js +1 -2
  71. package/dist/transports/fixtures.d.ts +19 -25
  72. package/dist/transports/fixtures.d.ts.map +1 -1
  73. package/dist/transports/fixtures.js +1 -2
  74. package/dist/transports/remote.d.ts +8 -11
  75. package/dist/transports/remote.d.ts.map +1 -1
  76. package/dist/transports/remote.js +1 -2
  77. package/dist/transports/sse-parser.d.ts +1 -4
  78. package/dist/transports/sse-parser.d.ts.map +1 -1
  79. package/dist/transports/sse-parser.js +1 -2
  80. package/dist/transports/sse.d.ts +50 -52
  81. package/dist/transports/sse.d.ts.map +1 -1
  82. package/dist/transports/sse.js +9 -3
  83. package/dist/transports/sse.js.map +1 -1
  84. package/dist/transports/testing.d.ts +31 -34
  85. package/dist/transports/testing.d.ts.map +1 -1
  86. package/dist/transports/testing.js +1 -2
  87. package/dist/transports/ws.d.ts +5 -7
  88. package/dist/transports/ws.d.ts.map +1 -1
  89. package/dist/transports/ws.js +3 -4
  90. package/dist/transports/ws.js.map +1 -1
  91. package/dist/types.d.ts +81 -78
  92. package/dist/types.d.ts.map +1 -1
  93. package/dist/types.js +1 -2
  94. package/dist/util/draftTraps.d.ts +2 -4
  95. package/dist/util/draftTraps.d.ts.map +1 -1
  96. package/dist/util/draftTraps.js +1 -2
  97. package/dist/util/macrotask.d.ts +1 -4
  98. package/dist/util/macrotask.d.ts.map +1 -1
  99. package/dist/util/macrotask.js +1 -2
  100. package/dist/util/methodProxy.d.ts +1 -4
  101. package/dist/util/methodProxy.d.ts.map +1 -1
  102. package/dist/util/methodProxy.js +1 -2
  103. package/dist/util/randomId.d.ts +1 -4
  104. package/dist/util/randomId.d.ts.map +1 -1
  105. package/dist/util/randomId.js +1 -2
  106. package/dist/util/recordMutations.d.ts +5 -7
  107. package/dist/util/recordMutations.d.ts.map +1 -1
  108. package/dist/util/recordMutations.js +1 -2
  109. package/dist/util/statePath.d.ts +9 -12
  110. package/dist/util/statePath.d.ts.map +1 -1
  111. package/dist/util/statePath.js +1 -2
  112. package/dist/wire/context.d.ts +7 -12
  113. package/dist/wire/context.d.ts.map +1 -1
  114. package/dist/wire/context.js +2 -3
  115. package/dist/wire/documents.d.ts +36 -39
  116. package/dist/wire/documents.d.ts.map +1 -1
  117. package/dist/wire/documents.js +2 -3
  118. package/dist/wire/documents.js.map +1 -1
  119. package/dist/wire/limits.d.ts +7 -10
  120. package/dist/wire/limits.d.ts.map +1 -1
  121. package/dist/wire/limits.js +1 -2
  122. package/dist/wire/meta.d.ts +9 -11
  123. package/dist/wire/meta.d.ts.map +1 -1
  124. package/dist/wire/meta.js +1 -2
  125. package/dist/wire/meta.js.map +1 -1
  126. package/dist/wire/ops.d.ts +9 -12
  127. package/dist/wire/ops.d.ts.map +1 -1
  128. package/dist/wire/ops.js +1 -2
  129. package/dist/wire/protocol.d.ts +53 -56
  130. package/dist/wire/protocol.d.ts.map +1 -1
  131. package/dist/wire/protocol.js +1 -2
  132. package/dist/wire/version.d.ts +1 -4
  133. package/dist/wire/version.d.ts.map +1 -1
  134. package/dist/wire/version.js +1 -2
  135. package/package.json +6 -6
  136. package/src/client/connection.ts +25 -15
  137. package/src/client/session.ts +54 -15
  138. package/src/host/host.ts +9 -3
  139. package/src/host/http.ts +37 -5
  140. package/src/host/socket.ts +158 -9
  141. package/src/host-internal.ts +14 -0
  142. package/src/statewire.ts +11 -6
  143. package/src/transports/sse.ts +12 -0
@@ -1,6 +1,6 @@
1
+ import { DEFAULT_PROTOCOL, WIRE_VERSION, negotiateProtocols } from "../wire/protocol.js";
1
2
  import { encodeDocument, reviveDocumentValue } from "../document.js";
2
3
  import { COMMAND_CAPACITY_RETRY_MS, MAX_INFLIGHT_COMMANDS_PER_HOST } from "../wire/limits.js";
3
- import { DEFAULT_PROTOCOL, WIRE_VERSION, negotiateProtocols } from "../wire/protocol.js";
4
4
  import { captureContext, contextValue } from "../wire/context.js";
5
5
  import { sanitizeValue } from "../wire/ops.js";
6
6
  import { StatewireSendError } from "../types.js";
@@ -9,7 +9,7 @@ import { createMacrotaskPoster } from "../util/macrotask.js";
9
9
  import { frames, stream } from "./http.js";
10
10
  import { useEffect, useState } from "@assistant-ui/tap/react-shim";
11
11
  import { createTapRoot, flushTapSync, useResource } from "@assistant-ui/tap";
12
- //#region src/host/socket.ts
12
+ // src/host/socket.ts
13
13
  const CLIENT_ID_RE = /^[A-Za-z0-9._~-]{1,256}$/;
14
14
  const PRINCIPAL_MISMATCH_MESSAGE = "client id is bound to another principal";
15
15
  /** Structural equality over auth values: primitives, Dates, arrays, and plain
@@ -149,11 +149,13 @@ const cmdOf = (adm, outcome) => {
149
149
  ...encodePayload(failure.payload)
150
150
  };
151
151
  }
152
- default: return {
153
- ...at,
154
- type: "failed",
155
- message: outcome.error instanceof Error ? outcome.error.message : String(outcome.error)
156
- };
152
+ default:
153
+ console.error("statewire command failed", outcome.error);
154
+ return {
155
+ ...at,
156
+ type: "failed",
157
+ message: "the command failed"
158
+ };
157
159
  }
158
160
  };
159
161
  const parseInitialFrame = (raw) => {
@@ -211,6 +213,10 @@ const closeConn = (h, conn, code, reason) => {
211
213
  if (!conn.open) return;
212
214
  conn.open = false;
213
215
  h.conns.delete(conn);
216
+ if (conn.leaseTimer !== null) {
217
+ clearTimeout(conn.leaseTimer);
218
+ conn.leaseTimer = null;
219
+ }
214
220
  try {
215
221
  conn.socket.close(code, reason);
216
222
  } catch {}
@@ -229,6 +235,35 @@ const sendTo = (h, conn, data) => {
229
235
  closeConn(h, conn);
230
236
  }
231
237
  };
238
+ const expiredAt = (h, authorizedAt) => {
239
+ const now = Date.now();
240
+ return authorizedAt === void 0 || !Number.isFinite(authorizedAt) || authorizedAt < 0 || authorizedAt > now || h.maxLeaseLifetimeMs !== null && now - authorizedAt >= h.maxLeaseLifetimeMs;
241
+ };
242
+ const leaseExpiredFin = JSON.stringify({ fin: {
243
+ reason: "reconnect",
244
+ code: "lease-expired"
245
+ } });
246
+ const expireLease = (h, conn) => {
247
+ if (!conn.open || expiredAt(h, conn.authorizedAt)) {
248
+ if (conn.open) {
249
+ sendTo(h, conn, leaseExpiredFin);
250
+ closeConn(h, conn, 1e3, "lease-expired");
251
+ }
252
+ return true;
253
+ }
254
+ return false;
255
+ };
256
+ const armLeaseTimer = (h, conn) => {
257
+ if (conn.leaseTimer !== null) clearTimeout(conn.leaseTimer);
258
+ conn.leaseTimer = null;
259
+ if (h.disposed || h.maxLeaseLifetimeMs === null || expireLease(h, conn)) return;
260
+ const delay = h.maxLeaseLifetimeMs - (Date.now() - conn.authorizedAt);
261
+ conn.leaseTimer = setTimeout(() => {
262
+ conn.leaseTimer = null;
263
+ if (!expireLease(h, conn)) armLeaseTimer(h, conn);
264
+ }, Math.min(delay, 2147483647));
265
+ conn.leaseTimer.unref?.();
266
+ };
232
267
  const violate = (h, conn, reason) => {
233
268
  sendTo(h, conn, JSON.stringify({ fin: {
234
269
  reason: "protocol-error",
@@ -319,6 +354,11 @@ const diffView = (h, conn) => {
319
354
  }
320
355
  return ops;
321
356
  };
357
+ const notifyIncluded = (conn) => conn.onIncludedChange?.([...conn.included].map((id) => JSON.parse(id)));
358
+ const restoredIncluded = (included) => new Set(included.map((entry) => {
359
+ if (!Array.isArray(entry) || entry.length !== 2 || typeof entry[0] !== "string" || !(entry[1] === null || Array.isArray(entry[1]) && entry[1].every((part) => typeof part === "string"))) throw new TypeError("statewire: invalid restored include");
360
+ return documentIdentity(entry[0], entry[1] ?? void 0);
361
+ }));
322
362
  const attachOf = (h, conn) => {
323
363
  const requireSelected = (protocol) => {
324
364
  const entry = conn.selection.protocols.find((p) => p.name === protocol);
@@ -361,11 +401,15 @@ const attachOf = (h, conn) => {
361
401
  },
362
402
  include: (protocol, key) => {
363
403
  if (requireSelected(protocol).record !== void 0) throw new Error(`statewire: protocol ${protocol} is not durable`);
364
- conn.included.add(documentIdentity(protocol, key));
404
+ const id = documentIdentity(protocol, key);
405
+ if (conn.included.has(id)) return;
406
+ conn.included.add(id);
407
+ notifyIncluded(conn);
365
408
  markViews(h);
366
409
  },
367
410
  exclude: (protocol, key) => {
368
- conn.included.delete(documentIdentity(protocol, key));
411
+ if (!conn.included.delete(documentIdentity(protocol, key))) return;
412
+ notifyIncluded(conn);
369
413
  markViews(h);
370
414
  }
371
415
  };
@@ -626,6 +670,7 @@ const installContexts = (h, conn, frame) => {
626
670
  for (const entry of frame.ctx ?? []) h.current?.contextChanged?.(entry.protocol, captureContext(entry.value), conn.attach);
627
671
  };
628
672
  const admitFrame = (h, conn, frame) => {
673
+ if (expireLease(h, conn)) return { type: "expired" };
629
674
  validateContexts(h, conn, frame);
630
675
  installContexts(h, conn, frame);
631
676
  const rec = h.clients.get(conn.clientId);
@@ -659,6 +704,7 @@ const admitFrame = (h, conn, frame) => {
659
704
  return refusal(h, conn, retry);
660
705
  }
661
706
  }
707
+ if (expireLease(h, conn)) return { type: "expired" };
662
708
  for (const s of fresh) admitCmd(h, lanes.get(s.protocol), conn, s);
663
709
  for (const [protocol, lane] of lanes) {
664
710
  const last = fresh.findLast((s) => s.protocol === protocol);
@@ -673,7 +719,7 @@ const admitFrame = (h, conn, frame) => {
673
719
  return { type: "ok" };
674
720
  };
675
721
  const message = (h, conn, data) => {
676
- if (!conn.open || h.disposed || h.finished !== null) return;
722
+ if (!conn.open || h.disposed || h.finished !== null || expireLease(h, conn)) return;
677
723
  if (frameIsOversized(data)) {
678
724
  sendTo(h, conn, JSON.stringify({ fin: {
679
725
  reason: "protocol-error",
@@ -755,7 +801,18 @@ const normalizedHeaders = (headers) => {
755
801
  };
756
802
  const connectAuthorized = (h, clientId, socket, options) => {
757
803
  const authorize = h.authorize;
758
- if (options.authorized === true) return register(h, clientId, socket, options.authorized === true, options.auth, options.selection)?.connection ?? null;
804
+ if (options.authorized === true) {
805
+ if (expiredAt(h, options.authorizedAt)) {
806
+ try {
807
+ socket.send(leaseExpiredFin);
808
+ } catch {}
809
+ try {
810
+ socket.close(1e3, "lease-expired");
811
+ } catch {}
812
+ return null;
813
+ }
814
+ return register(h, clientId, socket, options.authorized === true, options.auth, options.selection, void 0, options.authorizedAt, options)?.connection ?? null;
815
+ }
759
816
  const refuse = (code, reason) => {
760
817
  try {
761
818
  socket.close(code, reason);
@@ -846,19 +903,20 @@ const connectAuthorized = (h, clientId, socket, options) => {
846
903
  pending.authorizing = true;
847
904
  Promise.resolve().then(() => authorize?.({ headers })).then((auth) => {
848
905
  if (!pending.open || h.disposed || h.finished !== null) return;
906
+ const authorizedAt = Date.now();
849
907
  const selection = negotiateProtocols(new Headers(headers), h.wireVersion, [h.protocol]);
850
908
  if ("fin" in selection) {
851
909
  sendPending(pending, JSON.stringify(selection));
852
910
  closePending(h, pending, 1008, selection.fin.message);
853
911
  return;
854
912
  }
855
- options.onAuthorized?.(auth, selection);
913
+ options.onAuthorized?.(auth, selection, authorizedAt);
856
914
  pending.open = false;
857
915
  h.pendingConns.delete(pending);
858
916
  if (pending.timer !== null) clearTimeout(pending.timer);
859
917
  let registration;
860
918
  try {
861
- registration = register(h, clientId, socket, true, auth, selection, frame);
919
+ registration = register(h, clientId, socket, true, auth, selection, frame, authorizedAt, options.onIncludedChange === void 0 ? {} : { onIncludedChange: options.onIncludedChange });
862
920
  } catch (error) {
863
921
  if (violationReason(error) === null) throw error;
864
922
  return;
@@ -902,7 +960,8 @@ const register = (h, clientId, socket, sawFrame = false, auth = void 0, selectio
902
960
  name: h.protocol.name,
903
961
  version: h.protocol.version
904
962
  }]
905
- }, frame = { cmd: [] }) => {
963
+ }, frame = { cmd: [] }, authorizedAt, included = {}) => {
964
+ const restored = restoredIncluded(included.included ?? []);
906
965
  const refuse = (code, reason) => {
907
966
  try {
908
967
  socket.close(code, reason);
@@ -920,6 +979,12 @@ const register = (h, clientId, socket, sawFrame = false, auth = void 0, selectio
920
979
  return null;
921
980
  }
922
981
  if (atSubscriberLimit(h)) return refuseSubscriberLimit(socket);
982
+ if (expiredAt(h, authorizedAt)) {
983
+ try {
984
+ socket.send(leaseExpiredFin);
985
+ } catch {}
986
+ return refuse(1e3, "lease-expired");
987
+ }
923
988
  const known = h.clients.get(clientId);
924
989
  if (known !== void 0 && !authEquals(known.principal, auth)) {
925
990
  try {
@@ -952,10 +1017,13 @@ const register = (h, clientId, socket, sawFrame = false, auth = void 0, selectio
952
1017
  lastSentAt: 0,
953
1018
  lease: null,
954
1019
  auth,
1020
+ authorizedAt,
1021
+ leaseTimer: null,
955
1022
  lanes: /* @__PURE__ */ new Map(),
956
1023
  contexts: /* @__PURE__ */ new Map(),
957
1024
  documents: /* @__PURE__ */ new Map(),
958
- included: /* @__PURE__ */ new Set(),
1025
+ included: restored,
1026
+ onIncludedChange: included.onIncludedChange,
959
1027
  view: /* @__PURE__ */ new Map(),
960
1028
  nextDoc: 1,
961
1029
  pendingApplied: [],
@@ -976,6 +1044,8 @@ const register = (h, clientId, socket, sawFrame = false, auth = void 0, selectio
976
1044
  }
977
1045
  gcClients(h);
978
1046
  ensureHeartbeat(h);
1047
+ armLeaseTimer(h, conn);
1048
+ if (!conn.open) return null;
979
1049
  const isEphemeral = (s) => selection.protocols.find((p) => p.name === s.protocol)?.record === "ephemeral";
980
1050
  const early = frame.cmd.filter(isEphemeral);
981
1051
  const late = frame.cmd.filter((s) => !isEphemeral(s));
@@ -993,6 +1063,7 @@ const register = (h, clientId, socket, sawFrame = false, auth = void 0, selectio
993
1063
  const earlyAdmission = admit(early);
994
1064
  if (early.length > 0) flush(h);
995
1065
  notifyClientRecords(h);
1066
+ if (expireLease(h, conn)) return null;
996
1067
  const ops = [];
997
1068
  if (selects(conn, h.protocol.name)) {
998
1069
  const { value, meta } = encodeDocument(h.stateHost.snapshot());
@@ -1051,9 +1122,14 @@ const resume = (h) => {
1051
1122
  h.disposed = false;
1052
1123
  h.stateHost?.setOnOp((op) => recordOp(h, op));
1053
1124
  h.stateHost?.setOnDocuments(() => markViews(h));
1125
+ for (const conn of h.conns) armLeaseTimer(h, conn);
1054
1126
  };
1055
1127
  const suspend = (h) => {
1056
1128
  h.disposed = true;
1129
+ for (const conn of h.conns) {
1130
+ if (conn.leaseTimer !== null) clearTimeout(conn.leaseTimer);
1131
+ conn.leaseTimer = null;
1132
+ }
1057
1133
  if (h.heartbeatTimer !== null) {
1058
1134
  clearInterval(h.heartbeatTimer);
1059
1135
  h.heartbeatTimer = null;
@@ -1087,6 +1163,7 @@ const useSocketHost = (transport, options, persistence) => {
1087
1163
  key: void 0,
1088
1164
  wireVersion: { version: WIRE_VERSION },
1089
1165
  heartbeatMs: 15e3,
1166
+ maxLeaseLifetimeMs: 864e5,
1090
1167
  wsInitTimeoutMs: 5e3,
1091
1168
  maxSubscribers: MAX_SUBSCRIBERS,
1092
1169
  maxInflightCommands: MAX_INFLIGHT_COMMANDS_PER_HOST,
@@ -1129,6 +1206,12 @@ const useSocketHost = (transport, options, persistence) => {
1129
1206
  h.key = options?.key;
1130
1207
  h.wireVersion = options?.wireVersion ?? { version: "2026-09-30" };
1131
1208
  setHeartbeatMs(h, options?.heartbeatIntervalMs ?? 15e3);
1209
+ const maxLeaseLifetimeMs = options?.maxLeaseLifetimeMs === void 0 ? 864e5 : options.maxLeaseLifetimeMs;
1210
+ if (maxLeaseLifetimeMs !== null && (!Number.isFinite(maxLeaseLifetimeMs) || maxLeaseLifetimeMs <= 0)) throw new TypeError("maxLeaseLifetimeMs must be finite and positive or null");
1211
+ if (h.maxLeaseLifetimeMs !== maxLeaseLifetimeMs) {
1212
+ h.maxLeaseLifetimeMs = maxLeaseLifetimeMs;
1213
+ for (const conn of [...h.conns]) armLeaseTimer(h, conn);
1214
+ }
1132
1215
  h.pushback = options?.pushback;
1133
1216
  h.wsInitTimeoutMs = options?.wsInitTimeoutMs ?? 5e3;
1134
1217
  const maxSubscribers = options?.maxSubscribers === void 0 ? MAX_SUBSCRIBERS : options.maxSubscribers;
@@ -1186,7 +1269,6 @@ const StatewireSocketHost = (transport, options, persistence) => {
1186
1269
  }
1187
1270
  };
1188
1271
  };
1189
- //#endregion
1190
- export { CLIENT_ID_RE, PRINCIPAL_MISMATCH_MESSAGE, SUBSCRIBER_LIMIT_MESSAGE, StatewireSocketHost, admitFrame, atSubscriberLimit, authEquals, closeConn, flush, parseFrame, parseFrameValue, register, sendTo };
1272
+ export { CLIENT_ID_RE, PRINCIPAL_MISMATCH_MESSAGE, SUBSCRIBER_LIMIT_MESSAGE, StatewireSocketHost, admitFrame, atSubscriberLimit, authEquals, closeConn, expireLease, flush, parseFrame, parseFrameValue, register, sendTo };
1191
1273
 
1192
1274
  //# sourceMappingURL=socket.js.map