@alpamayo-solutions/colca-client 0.36.2 → 0.36.4

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
@@ -27,6 +27,11 @@ for await (const record of panels.follow({ bell })) {
27
27
  }
28
28
  ```
29
29
 
30
+ `follow()` also reads from its cursor when the bell stayed silent for
31
+ `idleDrainMs` (five minutes by default), so a ring that was lost does not stall
32
+ the consumer for good. That is a recovery read, not a polling cadence: keep it
33
+ long. `idleDrainMs: null` waits for a ring alone.
34
+
30
35
  ## A producer
31
36
 
32
37
  ```ts
@@ -6,16 +6,44 @@
6
6
  * takes `generation` before it drains and afterwards waits for a newer one, so
7
7
  * a ring that arrives while it drains is never lost. Rings are coalesced: many
8
8
  * rings during one drain cost one more drain.
9
+ *
10
+ * A ring can still go missing before it reaches the bell: a subscription the
11
+ * broker dropped, a `/watch` connection that died without an error, a wiring
12
+ * mistake in the caller. So a consumer bounds its wait (`timeoutMs`) and reads
13
+ * from its cursor again when the bound passes. One lost ring then costs a
14
+ * delay, not a consumer that stands still until it is restarted.
15
+ */
16
+ /**
17
+ * How long a consumer waits on a silent bell before it reads from its cursor
18
+ * anyway: five minutes, the value the Python SDK uses. It is a recovery read
19
+ * for a ring that never arrived, not a polling cadence — the bell is what
20
+ * keeps a consumer current, and this must stay long enough to say so.
9
21
  */
22
+ export declare const IDLE_DRAIN_MS = 300000;
23
+ /** Why a wait on the bell ended. */
24
+ export type Wake = "rang" | "timeout" | "aborted";
25
+ export interface AfterOptions {
26
+ /** Ends the wait with `"aborted"`. */
27
+ signal?: AbortSignal;
28
+ /**
29
+ * Ends the wait with `"timeout"` when nothing rang for this long. Greater
30
+ * than zero; without it the wait has no bound.
31
+ */
32
+ timeoutMs?: number;
33
+ }
10
34
  export declare class Doorbell {
11
35
  #private;
12
36
  /** Increases with every ring. */
13
37
  get generation(): number;
14
38
  ring(): void;
15
39
  /**
16
- * Resolves once the bell rang after `since` was taken — at once when it
17
- * already has — or when `signal` aborts.
40
+ * Resolves with why the wait ended: `"rang"` once the bell rang after
41
+ * `since` was taken — at once when it already has —, `"aborted"` when
42
+ * `signal` aborts, `"timeout"` when `timeoutMs` passed without a ring. The
43
+ * caller reads from its cursor again on `"rang"` and on `"timeout"` alike.
44
+ *
45
+ * The options may also be the signal alone.
18
46
  */
19
- after(since: number, signal?: AbortSignal): Promise<void>;
47
+ after(since: number, options?: AbortSignal | AfterOptions): Promise<Wake>;
20
48
  }
21
49
  //# sourceMappingURL=doorbell.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"doorbell.d.ts","sourceRoot":"","sources":["../src/doorbell.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,qBAAa,QAAQ;;IAInB,iCAAiC;IACjC,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,IAAI,IAAI,IAAI;IAOZ;;;OAGG;IACG,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;CAahE"}
1
+ {"version":3,"file":"doorbell.d.ts","sourceRoot":"","sources":["../src/doorbell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,aAAa,SAAU,CAAC;AAErC,oCAAoC;AACpC,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;AAElD,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAKD,qBAAa,QAAQ;;IAInB,iCAAiC;IACjC,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,IAAI,IAAI,IAAI;IAOZ;;;;;;;OAOG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,WAAW,GAAG,YAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;CAoC9E"}
package/dist/doorbell.js CHANGED
@@ -6,7 +6,22 @@
6
6
  * takes `generation` before it drains and afterwards waits for a newer one, so
7
7
  * a ring that arrives while it drains is never lost. Rings are coalesced: many
8
8
  * rings during one drain cost one more drain.
9
+ *
10
+ * A ring can still go missing before it reaches the bell: a subscription the
11
+ * broker dropped, a `/watch` connection that died without an error, a wiring
12
+ * mistake in the caller. So a consumer bounds its wait (`timeoutMs`) and reads
13
+ * from its cursor again when the bound passes. One lost ring then costs a
14
+ * delay, not a consumer that stands still until it is restarted.
15
+ */
16
+ /**
17
+ * How long a consumer waits on a silent bell before it reads from its cursor
18
+ * anyway: five minutes, the value the Python SDK uses. It is a recovery read
19
+ * for a ring that never arrived, not a polling cadence — the bell is what
20
+ * keeps a consumer current, and this must stay long enough to say so.
9
21
  */
22
+ export const IDLE_DRAIN_MS = 300_000;
23
+ /** The longest delay a timer takes; a longer one would fire at once. */
24
+ const MAX_TIMER_MS = 2 ** 31 - 1;
10
25
  export class Doorbell {
11
26
  #generation = 0;
12
27
  #waiters = new Set();
@@ -22,21 +37,46 @@ export class Doorbell {
22
37
  wake();
23
38
  }
24
39
  /**
25
- * Resolves once the bell rang after `since` was taken — at once when it
26
- * already has — or when `signal` aborts.
40
+ * Resolves with why the wait ended: `"rang"` once the bell rang after
41
+ * `since` was taken — at once when it already has —, `"aborted"` when
42
+ * `signal` aborts, `"timeout"` when `timeoutMs` passed without a ring. The
43
+ * caller reads from its cursor again on `"rang"` and on `"timeout"` alike.
44
+ *
45
+ * The options may also be the signal alone.
27
46
  */
28
- async after(since, signal) {
29
- while (this.#generation === since && !signal?.aborted) {
30
- await new Promise((resolve) => {
31
- const done = () => {
32
- this.#waiters.delete(done);
33
- signal?.removeEventListener("abort", done);
34
- resolve();
35
- };
36
- this.#waiters.add(done);
37
- signal?.addEventListener("abort", done, { once: true });
38
- });
47
+ after(since, options = {}) {
48
+ const { signal, timeoutMs } = "aborted" in options ? { signal: options } : options;
49
+ if (timeoutMs !== undefined && !(timeoutMs > 0)) {
50
+ return Promise.reject(new RangeError(`timeoutMs must be greater than zero, got ${String(timeoutMs)}`));
39
51
  }
52
+ if (this.#generation !== since)
53
+ return Promise.resolve("rang");
54
+ if (signal?.aborted)
55
+ return Promise.resolve("aborted");
56
+ return new Promise((resolve) => {
57
+ let timer;
58
+ // Whatever ends the wait takes the other two with it, so a consumer that
59
+ // waits for days leaves no timer and no listener behind per wait.
60
+ const end = (why) => {
61
+ this.#waiters.delete(rang);
62
+ signal?.removeEventListener("abort", aborted);
63
+ clearTimeout(timer);
64
+ resolve(why);
65
+ };
66
+ const rang = () => {
67
+ end("rang");
68
+ };
69
+ const aborted = () => {
70
+ end("aborted");
71
+ };
72
+ this.#waiters.add(rang);
73
+ signal?.addEventListener("abort", aborted, { once: true });
74
+ if (timeoutMs !== undefined) {
75
+ timer = setTimeout(() => {
76
+ end("timeout");
77
+ }, Math.min(timeoutMs, MAX_TIMER_MS));
78
+ }
79
+ });
40
80
  }
41
81
  }
42
82
  //# sourceMappingURL=doorbell.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"doorbell.js","sourceRoot":"","sources":["../src/doorbell.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,OAAO,QAAQ;IACnB,WAAW,GAAG,CAAC,CAAC;IAChB,QAAQ,GAAG,IAAI,GAAG,EAAc,CAAC;IAEjC,iCAAiC;IACjC,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED,IAAI;QACF,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC;QACtB,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,KAAK,MAAM,IAAI,IAAI,OAAO;YAAE,IAAI,EAAE,CAAC;IACrC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,KAAK,CAAC,KAAa,EAAE,MAAoB;QAC7C,OAAO,IAAI,CAAC,WAAW,KAAK,KAAK,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;YACtD,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;gBAClC,MAAM,IAAI,GAAG,GAAS,EAAE;oBACtB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;oBAC3B,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;oBAC3C,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC;gBACF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBACxB,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1D,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"doorbell.js","sourceRoot":"","sources":["../src/doorbell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAC;AAerC,wEAAwE;AACxE,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAEjC,MAAM,OAAO,QAAQ;IACnB,WAAW,GAAG,CAAC,CAAC;IAChB,QAAQ,GAAG,IAAI,GAAG,EAAc,CAAC;IAEjC,iCAAiC;IACjC,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED,IAAI;QACF,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC;QACtB,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,KAAK,MAAM,IAAI,IAAI,OAAO;YAAE,IAAI,EAAE,CAAC;IACrC,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,KAAa,EAAE,UAAsC,EAAE;QAC3D,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAiB,SAAS,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;QACjG,IAAI,SAAS,KAAK,SAAS,IAAI,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,EAAE,CAAC;YAChD,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,UAAU,CAAC,4CAA4C,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;QACzG,CAAC;QACD,IAAI,IAAI,CAAC,WAAW,KAAK,KAAK;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC/D,IAAI,MAAM,EAAE,OAAO;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAEvD,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACnC,IAAI,KAAgD,CAAC;YACrD,yEAAyE;YACzE,kEAAkE;YAClE,MAAM,GAAG,GAAG,CAAC,GAAS,EAAQ,EAAE;gBAC9B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBAC3B,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAC9C,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,OAAO,CAAC,GAAG,CAAC,CAAC;YACf,CAAC,CAAC;YACF,MAAM,IAAI,GAAG,GAAS,EAAE;gBACtB,GAAG,CAAC,MAAM,CAAC,CAAC;YACd,CAAC,CAAC;YACF,MAAM,OAAO,GAAG,GAAS,EAAE;gBACzB,GAAG,CAAC,SAAS,CAAC,CAAC;YACjB,CAAC,CAAC;YACF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACxB,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3D,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;gBAC5B,KAAK,GAAG,UAAU,CAChB,GAAG,EAAE;oBACH,GAAG,CAAC,SAAS,CAAC,CAAC;gBACjB,CAAC,EACD,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,YAAY,CAAC,CAClC,CAAC;YACJ,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
package/dist/index.d.ts CHANGED
@@ -4,14 +4,16 @@
4
4
  * ```ts
5
5
  * const door = new Door({ baseUrl: "http://colca", service: "my-app" });
6
6
  * const stream = new Stream(door, "annotations", door.cursorName("panels"));
7
- * for await (const record of stream.follow()) {
7
+ * const bell = new Doorbell(); // rung whenever the stream may have grown
8
+ * for await (const record of stream.follow({ bell })) {
8
9
  * console.log(record.topic, record.payload);
9
10
  * }
10
11
  * ```
11
12
  */
12
13
  export { Door, DoorError } from "./door.js";
13
14
  export type { Attribution, DoorOptions, DoorRecord, FetchOptions, Gap, KvEntry, KvLevel, KvOptions, Page, PublishResult, SelfInfo, } from "./door.js";
14
- export { Doorbell } from "./doorbell.js";
15
+ export { Doorbell, IDLE_DRAIN_MS } from "./doorbell.js";
16
+ export type { AfterOptions, Wake } from "./doorbell.js";
15
17
  export { Stream } from "./stream.js";
16
18
  export type { FollowOptions, StreamOptions } from "./stream.js";
17
19
  export { DEFAULT_ROOT, TOPIC_VERSION, parseTopic, topic, topicMatches, topicPrefix } from "./topics.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC5C,YAAY,EACV,WAAW,EACX,WAAW,EACX,UAAU,EACV,YAAY,EACZ,GAAG,EACH,OAAO,EACP,OAAO,EACP,SAAS,EACT,IAAI,EACJ,aAAa,EACb,QAAQ,GACT,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEhE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACxG,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE5D,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC5F,YAAY,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAElD,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,eAAe,EACf,qBAAqB,GACtB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,EACb,kBAAkB,GACnB,MAAM,cAAc,CAAC;AAGtB,cAAc,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC5C,YAAY,EACV,WAAW,EACX,WAAW,EACX,UAAU,EACV,YAAY,EACZ,GAAG,EACH,OAAO,EACP,OAAO,EACP,SAAS,EACT,IAAI,EACJ,aAAa,EACb,QAAQ,GACT,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACxD,YAAY,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEhE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACxG,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE5D,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC5F,YAAY,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAElD,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,eAAe,EACf,qBAAqB,GACtB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,EACb,kBAAkB,GACnB,MAAM,cAAc,CAAC;AAGtB,cAAc,0BAA0B,CAAC"}
package/dist/index.js CHANGED
@@ -4,13 +4,14 @@
4
4
  * ```ts
5
5
  * const door = new Door({ baseUrl: "http://colca", service: "my-app" });
6
6
  * const stream = new Stream(door, "annotations", door.cursorName("panels"));
7
- * for await (const record of stream.follow()) {
7
+ * const bell = new Doorbell(); // rung whenever the stream may have grown
8
+ * for await (const record of stream.follow({ bell })) {
8
9
  * console.log(record.topic, record.payload);
9
10
  * }
10
11
  * ```
11
12
  */
12
13
  export { Door, DoorError } from "./door.js";
13
- export { Doorbell } from "./doorbell.js";
14
+ export { Doorbell, IDLE_DRAIN_MS } from "./doorbell.js";
14
15
  export { Stream } from "./stream.js";
15
16
  export { DEFAULT_ROOT, TOPIC_VERSION, parseTopic, topic, topicMatches, topicPrefix } from "./topics.js";
16
17
  export { annotationIdMaterial, deriveAnnotationId, newUlid, ulidFromBytes } from "./ids.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAe5C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAGxG,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG5F,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,eAAe,EACf,qBAAqB,GACtB,MAAM,cAAc,CAAC;AAStB,yEAAyE;AACzE,cAAc,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAe5C,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAExD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAGxG,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG5F,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,eAAe,EACf,qBAAqB,GACtB,MAAM,cAAc,CAAC;AAStB,yEAAyE;AACzE,cAAc,0BAA0B,CAAC"}
package/dist/stream.d.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * must be able to run twice on the same record.
11
11
  */
12
12
  import type { Door, DoorRecord, Gap, Page } from "./door.js";
13
- import type { Doorbell } from "./doorbell.js";
13
+ import { type Doorbell } from "./doorbell.js";
14
14
  export interface StreamOptions {
15
15
  /** Records per page, 1…1000. */
16
16
  max?: number;
@@ -37,6 +37,14 @@ export interface FollowOptions {
37
37
  */
38
38
  bell: Doorbell;
39
39
  signal?: AbortSignal;
40
+ /**
41
+ * How long to wait on a silent bell before reading from the cursor anyway,
42
+ * `IDLE_DRAIN_MS` (five minutes) when omitted. It recovers from a ring that
43
+ * never arrived and is not a polling cadence: keep it long, and ring the
44
+ * bell for everything that can grow the stream. `null` waits for a ring
45
+ * alone — then a lost ring stalls the consumer until `signal` aborts.
46
+ */
47
+ idleDrainMs?: number | null;
40
48
  }
41
49
  export declare class Stream {
42
50
  #private;
@@ -60,9 +68,16 @@ export declare class Stream {
60
68
  drain(signal?: AbortSignal): AsyncGenerator<DoorRecord>;
61
69
  /**
62
70
  * Drain now, then again after every ring of `bell`, until `signal` aborts.
63
- * Nothing is read on a timer: a ring during a drain leads to one more drain,
64
- * and a consumer rings the bell on reconnect so what arrived meanwhile is read.
71
+ * A ring during a drain leads to one more drain, and a consumer rings the
72
+ * bell on reconnect so what arrived meanwhile is read.
73
+ *
74
+ * The wait on the bell is bounded by `idleDrainMs`: when nothing rang for
75
+ * that long, the stream is read from its cursor once more. That read is the
76
+ * recovery for a ring that was lost on its way to the bell, which would
77
+ * otherwise leave the consumer waiting forever beside a growing stream. It
78
+ * is not how a consumer learns of new records, and an idle stream costs one
79
+ * empty page per bound.
65
80
  */
66
- follow({ bell, signal }: FollowOptions): AsyncGenerator<DoorRecord>;
81
+ follow({ bell, signal, idleDrainMs }: FollowOptions): AsyncGenerator<DoorRecord>;
67
82
  }
68
83
  //# sourceMappingURL=stream.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAgB,GAAG,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC3E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,qBAAa,MAAM;;IAEf,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAHP,IAAI,EAAE,IAAI,EAClB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACN,OAAO,GAAE,aAAkB;IAG9C,mEAAmE;IAC7D,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C,4EAA4E;IACtE,GAAG,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAItD,kEAAkE;IAC5D,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B;;;OAGG;IACG,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAS/C,mGAAmG;IAC5F,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,cAAc,CAAC,UAAU,CAAC;IAY9D;;;;OAIG;IACI,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,aAAa,GAAG,cAAc,CAAC,UAAU,CAAC;CAiB3E"}
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAgB,GAAG,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC3E,OAAO,EAAiB,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE7D,MAAM,WAAW,aAAa;IAC5B,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,qBAAa,MAAM;;IAEf,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAHP,IAAI,EAAE,IAAI,EAClB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACN,OAAO,GAAE,aAAkB;IAG9C,mEAAmE;IAC7D,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C,4EAA4E;IACtE,GAAG,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAItD,kEAAkE;IAC5D,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B;;;OAGG;IACG,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAS/C,mGAAmG;IAC5F,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,cAAc,CAAC,UAAU,CAAC;IAY9D;;;;;;;;;;;OAWG;IACI,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,WAA2B,EAAE,EAAE,aAAa,GAAG,cAAc,CAAC,UAAU,CAAC;CAoBxG"}
package/dist/stream.js CHANGED
@@ -9,6 +9,7 @@
9
9
  * last one. A consumer that throws mid-page sees that page again, so handlers
10
10
  * must be able to run twice on the same record.
11
11
  */
12
+ import { IDLE_DRAIN_MS } from "./doorbell.js";
12
13
  export class Stream {
13
14
  door;
14
15
  name;
@@ -59,14 +60,24 @@ export class Stream {
59
60
  }
60
61
  /**
61
62
  * Drain now, then again after every ring of `bell`, until `signal` aborts.
62
- * Nothing is read on a timer: a ring during a drain leads to one more drain,
63
- * and a consumer rings the bell on reconnect so what arrived meanwhile is read.
63
+ * A ring during a drain leads to one more drain, and a consumer rings the
64
+ * bell on reconnect so what arrived meanwhile is read.
65
+ *
66
+ * The wait on the bell is bounded by `idleDrainMs`: when nothing rang for
67
+ * that long, the stream is read from its cursor once more. That read is the
68
+ * recovery for a ring that was lost on its way to the bell, which would
69
+ * otherwise leave the consumer waiting forever beside a growing stream. It
70
+ * is not how a consumer learns of new records, and an idle stream costs one
71
+ * empty page per bound.
64
72
  */
65
- async *follow({ bell, signal }) {
73
+ async *follow({ bell, signal, idleDrainMs = IDLE_DRAIN_MS }) {
74
+ if (idleDrainMs !== null && !(idleDrainMs > 0)) {
75
+ throw new RangeError(`idleDrainMs must be greater than zero, or null, got ${String(idleDrainMs)}`);
76
+ }
66
77
  while (!signal?.aborted) {
67
78
  const seen = bell.generation;
68
79
  yield* this.drain(signal);
69
- await bell.after(seen, signal);
80
+ await bell.after(seen, { signal, timeoutMs: idleDrainMs ?? undefined });
70
81
  }
71
82
  }
72
83
  #fetchOptions(signal) {
@@ -1 +1 @@
1
- {"version":3,"file":"stream.js","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"stream.js","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alpamayo-solutions/colca-client",
3
- "version": "0.36.2",
3
+ "version": "0.36.4",
4
4
  "description": "Client for a Colca node's door: fetch, ack, publish, kv, self.",
5
5
  "keywords": [
6
6
  "colca",