@jarenjs/contract 0.56.0 → 0.67.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 (48) hide show
  1. package/README.md +139 -25
  2. package/dist/types/adapters/fetch.d.ts +11 -10
  3. package/dist/types/adapters/node.d.ts +24 -10
  4. package/dist/types/client/http.d.ts +77 -10
  5. package/dist/types/compat.d.ts +1 -1
  6. package/dist/types/errors.d.ts +3 -0
  7. package/dist/types/host.d.ts +179 -0
  8. package/dist/types/http/body.d.ts +147 -0
  9. package/dist/types/http/dispatch.d.ts +26 -2
  10. package/dist/types/http/serve.d.ts +46 -3
  11. package/dist/types/http/wire.d.ts +31 -17
  12. package/dist/types/ledger.d.ts +57 -12
  13. package/dist/types/local/index.d.ts +7 -1
  14. package/dist/types/messages.d.ts +2 -0
  15. package/dist/types/path.d.ts +4 -2
  16. package/dist/types/pipeline.d.ts +15 -1
  17. package/dist/types/port/client.d.ts +18 -1
  18. package/dist/types/port/serve.d.ts +38 -5
  19. package/dist/types/runtime.d.ts +25 -0
  20. package/dist/types/stream/client.d.ts +14 -3
  21. package/dist/types/stream/server.d.ts +218 -44
  22. package/dist/types/stream/sse.d.ts +10 -0
  23. package/docs/APP-INTEGRATION.md +4 -2
  24. package/docs/CONTRACT-FORMAT.md +580 -128
  25. package/package.json +5 -5
  26. package/src/adapters/fetch.js +144 -25
  27. package/src/adapters/node.js +246 -82
  28. package/src/cli.js +22 -16
  29. package/src/client/http.js +588 -189
  30. package/src/compat.js +1 -1
  31. package/src/errors.js +3 -0
  32. package/src/host.js +319 -0
  33. package/src/http/body.js +337 -0
  34. package/src/http/dispatch.js +511 -75
  35. package/src/http/serve.js +39 -5
  36. package/src/http/wire.js +33 -14
  37. package/src/ledger.js +119 -36
  38. package/src/local/index.js +91 -35
  39. package/src/messages.js +2 -0
  40. package/src/path.js +9 -3
  41. package/src/pipeline.js +18 -1
  42. package/src/port/client.js +39 -6
  43. package/src/port/serve.js +207 -69
  44. package/src/project/typescript.jtlt.json +39 -7
  45. package/src/runtime.js +36 -0
  46. package/src/stream/client.js +40 -6
  47. package/src/stream/server.js +573 -138
  48. package/src/stream/sse.js +2 -0
@@ -27,8 +27,12 @@ import { STREAM_ERRORS } from './sse.js';
27
27
 
28
28
  /**
29
29
  * The callbacks of one `client.subscribe` call; every one optional.
30
+ * `onSnapshot`'s `info` is stable in shape: `reset` says the snapshot
31
+ * re-seeds a consumer whose cursor fell behind the server's retention
32
+ * (its `seq` is then the cursor to resume from), and the two watermarks
33
+ * are the server log's when it reported them (`null` on a fresh stream).
30
34
  * @typedef {Object} StreamCallbacks
31
- * @property {(value: unknown, info: { seq: number, resumed: boolean }) => void} [onSnapshot]
35
+ * @property {(value: unknown, info: { seq: number, resumed: boolean, reset: boolean, earliestAvailable: number | null, highWatermark: number | null }) => void} [onSnapshot]
32
36
  * @property {(emission: { patch: unknown[], seq: number }) => void} [onPatch]
33
37
  * @property {(outcome: Outcome) => void} [onError]
34
38
  * @property {(info: { reason: string }) => void} [onEnd]
@@ -51,7 +55,9 @@ import { STREAM_ERRORS } from './sse.js';
51
55
  * wire carried (the SSE id, the frame's `seq`) — `null` falls back to
52
56
  * the data's own `seq`; `error`/`end` take the event data; `fail` takes
53
57
  * a ready outcome (a transport failure the carrier classified). All are
54
- * no-ops once finished.
58
+ * no-ops once finished. `lastSeq` reads the cursor: the resume seq the
59
+ * caller passed until a snapshot or patch moves it — what a further
60
+ * attempt resumes from.
55
61
  * @param {StreamConsumerOptions} options
56
62
  * @returns {{ snapshot: (seq: number | null, data: unknown) => void,
57
63
  * patch: (seq: number | null, data: unknown) => void,
@@ -59,11 +65,13 @@ import { STREAM_ERRORS } from './sse.js';
59
65
  * end: (data: unknown) => void,
60
66
  * fail: (outcome: Outcome) => void,
61
67
  * cancel: () => void,
62
- * finished: () => boolean }}
68
+ * finished: () => boolean,
69
+ * lastSeq: () => number | null }}
63
70
  */
64
71
  export function createStreamConsumer(options) {
65
72
  const { route, catalog, meta, callbacks, finish } = options;
66
73
  let lastSeq = options.lastSeq;
74
+ let delivered = false;
67
75
  let done = false;
68
76
 
69
77
  /**
@@ -135,13 +143,27 @@ export function createStreamConsumer(options) {
135
143
  clientError(catalog, 'JC2053', { op: route.id }, null, projectValidationDetails(route.details, v.errors)), meta));
136
144
  return;
137
145
  }
138
- // a mid-stream snapshot (a maxPatchBytes replacement) must still advance
139
- if (at !== null && lastSeq !== null && at !== 0 && at <= lastSeq) {
146
+ const reset = envelope.reset === true;
147
+ // a mid-stream snapshot (a maxPatchBytes replacement) must still
148
+ // advance; a reset snapshot may land AT the resume cursor — the
149
+ // server's watermark had not moved past what the consumer held.
150
+ // A zero seed may replace the resume cursor only before this
151
+ // attempt delivers anything, when replay falls back to a fresh source.
152
+ const initialSeed = !delivered && at === 0;
153
+ if (at !== null && lastSeq !== null && !initialSeed && (reset ? at < lastSeq : at <= lastSeq)) {
140
154
  if (terminate()) call(callbacks.onError, streamOutcome('JC2092', {}));
141
155
  return;
142
156
  }
143
157
  if (at !== null) lastSeq = at;
144
- call(callbacks.onSnapshot, envelope.value, { seq: at === null ? 0 : at, resumed: envelope.resumed === true });
158
+ delivered = true;
159
+ const watermark = (/** @type {unknown} */ v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
160
+ call(callbacks.onSnapshot, envelope.value, {
161
+ seq: at === null ? 0 : at,
162
+ resumed: envelope.resumed === true,
163
+ reset,
164
+ earliestAvailable: watermark(envelope.earliestAvailable),
165
+ highWatermark: watermark(envelope.highWatermark),
166
+ });
145
167
  },
146
168
  patch(seq, data) {
147
169
  if (done) return;
@@ -158,6 +180,7 @@ export function createStreamConsumer(options) {
158
180
  return;
159
181
  }
160
182
  lastSeq = at;
183
+ delivered = true;
161
184
  call(callbacks.onPatch, { patch, seq: at });
162
185
  },
163
186
  error(data) {
@@ -175,6 +198,16 @@ export function createStreamConsumer(options) {
175
198
  call(callbacks.onError, failedOutcome('failure', outcomeError(code, message, null, record.details, retryable), meta));
176
199
  return;
177
200
  }
201
+ // a stream code the server ends with that is a NETWORK verdict (the
202
+ // consumer fell behind, JC2096) is a network outcome under its own
203
+ // code — retryable, and what a reconnect policy keys on
204
+ if (code !== null && Object.hasOwn(STREAM_ERRORS, code)
205
+ && STREAM_ERRORS[/** @type {keyof typeof STREAM_ERRORS} */ (code)].kind === 'network') {
206
+ const row = STREAM_ERRORS[/** @type {keyof typeof STREAM_ERRORS} */ (code)];
207
+ const message = typeof record.message === 'string' ? record.message : renderMessage(catalog, row.msgid, { op: route.id });
208
+ call(callbacks.onError, failedOutcome('network', outcomeError(code, message, null, record.details, row.retryable), meta));
209
+ return;
210
+ }
178
211
  // an undeclared server error ends the stream as a contract violation;
179
212
  // the server's record rides in details so a JC2091 stays visible
180
213
  const details = code === null ? null : {
@@ -206,6 +239,7 @@ export function createStreamConsumer(options) {
206
239
  }
207
240
  },
208
241
  finished: () => done,
242
+ lastSeq: () => lastSeq,
209
243
  };
210
244
  }
211
245