@koolbase/core 12.10.0 → 12.11.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.
@@ -8,6 +8,13 @@ export interface CollectionQuery {
8
8
  orderDesc?: boolean;
9
9
  /** Page size; the server's default (20) when absent. */
10
10
  limit?: number;
11
+ /**
12
+ * Re-read page one, silently, when Koolbase realtime reports a record
13
+ * created, updated or deleted in this collection (debounced: a burst is one
14
+ * read). Realtime needs a signed-in user; until there is one, the list
15
+ * behaves as a normal list and goes live once the session exists.
16
+ */
17
+ live?: boolean;
11
18
  }
12
19
  export interface CollectionState {
13
20
  readonly status: CollectionStatus;
@@ -39,6 +46,8 @@ export declare class KoolbaseCollectionController {
39
46
  private laterPages;
40
47
  private nextOffset;
41
48
  private unregister;
49
+ private unlive;
50
+ private liveTimer;
42
51
  constructor(db: QueryReader, collection: string, query?: CollectionQuery);
43
52
  getState(): CollectionState;
44
53
  /** Called on every change. Returns the unsubscribe. */
@@ -53,6 +62,11 @@ export declare class KoolbaseCollectionController {
53
62
  * shown stays while it runs, and stays if it fails.
54
63
  */
55
64
  private refreshAfterWrite;
65
+ /**
66
+ * A change someone made, reported by realtime: page one again, silently, as
67
+ * after a write -- once per burst (250 ms), not once per event.
68
+ */
69
+ private scheduleLiveRefresh;
56
70
  /**
57
71
  * The next page, appended. Does nothing unless loaded with more to load.
58
72
  * If rows are added or removed on the server between pages, one can be
@@ -25,6 +25,7 @@
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.KoolbaseCollectionController = exports.initialCollectionState = void 0;
27
27
  exports.collectionQueryKey = collectionQueryKey;
28
+ const live_source_js_1 = require("./live-source.js");
28
29
  const query_refresh_js_1 = require("./query-refresh.js");
29
30
  exports.initialCollectionState = Object.freeze({
30
31
  status: 'loading',
@@ -49,6 +50,8 @@ function collectionQueryKey(collection, query = {}) {
49
50
  query.orderBy ?? null,
50
51
  query.orderDesc === true,
51
52
  query.limit ?? null,
53
+ // Only when live: every other query keeps the key it always had.
54
+ ...(query.live === true ? ['live'] : []),
52
55
  ]);
53
56
  }
54
57
  /** Page one, then the later pages without any record already shown. */
@@ -76,6 +79,9 @@ class KoolbaseCollectionController {
76
79
  this.nextOffset = 0;
77
80
  // A write to this collection re-runs this query (query-refresh.ts).
78
81
  this.unregister = null;
82
+ // A live list's realtime subscription, and its pending debounced re-read.
83
+ this.unlive = null;
84
+ this.liveTimer = null;
79
85
  this.db = db;
80
86
  this.collection = collection;
81
87
  this.spec = query;
@@ -97,6 +103,9 @@ class KoolbaseCollectionController {
97
103
  if (this.disposed)
98
104
  return;
99
105
  this.unregister ?? (this.unregister = (0, query_refresh_js_1.registerQueryRefresher)(this.collection, () => this.refreshAfterWrite()));
106
+ if (this.spec.live === true) {
107
+ this.unlive ?? (this.unlive = (0, live_source_js_1.liveSource)()?.subscribe(this.collection, () => this.scheduleLiveRefresh()) ?? null);
108
+ }
100
109
  await this.firstPage('default');
101
110
  }
102
111
  /** Page one again, from the server. What is shown stays while it runs, and stays if it fails. */
@@ -118,6 +127,20 @@ class KoolbaseCollectionController {
118
127
  return;
119
128
  await this.firstPage('network-only');
120
129
  }
130
+ /**
131
+ * A change someone made, reported by realtime: page one again, silently, as
132
+ * after a write -- once per burst (250 ms), not once per event.
133
+ */
134
+ scheduleLiveRefresh() {
135
+ if (this.disposed || this.liveTimer)
136
+ return;
137
+ this.liveTimer = setTimeout(() => {
138
+ this.liveTimer = null;
139
+ void this.refreshAfterWrite();
140
+ }, 250);
141
+ // A pending re-read must not be what keeps a Node process alive.
142
+ this.liveTimer.unref?.();
143
+ }
121
144
  /**
122
145
  * The next page, appended. Does nothing unless loaded with more to load.
123
146
  * If rows are added or removed on the server between pages, one can be
@@ -153,6 +176,12 @@ class KoolbaseCollectionController {
153
176
  this.disposed = true;
154
177
  this.unregister?.();
155
178
  this.unregister = null;
179
+ this.unlive?.();
180
+ this.unlive = null;
181
+ if (this.liveTimer) {
182
+ clearTimeout(this.liveTimer);
183
+ this.liveTimer = null;
184
+ }
156
185
  this.listeners.clear();
157
186
  }
158
187
  async firstPage(cache) {
@@ -0,0 +1,6 @@
1
+ export interface LiveSource {
2
+ /** Calls back on every record event in the collection; returns the unsubscribe. */
3
+ subscribe(collection: string, callback: (event: unknown) => void): () => void;
4
+ }
5
+ export declare function setLiveSource(source: LiveSource | null): void;
6
+ export declare function liveSource(): LiveSource | null;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ // The realtime client a live list subscribes through. KoolbaseRealtime
3
+ // registers itself when it is created, as query-refresh.ts registers the
4
+ // refreshers writes run: a list reaches realtime without the host packages
5
+ // passing it in. None registered (realtime never created): a live list is
6
+ // simply a list.
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.setLiveSource = setLiveSource;
9
+ exports.liveSource = liveSource;
10
+ let active = null;
11
+ function setLiveSource(source) {
12
+ active = source;
13
+ }
14
+ function liveSource() {
15
+ return active;
16
+ }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.KoolbaseRealtime = void 0;
4
4
  const cache_store_js_1 = require("./cache-store.js");
5
5
  const record_js_1 = require("./record.js");
6
+ const live_source_js_1 = require("./live-source.js");
6
7
  const EVENT_TYPE_MAP = {
7
8
  'db.record.created': 'created',
8
9
  'db.record.updated': 'updated',
@@ -43,6 +44,8 @@ class KoolbaseRealtime {
43
44
  this.config = config;
44
45
  this.getToken = getToken;
45
46
  this.getUserId = getUserId;
47
+ // Live lists (CollectionQuery.live) subscribe through this client.
48
+ (0, live_source_js_1.setLiveSource)(this);
46
49
  }
47
50
  /** Files a record seen over the socket, if we know whose it is. */
48
51
  async cacheSeenRecord(collection, record) {
@@ -8,6 +8,13 @@ export interface CollectionQuery {
8
8
  orderDesc?: boolean;
9
9
  /** Page size; the server's default (20) when absent. */
10
10
  limit?: number;
11
+ /**
12
+ * Re-read page one, silently, when Koolbase realtime reports a record
13
+ * created, updated or deleted in this collection (debounced: a burst is one
14
+ * read). Realtime needs a signed-in user; until there is one, the list
15
+ * behaves as a normal list and goes live once the session exists.
16
+ */
17
+ live?: boolean;
11
18
  }
12
19
  export interface CollectionState {
13
20
  readonly status: CollectionStatus;
@@ -39,6 +46,8 @@ export declare class KoolbaseCollectionController {
39
46
  private laterPages;
40
47
  private nextOffset;
41
48
  private unregister;
49
+ private unlive;
50
+ private liveTimer;
42
51
  constructor(db: QueryReader, collection: string, query?: CollectionQuery);
43
52
  getState(): CollectionState;
44
53
  /** Called on every change. Returns the unsubscribe. */
@@ -53,6 +62,11 @@ export declare class KoolbaseCollectionController {
53
62
  * shown stays while it runs, and stays if it fails.
54
63
  */
55
64
  private refreshAfterWrite;
65
+ /**
66
+ * A change someone made, reported by realtime: page one again, silently, as
67
+ * after a write -- once per burst (250 ms), not once per event.
68
+ */
69
+ private scheduleLiveRefresh;
56
70
  /**
57
71
  * The next page, appended. Does nothing unless loaded with more to load.
58
72
  * If rows are added or removed on the server between pages, one can be
@@ -21,6 +21,7 @@
21
21
  // Known limit: if rows are added or removed on the server between pages,
22
22
  // the pages shift and a row can be skipped until refresh(), which reloads
23
23
  // from page one. Duplicates are removed; a skipped row cannot be detected.
24
+ import { liveSource } from './live-source.js';
24
25
  import { registerQueryRefresher } from './query-refresh.js';
25
26
  export const initialCollectionState = Object.freeze({
26
27
  status: 'loading',
@@ -45,6 +46,8 @@ export function collectionQueryKey(collection, query = {}) {
45
46
  query.orderBy ?? null,
46
47
  query.orderDesc === true,
47
48
  query.limit ?? null,
49
+ // Only when live: every other query keeps the key it always had.
50
+ ...(query.live === true ? ['live'] : []),
48
51
  ]);
49
52
  }
50
53
  /** Page one, then the later pages without any record already shown. */
@@ -72,6 +75,9 @@ export class KoolbaseCollectionController {
72
75
  this.nextOffset = 0;
73
76
  // A write to this collection re-runs this query (query-refresh.ts).
74
77
  this.unregister = null;
78
+ // A live list's realtime subscription, and its pending debounced re-read.
79
+ this.unlive = null;
80
+ this.liveTimer = null;
75
81
  this.db = db;
76
82
  this.collection = collection;
77
83
  this.spec = query;
@@ -93,6 +99,9 @@ export class KoolbaseCollectionController {
93
99
  if (this.disposed)
94
100
  return;
95
101
  this.unregister ?? (this.unregister = registerQueryRefresher(this.collection, () => this.refreshAfterWrite()));
102
+ if (this.spec.live === true) {
103
+ this.unlive ?? (this.unlive = liveSource()?.subscribe(this.collection, () => this.scheduleLiveRefresh()) ?? null);
104
+ }
96
105
  await this.firstPage('default');
97
106
  }
98
107
  /** Page one again, from the server. What is shown stays while it runs, and stays if it fails. */
@@ -114,6 +123,20 @@ export class KoolbaseCollectionController {
114
123
  return;
115
124
  await this.firstPage('network-only');
116
125
  }
126
+ /**
127
+ * A change someone made, reported by realtime: page one again, silently, as
128
+ * after a write -- once per burst (250 ms), not once per event.
129
+ */
130
+ scheduleLiveRefresh() {
131
+ if (this.disposed || this.liveTimer)
132
+ return;
133
+ this.liveTimer = setTimeout(() => {
134
+ this.liveTimer = null;
135
+ void this.refreshAfterWrite();
136
+ }, 250);
137
+ // A pending re-read must not be what keeps a Node process alive.
138
+ this.liveTimer.unref?.();
139
+ }
117
140
  /**
118
141
  * The next page, appended. Does nothing unless loaded with more to load.
119
142
  * If rows are added or removed on the server between pages, one can be
@@ -149,6 +172,12 @@ export class KoolbaseCollectionController {
149
172
  this.disposed = true;
150
173
  this.unregister?.();
151
174
  this.unregister = null;
175
+ this.unlive?.();
176
+ this.unlive = null;
177
+ if (this.liveTimer) {
178
+ clearTimeout(this.liveTimer);
179
+ this.liveTimer = null;
180
+ }
152
181
  this.listeners.clear();
153
182
  }
154
183
  async firstPage(cache) {
@@ -0,0 +1,6 @@
1
+ export interface LiveSource {
2
+ /** Calls back on every record event in the collection; returns the unsubscribe. */
3
+ subscribe(collection: string, callback: (event: unknown) => void): () => void;
4
+ }
5
+ export declare function setLiveSource(source: LiveSource | null): void;
6
+ export declare function liveSource(): LiveSource | null;
@@ -0,0 +1,12 @@
1
+ // The realtime client a live list subscribes through. KoolbaseRealtime
2
+ // registers itself when it is created, as query-refresh.ts registers the
3
+ // refreshers writes run: a list reaches realtime without the host packages
4
+ // passing it in. None registered (realtime never created): a live list is
5
+ // simply a list.
6
+ let active = null;
7
+ export function setLiveSource(source) {
8
+ active = source;
9
+ }
10
+ export function liveSource() {
11
+ return active;
12
+ }
@@ -1,5 +1,6 @@
1
1
  import { cacheRecord, removeCachedRecord } from './cache-store.js';
2
2
  import { recordFromWire } from './record.js';
3
+ import { setLiveSource } from './live-source.js';
3
4
  const EVENT_TYPE_MAP = {
4
5
  'db.record.created': 'created',
5
6
  'db.record.updated': 'updated',
@@ -40,6 +41,8 @@ export class KoolbaseRealtime {
40
41
  this.config = config;
41
42
  this.getToken = getToken;
42
43
  this.getUserId = getUserId;
44
+ // Live lists (CollectionQuery.live) subscribe through this client.
45
+ setLiveSource(this);
43
46
  }
44
47
  /** Files a record seen over the socket, if we know whose it is. */
45
48
  async cacheSeenRecord(collection, record) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koolbase/core",
3
- "version": "12.10.0",
3
+ "version": "12.11.0",
4
4
  "description": "Koolbase SDK core \u2014 shared behaviour behind @koolbase/react-native and @koolbase/js. Install one of those, not this.",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "types": "./dist/esm/index.d.ts",