@collective-intelligence/external-apps-client-sdk 0.1.24 → 0.1.26

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/README.md CHANGED
@@ -315,8 +315,16 @@ const unsubscribe = realtime.subscribe(
315
315
  the user who received them: store and resend them unchanged, never parse or
316
316
  order them. An id the server cannot read answers `reset`.
317
317
  `reset` means the gap was too old to replay: refetch every subscribed topic.
318
+ A connection that opens without an event id, the first one included, starts
319
+ at the present: a change between your initial queries and the open would not
320
+ arrive. So the client resyncs: it calls `onReset` once that connection
321
+ opens, at the next delivery. Expect one extra refetch per page load.
318
322
  `onReset` runs at the next delivery instead of its pending changes, so the
319
- tabs that reset together refetch apart. `onAccessChanged` runs at once.
323
+ tabs that reset together refetch apart; a server `reset` and a resync in one
324
+ delivery call it once. Pass `onReset` for gap-free delivery: without it the
325
+ client cannot recover from a reset or a cursorless open, so it delivers the
326
+ pending changes as they are and a change can be lost. `onAccessChanged` runs
327
+ at once.
320
328
  - A connection without a ping for 45 seconds is reopened. Failures, 429 and 503
321
329
  back off with full jitter between 1 and 30 seconds; `Retry-After` and the
322
330
  server's `retry:` set the lower bound. The server ends each connection after
package/dist/index.d.ts CHANGED
@@ -1011,8 +1011,11 @@ type RealtimeClientOptions = Readonly<{
1011
1011
  /** A number in `[0, 1)` for the backoff jitter. */
1012
1012
  random?: () => number;
1013
1013
  /**
1014
- * The server could not replay the missed changes: refetch every subscribed
1015
- * topic. It arrives at the next delivery and replaces its pending changes.
1014
+ * Refetch every subscribed topic: the server could not replay the missed
1015
+ * changes, or a stream opened without a last event id, so changes before it
1016
+ * opened did not arrive. It arrives at the next delivery and replaces its
1017
+ * pending changes. Without it, the client cannot recover from such a gap and
1018
+ * delivers the pending changes instead.
1016
1019
  */
1017
1020
  onReset?: () => void;
1018
1021
  /** The user's access changed: re-check the session and its capabilities. */
@@ -1040,9 +1043,10 @@ type RealtimeClient = Readonly<{
1040
1043
  * One realtime connection per tab for every subscribed topic. The connection
1041
1044
  * opens while the page is visible and a topic has a listener; a topic set
1042
1045
  * change reconnects after a short debounce, and every reconnect resumes after
1043
- * the last received event. Changes and resets reach the listeners in one
1044
- * delivery per tab, 250 to 1000 ms after the first of them: a burst of
1045
- * changes refetches once, and tabs that reset together refetch apart.
1046
+ * the last received event; a stream that opens without one resets instead.
1047
+ * Changes and resets reach the listeners in one delivery per tab, 250 to
1048
+ * 1000 ms after the first of them: a burst of changes refetches once, and
1049
+ * tabs that reset together refetch apart.
1046
1050
  * Connection decisions live in the pure state machine (`./machine`); this
1047
1051
  * client only runs its commands.
1048
1052
  */
package/dist/index.js CHANGED
@@ -1038,7 +1038,7 @@ const linkTransition = (state, event) => {
1038
1038
  case "ticketFailed":
1039
1039
  case "streamEnded": return retry(state, event.retryMilliseconds);
1040
1040
  case "streamOpened": return {
1041
- commands: [watchdog(link.id)],
1041
+ commands: state.lastEventId === void 0 ? [watchdog(link.id), { type: "resync" }] : [watchdog(link.id)],
1042
1042
  state: {
1043
1043
  ...shared(state),
1044
1044
  link,
@@ -1117,9 +1117,10 @@ const defaultTimers = { schedule: (callback, milliseconds) => {
1117
1117
  * One realtime connection per tab for every subscribed topic. The connection
1118
1118
  * opens while the page is visible and a topic has a listener; a topic set
1119
1119
  * change reconnects after a short debounce, and every reconnect resumes after
1120
- * the last received event. Changes and resets reach the listeners in one
1121
- * delivery per tab, 250 to 1000 ms after the first of them: a burst of
1122
- * changes refetches once, and tabs that reset together refetch apart.
1120
+ * the last received event; a stream that opens without one resets instead.
1121
+ * Changes and resets reach the listeners in one delivery per tab, 250 to
1122
+ * 1000 ms after the first of them: a burst of changes refetches once, and
1123
+ * tabs that reset together refetch apart.
1123
1124
  * Connection decisions live in the pure state machine (`./machine`); this
1124
1125
  * client only runs its commands.
1125
1126
  */
@@ -1174,6 +1175,15 @@ const createRealtimeClient = (options) => {
1174
1175
  const scheduleDelivery = () => {
1175
1176
  cancelDelivery ??= timers.schedule(deliver, changeWindowMilliseconds + Math.floor(random() * changeJitterMilliseconds));
1176
1177
  };
1178
+ /**
1179
+ * Replaces the pending changes with one `onReset` at the next delivery.
1180
+ * Without `onReset` no refetch covers them, so they stay.
1181
+ */
1182
+ const resync = () => {
1183
+ if (options.onReset === void 0) return;
1184
+ resetPending = true;
1185
+ scheduleDelivery();
1186
+ };
1177
1187
  const route = (change) => {
1178
1188
  const key = realtimeTopicKey(change.topic);
1179
1189
  if (!registrations.has(key)) return;
@@ -1240,10 +1250,8 @@ const createRealtimeClient = (options) => {
1240
1250
  if (event.type === "change") {
1241
1251
  const change = parseRealtimeChange(event.data);
1242
1252
  if (change !== void 0) route(change);
1243
- } else if (event.type === "reset") {
1244
- resetPending = true;
1245
- scheduleDelivery();
1246
- } else if (event.type === "access-changed") options.onAccessChanged?.();
1253
+ } else if (event.type === "reset") resync();
1254
+ else if (event.type === "access-changed") options.onAccessChanged?.();
1247
1255
  },
1248
1256
  onOpen: () => {
1249
1257
  dispatch({
@@ -1305,6 +1313,9 @@ const createRealtimeClient = (options) => {
1305
1313
  resources?.close?.();
1306
1314
  return;
1307
1315
  }
1316
+ case "resync":
1317
+ resync();
1318
+ return;
1308
1319
  }
1309
1320
  };
1310
1321
  const topicsChanged = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@collective-intelligence/external-apps-client-sdk",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "description": "Framework-neutral client for Collective Intelligence external applications.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -52,5 +52,5 @@
52
52
  "typescript": "5.9.3",
53
53
  "ultracite": "7.10.7"
54
54
  },
55
- "gitHead": "5d48a1654c4bd7728d61f97818033d2685d99210"
55
+ "gitHead": "8200af5951cee11bdd23c59aba0709412aa8ea62"
56
56
  }