@ultimabase/realtime 0.1.2 → 0.3.0-canary.20261031104207

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/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @ultimabase/realtime
2
2
 
3
+ ## 0.3.0-canary.20261031104207
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [41cfff6]
8
+ - Updated dependencies [6e8ebf6]
9
+ - Updated dependencies [9cd2dd4]
10
+ - Updated dependencies [353c2bf]
11
+ - ultimabase@0.3.0-canary.20261031104207
12
+
13
+ ## 0.2.0
14
+
15
+ ### Minor Changes
16
+
17
+ - c0c5a21: `viewer.onClose(listener)` runs once a viewer closes, whatever closes it: its caller ended, the application stopped or `close()` was called. A plugin's own socket closes there.
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies [2c3bf4e]
22
+ - Updated dependencies [fd1263e]
23
+ - ultimabase@0.2.0
24
+
3
25
  ## 0.1.2
4
26
 
5
27
  ### Patch Changes
@@ -1,8 +1,12 @@
1
- import type { Caller } from 'ultimabase';
1
+ import type { Caller, ExposedFunction } from 'ultimabase';
2
2
  import type { Recorded } from 'ultimabase/internal';
3
3
  import type { Batch } from './changes.ts';
4
4
  import type { LiveMessage } from './messages.ts';
5
5
  import type { Loader } from './reads.ts';
6
+ /** A function's name, with its plugin's for a plugin's (D-113). */
7
+ export declare const ownerOf: (fn: ExposedFunction) => string;
8
+ /** How long a live function's wake waits for quiet, in milliseconds. */
9
+ export declare const debounce = 50;
6
10
  export type ComputedOptions = {
7
11
  /** The function's result for `caller`, shaped as its transport sends it. */
8
12
  readonly compute: (caller: Caller) => Promise<unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"computed.d.ts","sourceRoot":"","sources":["../src/computed.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAEnD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAMxC,MAAM,MAAM,eAAe,GAAG;IAC7B,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACtD,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;IAC7C,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAA;IAC1B,sDAAsD;IACtD,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAA;CAC3B,CAAA;AAED,qBAAa,QAAQ;;IAGpB,8BAA8B;IAC9B,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAK;IAC/B,iEAAiE;IACjE,OAAO,EAAE,IAAI,GAAG,SAAS,CAAA;IASzB,YAAY,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAGnD;IAED,2EAA2E;IACrE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAG3B;IAED,gEAAgE;IAC1D,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAGxE;IAED,+DAA+D;IACzD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG1C;IAED,mEAAmE;IACnE,MAAM,IAAI,IAAI,CAKb;IAED,KAAK,IAAI,IAAI,CAIZ;CA8DD"}
1
+ {"version":3,"file":"computed.d.ts","sourceRoot":"","sources":["../src/computed.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAEnD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAGxC,mEAAmE;AACnE,eAAO,MAAM,OAAO,OAAQ,eAAe,KAAG,MACgB,CAAA;AAE9D,wEAAwE;AACxE,eAAO,MAAM,QAAQ,KAAK,CAAA;AAK1B,MAAM,MAAM,eAAe,GAAG;IAC7B,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACtD,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;IAC7C,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAA;IAC1B,sDAAsD;IACtD,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAA;CAC3B,CAAA;AAED,qBAAa,QAAQ;;IAGpB,8BAA8B;IAC9B,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAK;IAC/B,iEAAiE;IACjE,OAAO,EAAE,IAAI,GAAG,SAAS,CAAA;IASzB,YAAY,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAGnD;IAED,2EAA2E;IACrE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAG3B;IAED,gEAAgE;IAC1D,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAGxE;IAED,+DAA+D;IACzD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG1C;IAED,mEAAmE;IACnE,MAAM,IAAI,IAAI,CAKb;IAED,KAAK,IAAI,IAAI,CAIZ;CA8DD"}
package/dist/computed.js CHANGED
@@ -5,6 +5,10 @@
5
5
  import { isDeepStrictEqual } from 'node:util';
6
6
  import { recording } from 'ultimabase/internal';
7
7
  import { anyStale } from './stale.js';
8
+ /** A function's name, with its plugin's for a plugin's (D-113). */
9
+ export const ownerOf = (fn) => fn.plugin === undefined ? fn.name : `${fn.plugin}.${fn.name}`;
10
+ /** How long a live function's wake waits for quiet, in milliseconds. */
11
+ export const debounce = 50;
8
12
  /** The longest a run waits after the first wake, however many follow. */
9
13
  const longest = 1000;
10
14
  export class Computed {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAI9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGzC,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AACjE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAErD,MAAM,MAAM,eAAe,GAAG;IAC7B,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,qDAAqD;IACrD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAClC,iFAAiF;IACjF,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IACnC,yEAAyE;IACzE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CACzB,CAAA;AAED,kDAAkD;AAClD,MAAM,WAAW,WAAW;IAC3B,gEAAgE;IAChE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACtD,kDAAkD;IAClD,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;IACxC,wFAAwF;IACxF,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;CAC/C;AAED,0CAA0C;AAC1C,MAAM,WAAW,QAAS,SAAQ,WAAW;IAC5C,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAA;IACpC,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C,2EAA2E;IAC3E,QAAQ,CAAC,YAAY,EAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACrD,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAA;IACxB,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAA;CACzB;AAsDD,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAA0B,CAAA;AAC/D,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,QAAQ,CAA6B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAK9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AACjE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAErD,MAAM,MAAM,eAAe,GAAG;IAC7B,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,qDAAqD;IACrD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAClC,iFAAiF;IACjF,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IACnC,yEAAyE;IACzE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CACzB,CAAA;AAED,kDAAkD;AAClD,MAAM,WAAW,WAAW;IAC3B,gEAAgE;IAChE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACtD,kDAAkD;IAClD,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;IACxC,wFAAwF;IACxF,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;CAC/C;AAED,0CAA0C;AAC1C,MAAM,WAAW,QAAS,SAAQ,WAAW;IAC5C,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAA;IACpC,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C,2EAA2E;IAC3E,QAAQ,CAAC,YAAY,EAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACrD,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAA;IACxB,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAA;CACzB;AAsDD,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAA0B,CAAA;AAC/D,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,QAAQ,CAA6B,CAAA"}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { definePlugin } from 'ultimabase';
2
2
  import manifest from '../package.json' with { type: 'json' };
3
+ import { ownerOf } from './computed.js';
3
4
  import { Engine } from './engine.js';
4
- import { ownerOf } from './viewer.js';
5
5
  // Each running application's engine, by the context its plugin received.
6
6
  const engines = new WeakMap();
7
7
  const positive = (name, value) => {
package/dist/viewer.d.ts CHANGED
@@ -25,8 +25,6 @@ export interface LiveHandle {
25
25
  close(this: void): void;
26
26
  }
27
27
  type Built<R> = Awaited<ReturnType<typeof recording<R>>>;
28
- /** A function's name, with its plugin's for a plugin's (D-113). */
29
- export declare const ownerOf: (fn: ExposedFunction) => string;
30
28
  export declare class Viewer {
31
29
  #private;
32
30
  /** `onDue` runs once the caller stops holding, to rebuild it. */
@@ -47,6 +45,8 @@ export declare class Viewer {
47
45
  */
48
46
  value(name: string, compute: (caller: Caller) => Promise<unknown>, send: (message: LiveMessage) => void): Promise<LiveHandle>;
49
47
  close(): void;
48
+ /** Runs `listener` once it closes, at once when it is closed (D-163). */
49
+ onClose(listener: () => void): void;
50
50
  /** Follows `batch`: rebuilds the caller first when a write may change it. */
51
51
  flush(batch: Batch, loader: Loader, missed: boolean): Promise<void>;
52
52
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"viewer.d.ts","sourceRoot":"","sources":["../src/viewer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"viewer.d.ts","sourceRoot":"","sources":["../src/viewer.ts"],"names":[],"mappings":"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"}
package/dist/viewer.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { livePlan, recording } from 'ultimabase/internal';
2
2
  import { checked, invalid } from './checked.js';
3
- import { Computed } from './computed.js';
3
+ import { Computed, debounce, ownerOf } from './computed.js';
4
4
  import { Live } from './live.js';
5
5
  import { liveArgs, shapeOf } from './shape.js';
6
6
  import { anyStale } from './stale.js';
@@ -8,10 +8,6 @@ import { anyStale } from './stale.js';
8
8
  const soonest = 1000;
9
9
  /** The longest wait `setTimeout` keeps. */
10
10
  const latest = 2 ** 31 - 1;
11
- /** A function's name, with its plugin's for a plugin's (D-113). */
12
- export const ownerOf = (fn) => fn.plugin === undefined ? fn.name : `${fn.plugin}.${fn.name}`;
13
- /** How long a live function's wake waits for quiet, in milliseconds. */
14
- const debounce = 50;
15
11
  /** Who `caller` is, which a rebuilt caller must keep. */
16
12
  const whoOf = (caller) => {
17
13
  if (caller.kind !== 'user')
@@ -22,7 +18,8 @@ const whoOf = (caller) => {
22
18
  export class Viewer {
23
19
  #settings;
24
20
  #request;
25
- #onClose;
21
+ /** What runs once it closes, the engine's first (D-163). */
22
+ #listeners = [];
26
23
  #onDue;
27
24
  #live = new Map();
28
25
  #computed = new Map();
@@ -38,7 +35,7 @@ export class Viewer {
38
35
  this.#caller = identified.result;
39
36
  this.#reads = identified.reads;
40
37
  this.#expires = identified.expires;
41
- this.#onClose = hooks.onClose;
38
+ this.#listeners.push(hooks.onClose);
42
39
  this.#onDue = hooks.onDue;
43
40
  this.#reschedule();
44
41
  }
@@ -105,7 +102,24 @@ export class Viewer {
105
102
  for (const computed of this.#computed.keys())
106
103
  computed.close();
107
104
  this.#computed.clear();
108
- this.#onClose();
105
+ this.#told();
106
+ }
107
+ /** Runs `listener` once it closes, at once when it is closed (D-163). */
108
+ onClose(listener) {
109
+ this.#listeners.push(listener);
110
+ if (this.#closed)
111
+ this.#told();
112
+ }
113
+ /** Runs each listener once; one that throws is logged, not the others. */
114
+ #told() {
115
+ for (const listener of this.#listeners.splice(0)) {
116
+ try {
117
+ listener();
118
+ }
119
+ catch (error) {
120
+ this.#settings.logger.error({ err: error }, 'A close listener failed.');
121
+ }
122
+ }
109
123
  }
110
124
  /** Follows `batch`: rebuilds the caller first when a write may change it. */
111
125
  async flush(batch, loader, missed) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimabase/realtime",
3
- "version": "0.1.2",
3
+ "version": "0.3.0-canary.20261031104207",
4
4
  "description": "Realtime for UltimaBase: live lists and live rows that follow every write, under the read rules.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -31,14 +31,14 @@
31
31
  }
32
32
  },
33
33
  "peerDependencies": {
34
- "ultimabase": "^0.1.2"
34
+ "ultimabase": "0.3.0-canary.20261031104207"
35
35
  },
36
36
  "devDependencies": {
37
- "@ultimabase/acl": "0.1.2",
38
- "@ultimabase/roles": "0.1.2",
39
- "@ultimabase/soft-delete": "0.1.2",
40
- "@ultimabase/testing": "0.1.2",
41
- "ultimabase": "0.1.2",
37
+ "@ultimabase/acl": "0.3.0-canary.20261031104207",
38
+ "@ultimabase/roles": "0.3.0-canary.20261031104207",
39
+ "@ultimabase/soft-delete": "0.3.0-canary.20261031104207",
40
+ "@ultimabase/testing": "0.3.0-canary.20261031104207",
41
+ "ultimabase": "0.3.0-canary.20261031104207",
42
42
  "vite": "8.3.4",
43
43
  "vitest": "5.0.5"
44
44
  }
package/src/computed.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  // result is sent whole; one equal to the last is not. A wake during a run
4
4
  // runs it once more after it, and an error closes it.
5
5
  import { isDeepStrictEqual } from 'node:util'
6
- import type { Caller } from 'ultimabase'
6
+ import type { Caller, ExposedFunction } from 'ultimabase'
7
7
  import type { Recorded } from 'ultimabase/internal'
8
8
  import { recording } from 'ultimabase/internal'
9
9
  import type { Batch } from './changes.ts'
@@ -11,6 +11,13 @@ import type { LiveMessage } from './messages.ts'
11
11
  import type { Loader } from './reads.ts'
12
12
  import { anyStale } from './stale.ts'
13
13
 
14
+ /** A function's name, with its plugin's for a plugin's (D-113). */
15
+ export const ownerOf = (fn: ExposedFunction): string =>
16
+ fn.plugin === undefined ? fn.name : `${fn.plugin}.${fn.name}`
17
+
18
+ /** How long a live function's wake waits for quiet, in milliseconds. */
19
+ export const debounce = 50
20
+
14
21
  /** The longest a run waits after the first wake, however many follow. */
15
22
  const longest = 1000
16
23
 
package/src/index.ts CHANGED
@@ -5,9 +5,9 @@
5
5
  import type { PluginOf, PluginTypes, UseOf } from 'ultimabase'
6
6
  import { definePlugin } from 'ultimabase'
7
7
  import manifest from '../package.json' with { type: 'json' }
8
+ import { ownerOf } from './computed.ts'
8
9
  import { Engine } from './engine.ts'
9
10
  import type { Viewer } from './viewer.ts'
10
- import { ownerOf } from './viewer.ts'
11
11
 
12
12
  export type { LiveMessage, Removal, Upsert } from './messages.ts'
13
13
  export type { LiveHandle, Viewer } from './viewer.ts'
package/src/viewer.ts CHANGED
@@ -9,7 +9,7 @@ import type { Recorded } from 'ultimabase/internal'
9
9
  import { livePlan, recording } from 'ultimabase/internal'
10
10
  import type { Batch } from './changes.ts'
11
11
  import { checked, invalid } from './checked.ts'
12
- import { Computed } from './computed.ts'
12
+ import { Computed, debounce, ownerOf } from './computed.ts'
13
13
  import type { LiveMessage } from './messages.ts'
14
14
  import { Live } from './live.ts'
15
15
  import type { Loader } from './reads.ts'
@@ -53,13 +53,6 @@ const soonest = 1000
53
53
  /** The longest wait `setTimeout` keeps. */
54
54
  const latest = 2 ** 31 - 1
55
55
 
56
- /** A function's name, with its plugin's for a plugin's (D-113). */
57
- export const ownerOf = (fn: ExposedFunction): string =>
58
- fn.plugin === undefined ? fn.name : `${fn.plugin}.${fn.name}`
59
-
60
- /** How long a live function's wake waits for quiet, in milliseconds. */
61
- const debounce = 50
62
-
63
56
  /** Who `caller` is, which a rebuilt caller must keep. */
64
57
  const whoOf = (caller: Caller): string => {
65
58
  if (caller.kind !== 'user') return JSON.stringify(caller)
@@ -70,7 +63,8 @@ const whoOf = (caller: Caller): string => {
70
63
  export class Viewer {
71
64
  readonly #settings: Settings
72
65
  #request: Request
73
- readonly #onClose: () => void
66
+ /** What runs once it closes, the engine's first (D-163). */
67
+ readonly #listeners: (() => void)[] = []
74
68
  readonly #onDue: () => void
75
69
  readonly #live = new Map<Live, Subscription>()
76
70
  readonly #computed = new Map<Computed, (message: LiveMessage) => void>()
@@ -92,7 +86,7 @@ export class Viewer {
92
86
  this.#caller = identified.result
93
87
  this.#reads = identified.reads
94
88
  this.#expires = identified.expires
95
- this.#onClose = hooks.onClose
89
+ this.#listeners.push(hooks.onClose)
96
90
  this.#onDue = hooks.onDue
97
91
  this.#reschedule()
98
92
  }
@@ -174,7 +168,24 @@ export class Viewer {
174
168
  this.#live.clear()
175
169
  for (const computed of this.#computed.keys()) computed.close()
176
170
  this.#computed.clear()
177
- this.#onClose()
171
+ this.#told()
172
+ }
173
+
174
+ /** Runs `listener` once it closes, at once when it is closed (D-163). */
175
+ onClose(listener: () => void): void {
176
+ this.#listeners.push(listener)
177
+ if (this.#closed) this.#told()
178
+ }
179
+
180
+ /** Runs each listener once; one that throws is logged, not the others. */
181
+ #told(): void {
182
+ for (const listener of this.#listeners.splice(0)) {
183
+ try {
184
+ listener()
185
+ } catch (error) {
186
+ this.#settings.logger.error({ err: error }, 'A close listener failed.')
187
+ }
188
+ }
178
189
  }
179
190
 
180
191
  /** Follows `batch`: rebuilds the caller first when a write may change it. */