@koolbase/core 12.9.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
+ }
@@ -0,0 +1,2 @@
1
+ export { createUseCollection, type UseCollectionResult } from './use-collection.js';
2
+ export { createUseRecord, type UseRecordResult } from './use-record.js';
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createUseRecord = exports.createUseCollection = void 0;
4
+ // React hooks over core's controllers: ONE implementation, exported to apps by
5
+ // @koolbase/react-native (from its root, as always) and @koolbase/js/react.
6
+ // React only -- nothing native, nothing DOM. Deliberately not exported from
7
+ // core's root entry, so importing @koolbase/core never loads React.
8
+ var use_collection_js_1 = require("./use-collection.js");
9
+ Object.defineProperty(exports, "createUseCollection", { enumerable: true, get: function () { return use_collection_js_1.createUseCollection; } });
10
+ var use_record_js_1 = require("./use-record.js");
11
+ Object.defineProperty(exports, "createUseRecord", { enumerable: true, get: function () { return use_record_js_1.createUseRecord; } });
@@ -0,0 +1,12 @@
1
+ import { type CollectionQuery, type CollectionState, type KoolbaseDatabase } from '../index.js';
2
+ export interface UseCollectionResult extends CollectionState {
3
+ /** Page one again, from the server. What is shown stays while it runs, and stays if it fails. */
4
+ refresh: () => Promise<void>;
5
+ /**
6
+ * The next page, appended, while hasMore. If rows are added or removed on
7
+ * the server between pages, one can be skipped until refresh(); none is
8
+ * ever shown twice.
9
+ */
10
+ loadMore: () => Promise<void>;
11
+ }
12
+ export declare function createUseCollection(getDb: () => Pick<KoolbaseDatabase, 'query'>): (collection: string, query?: CollectionQuery) => UseCollectionResult;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ // useCollection: a Koolbase collection as React state.
3
+ //
4
+ // A thin wrapper. Everything about getting the records right lives in
5
+ // @koolbase/core's KoolbaseCollectionController, which is tested there; this
6
+ // file only ties one controller's lifetime to a component's.
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.createUseCollection = createUseCollection;
9
+ const react_1 = require("react");
10
+ const index_js_1 = require("../index.js");
11
+ const noop = () => { };
12
+ const done = () => Promise.resolve();
13
+ function createUseCollection(getDb) {
14
+ return function useCollection(collection, query = {}) {
15
+ // The query as a value: an inline `where` object is a new object every
16
+ // render, and must not start a new fetch every render.
17
+ const key = (0, index_js_1.collectionQueryKey)(collection, query);
18
+ const [current, setCurrent] = (0, react_1.useState)(null);
19
+ (0, react_1.useEffect)(() => {
20
+ // Created here, not during render. Under React strict mode an effect
21
+ // runs, cleans up and runs again; each run gets its own controller, so
22
+ // the one left running is never one that was already disposed.
23
+ const controller = new index_js_1.KoolbaseCollectionController(getDb(), collection, query);
24
+ setCurrent({ key, controller });
25
+ void controller.load();
26
+ return () => controller.dispose();
27
+ // Depends on key alone: key is collection + query, as a value.
28
+ }, [key]);
29
+ // A controller from the previous key reads as a fresh load, never as
30
+ // the previous query's records.
31
+ const controller = current?.key === key ? current.controller : null;
32
+ const subscribe = (0, react_1.useCallback)((onChange) => (controller ? controller.subscribe(onChange) : noop), [controller]);
33
+ const getSnapshot = (0, react_1.useCallback)(() => (controller ? controller.getState() : index_js_1.initialCollectionState), [controller]);
34
+ const state = (0, react_1.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
35
+ const refresh = (0, react_1.useCallback)(() => (controller ? controller.refresh() : done()), [controller]);
36
+ const loadMore = (0, react_1.useCallback)(() => (controller ? controller.loadMore() : done()), [controller]);
37
+ return { ...state, refresh, loadMore };
38
+ };
39
+ }
@@ -0,0 +1,6 @@
1
+ import { type KoolbaseDatabase, type RecordState } from '../index.js';
2
+ export interface UseRecordResult extends RecordState {
3
+ /** The record again, from the server. What is shown stays while it runs, and stays if it fails. */
4
+ refresh: () => Promise<void>;
5
+ }
6
+ export declare function createUseRecord(getDb: () => Pick<KoolbaseDatabase, 'get'>): (collection: string, id: string | null | undefined) => UseRecordResult;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ // useRecord: one Koolbase record, by id, as React state.
3
+ //
4
+ // A thin wrapper, like useCollection: everything about getting the record
5
+ // right lives in @koolbase/core's KoolbaseRecordController, tested there;
6
+ // this file only ties one controller's lifetime to a component's.
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.createUseRecord = createUseRecord;
9
+ const react_1 = require("react");
10
+ const index_js_1 = require("../index.js");
11
+ const noop = () => { };
12
+ const done = () => Promise.resolve();
13
+ function createUseRecord(getDb) {
14
+ return function useRecord(collection, id) {
15
+ const key = JSON.stringify([collection, id ?? '']);
16
+ const [current, setCurrent] = (0, react_1.useState)(null);
17
+ (0, react_1.useEffect)(() => {
18
+ // Created here, not during render: under React strict mode an effect
19
+ // runs, cleans up and runs again, and each run gets its own controller.
20
+ const controller = new index_js_1.KoolbaseRecordController(getDb(), collection, id ?? '');
21
+ setCurrent({ key, controller });
22
+ void controller.load();
23
+ return () => controller.dispose();
24
+ // Depends on key alone: key is collection + id, as a value.
25
+ }, [key]);
26
+ // A controller from the previous key reads as a fresh load, never as the
27
+ // previous record.
28
+ const controller = current?.key === key ? current.controller : null;
29
+ const subscribe = (0, react_1.useCallback)((onChange) => (controller ? controller.subscribe(onChange) : noop), [controller]);
30
+ const getSnapshot = (0, react_1.useCallback)(() => (controller ? controller.getState() : index_js_1.initialRecordState), [controller]);
31
+ const state = (0, react_1.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
32
+ const refresh = (0, react_1.useCallback)(() => (controller ? controller.refresh() : done()), [controller]);
33
+ return { ...state, refresh };
34
+ };
35
+ }
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ export { createUseCollection, type UseCollectionResult } from './use-collection.js';
2
+ export { createUseRecord, type UseRecordResult } from './use-record.js';
@@ -0,0 +1,6 @@
1
+ // React hooks over core's controllers: ONE implementation, exported to apps by
2
+ // @koolbase/react-native (from its root, as always) and @koolbase/js/react.
3
+ // React only -- nothing native, nothing DOM. Deliberately not exported from
4
+ // core's root entry, so importing @koolbase/core never loads React.
5
+ export { createUseCollection } from './use-collection.js';
6
+ export { createUseRecord } from './use-record.js';
@@ -0,0 +1,12 @@
1
+ import { type CollectionQuery, type CollectionState, type KoolbaseDatabase } from '../index.js';
2
+ export interface UseCollectionResult extends CollectionState {
3
+ /** Page one again, from the server. What is shown stays while it runs, and stays if it fails. */
4
+ refresh: () => Promise<void>;
5
+ /**
6
+ * The next page, appended, while hasMore. If rows are added or removed on
7
+ * the server between pages, one can be skipped until refresh(); none is
8
+ * ever shown twice.
9
+ */
10
+ loadMore: () => Promise<void>;
11
+ }
12
+ export declare function createUseCollection(getDb: () => Pick<KoolbaseDatabase, 'query'>): (collection: string, query?: CollectionQuery) => UseCollectionResult;
@@ -0,0 +1,36 @@
1
+ // useCollection: a Koolbase collection as React state.
2
+ //
3
+ // A thin wrapper. Everything about getting the records right lives in
4
+ // @koolbase/core's KoolbaseCollectionController, which is tested there; this
5
+ // file only ties one controller's lifetime to a component's.
6
+ import { useCallback, useEffect, useState, useSyncExternalStore } from 'react';
7
+ import { KoolbaseCollectionController, collectionQueryKey, initialCollectionState, } from '../index.js';
8
+ const noop = () => { };
9
+ const done = () => Promise.resolve();
10
+ export function createUseCollection(getDb) {
11
+ return function useCollection(collection, query = {}) {
12
+ // The query as a value: an inline `where` object is a new object every
13
+ // render, and must not start a new fetch every render.
14
+ const key = collectionQueryKey(collection, query);
15
+ const [current, setCurrent] = useState(null);
16
+ useEffect(() => {
17
+ // Created here, not during render. Under React strict mode an effect
18
+ // runs, cleans up and runs again; each run gets its own controller, so
19
+ // the one left running is never one that was already disposed.
20
+ const controller = new KoolbaseCollectionController(getDb(), collection, query);
21
+ setCurrent({ key, controller });
22
+ void controller.load();
23
+ return () => controller.dispose();
24
+ // Depends on key alone: key is collection + query, as a value.
25
+ }, [key]);
26
+ // A controller from the previous key reads as a fresh load, never as
27
+ // the previous query's records.
28
+ const controller = current?.key === key ? current.controller : null;
29
+ const subscribe = useCallback((onChange) => (controller ? controller.subscribe(onChange) : noop), [controller]);
30
+ const getSnapshot = useCallback(() => (controller ? controller.getState() : initialCollectionState), [controller]);
31
+ const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
32
+ const refresh = useCallback(() => (controller ? controller.refresh() : done()), [controller]);
33
+ const loadMore = useCallback(() => (controller ? controller.loadMore() : done()), [controller]);
34
+ return { ...state, refresh, loadMore };
35
+ };
36
+ }
@@ -0,0 +1,6 @@
1
+ import { type KoolbaseDatabase, type RecordState } from '../index.js';
2
+ export interface UseRecordResult extends RecordState {
3
+ /** The record again, from the server. What is shown stays while it runs, and stays if it fails. */
4
+ refresh: () => Promise<void>;
5
+ }
6
+ export declare function createUseRecord(getDb: () => Pick<KoolbaseDatabase, 'get'>): (collection: string, id: string | null | undefined) => UseRecordResult;
@@ -0,0 +1,32 @@
1
+ // useRecord: one Koolbase record, by id, as React state.
2
+ //
3
+ // A thin wrapper, like useCollection: everything about getting the record
4
+ // right lives in @koolbase/core's KoolbaseRecordController, tested there;
5
+ // this file only ties one controller's lifetime to a component's.
6
+ import { useCallback, useEffect, useState, useSyncExternalStore } from 'react';
7
+ import { KoolbaseRecordController, initialRecordState, } from '../index.js';
8
+ const noop = () => { };
9
+ const done = () => Promise.resolve();
10
+ export function createUseRecord(getDb) {
11
+ return function useRecord(collection, id) {
12
+ const key = JSON.stringify([collection, id ?? '']);
13
+ const [current, setCurrent] = useState(null);
14
+ useEffect(() => {
15
+ // Created here, not during render: under React strict mode an effect
16
+ // runs, cleans up and runs again, and each run gets its own controller.
17
+ const controller = new KoolbaseRecordController(getDb(), collection, id ?? '');
18
+ setCurrent({ key, controller });
19
+ void controller.load();
20
+ return () => controller.dispose();
21
+ // Depends on key alone: key is collection + id, as a value.
22
+ }, [key]);
23
+ // A controller from the previous key reads as a fresh load, never as the
24
+ // previous record.
25
+ const controller = current?.key === key ? current.controller : null;
26
+ const subscribe = useCallback((onChange) => (controller ? controller.subscribe(onChange) : noop), [controller]);
27
+ const getSnapshot = useCallback(() => (controller ? controller.getState() : initialRecordState), [controller]);
28
+ const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
29
+ const refresh = useCallback(() => (controller ? controller.refresh() : done()), [controller]);
30
+ return { ...state, refresh };
31
+ };
32
+ }
@@ -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,11 +1,12 @@
1
1
  {
2
2
  "name": "@koolbase/core",
3
- "version": "12.9.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",
7
7
  "files": [
8
- "dist"
8
+ "dist",
9
+ "react"
9
10
  ],
10
11
  "scripts": {
11
12
  "build": "node ../../scripts/build-package.js core",
@@ -39,7 +40,25 @@
39
40
  "default": "./dist/cjs/index.js"
40
41
  }
41
42
  },
43
+ "./react": {
44
+ "import": {
45
+ "types": "./dist/esm/react/index.d.ts",
46
+ "default": "./dist/esm/react/index.js"
47
+ },
48
+ "require": {
49
+ "types": "./dist/cjs/react/index.d.ts",
50
+ "default": "./dist/cjs/react/index.js"
51
+ }
52
+ },
42
53
  "./package.json": "./package.json"
43
54
  },
44
- "sideEffects": false
55
+ "sideEffects": false,
56
+ "peerDependencies": {
57
+ "react": ">=18.0.0"
58
+ },
59
+ "peerDependenciesMeta": {
60
+ "react": {
61
+ "optional": true
62
+ }
63
+ }
45
64
  }
@@ -0,0 +1,7 @@
1
+ {
2
+ "//": "For resolvers that ignore \"exports\" (TypeScript node10, older bundlers): the ./react entry.",
3
+ "main": "../dist/cjs/react/index.js",
4
+ "module": "../dist/esm/react/index.js",
5
+ "types": "../dist/cjs/react/index.d.ts",
6
+ "sideEffects": false
7
+ }