@koolbase/core 12.11.1 → 12.12.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.
- package/dist/cjs/react/use-record.d.ts +3 -1
- package/dist/cjs/react/use-record.js +5 -3
- package/dist/cjs/record-controller.d.ts +14 -1
- package/dist/cjs/record-controller.js +49 -1
- package/dist/esm/react/use-record.d.ts +3 -1
- package/dist/esm/react/use-record.js +5 -3
- package/dist/esm/record-controller.d.ts +14 -1
- package/dist/esm/record-controller.js +49 -1
- package/package.json +1 -1
|
@@ -3,4 +3,6 @@ export interface UseRecordResult extends RecordState {
|
|
|
3
3
|
/** The record again, from the server. What is shown stays while it runs, and stays if it fails. */
|
|
4
4
|
refresh: () => Promise<void>;
|
|
5
5
|
}
|
|
6
|
-
export declare function createUseRecord(getDb: () => Pick<KoolbaseDatabase, 'get'>): (collection: string, id: string | null | undefined
|
|
6
|
+
export declare function createUseRecord(getDb: () => Pick<KoolbaseDatabase, 'get'>): (collection: string, id: string | null | undefined, options?: {
|
|
7
|
+
live?: boolean;
|
|
8
|
+
}) => UseRecordResult;
|
|
@@ -11,13 +11,15 @@ const index_js_1 = require("../index.js");
|
|
|
11
11
|
const noop = () => { };
|
|
12
12
|
const done = () => Promise.resolve();
|
|
13
13
|
function createUseRecord(getDb) {
|
|
14
|
-
return function useRecord(collection, id) {
|
|
15
|
-
|
|
14
|
+
return function useRecord(collection, id, options = {}) {
|
|
15
|
+
// live is part of the key: switching it starts a fresh controller.
|
|
16
|
+
const live = options.live === true;
|
|
17
|
+
const key = JSON.stringify([collection, id ?? '', ...(live ? ['live'] : [])]);
|
|
16
18
|
const [current, setCurrent] = (0, react_1.useState)(null);
|
|
17
19
|
(0, react_1.useEffect)(() => {
|
|
18
20
|
// Created here, not during render: under React strict mode an effect
|
|
19
21
|
// runs, cleans up and runs again, and each run gets its own controller.
|
|
20
|
-
const controller = new index_js_1.KoolbaseRecordController(getDb(), collection, id ?? '');
|
|
22
|
+
const controller = new index_js_1.KoolbaseRecordController(getDb(), collection, id ?? '', { live });
|
|
21
23
|
setCurrent({ key, controller });
|
|
22
24
|
void controller.load();
|
|
23
25
|
return () => controller.dispose();
|
|
@@ -19,7 +19,13 @@ export declare class KoolbaseRecordController {
|
|
|
19
19
|
private readonly listeners;
|
|
20
20
|
private generation;
|
|
21
21
|
private disposed;
|
|
22
|
-
|
|
22
|
+
/** Follow this record through Koolbase realtime (see onLive). */
|
|
23
|
+
private readonly live;
|
|
24
|
+
private unlive;
|
|
25
|
+
private liveTimer;
|
|
26
|
+
constructor(db: RecordReader, collection: string, id: string, options?: {
|
|
27
|
+
live?: boolean;
|
|
28
|
+
});
|
|
23
29
|
getState(): RecordState;
|
|
24
30
|
/** Called on every change. Returns the unsubscribe. */
|
|
25
31
|
subscribe(listener: () => void): () => void;
|
|
@@ -29,6 +35,13 @@ export declare class KoolbaseRecordController {
|
|
|
29
35
|
refresh(): Promise<void>;
|
|
30
36
|
/** Stops all updates. A result still in flight is dropped when it lands. */
|
|
31
37
|
dispose(): void;
|
|
38
|
+
/**
|
|
39
|
+
* A realtime event in this record's collection. Only one naming THIS record
|
|
40
|
+
* counts: an update (or a create, for an id that reappears) re-reads it,
|
|
41
|
+
* silently -- no `refreshing` -- and once per burst (250 ms); a delete is
|
|
42
|
+
* notFound at once, and a read still in flight is dropped.
|
|
43
|
+
*/
|
|
44
|
+
private onLive;
|
|
32
45
|
private fetch;
|
|
33
46
|
private isStale;
|
|
34
47
|
private set;
|
|
@@ -13,11 +13,14 @@
|
|
|
13
13
|
// - refresh() keeps the record shown while it runs, and if it fails.
|
|
14
14
|
// - every fetch has a generation: a result superseded by a later refresh,
|
|
15
15
|
// or landing after dispose(), is dropped.
|
|
16
|
+
// - live (the option): realtime events for THIS record only. An update
|
|
17
|
+
// re-reads it silently, once per burst; a delete is notFound at once.
|
|
16
18
|
// Not yet: an offline fallback from the record cache, which keeps data and
|
|
17
19
|
// revision but not createdAt or createdBy.
|
|
18
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
21
|
exports.KoolbaseRecordController = exports.initialRecordState = void 0;
|
|
20
22
|
const database_errors_js_1 = require("./database-errors.js");
|
|
23
|
+
const live_source_js_1 = require("./live-source.js");
|
|
21
24
|
exports.initialRecordState = Object.freeze({
|
|
22
25
|
status: 'loading',
|
|
23
26
|
record: null,
|
|
@@ -25,14 +28,18 @@ exports.initialRecordState = Object.freeze({
|
|
|
25
28
|
refreshing: false,
|
|
26
29
|
});
|
|
27
30
|
class KoolbaseRecordController {
|
|
28
|
-
constructor(db, collection, id) {
|
|
31
|
+
constructor(db, collection, id, options = {}) {
|
|
29
32
|
this.state = exports.initialRecordState;
|
|
30
33
|
this.listeners = new Set();
|
|
31
34
|
this.generation = 0;
|
|
32
35
|
this.disposed = false;
|
|
36
|
+
// The realtime subscription, and its pending debounced re-read.
|
|
37
|
+
this.unlive = null;
|
|
38
|
+
this.liveTimer = null;
|
|
33
39
|
this.db = db;
|
|
34
40
|
this.collection = collection;
|
|
35
41
|
this.id = id;
|
|
42
|
+
this.live = options.live === true;
|
|
36
43
|
}
|
|
37
44
|
getState() {
|
|
38
45
|
return this.state;
|
|
@@ -50,6 +57,10 @@ class KoolbaseRecordController {
|
|
|
50
57
|
async load() {
|
|
51
58
|
if (this.disposed)
|
|
52
59
|
return;
|
|
60
|
+
// Live needs an id: with none there is nothing to follow.
|
|
61
|
+
if (this.live && this.id) {
|
|
62
|
+
this.unlive ?? (this.unlive = (0, live_source_js_1.liveSource)()?.subscribe(this.collection, (event) => this.onLive(event)) ?? null);
|
|
63
|
+
}
|
|
53
64
|
await this.fetch();
|
|
54
65
|
}
|
|
55
66
|
/** The record again, from the server. What is shown stays while it runs, and stays if it fails. */
|
|
@@ -64,8 +75,45 @@ class KoolbaseRecordController {
|
|
|
64
75
|
/** Stops all updates. A result still in flight is dropped when it lands. */
|
|
65
76
|
dispose() {
|
|
66
77
|
this.disposed = true;
|
|
78
|
+
this.unlive?.();
|
|
79
|
+
this.unlive = null;
|
|
80
|
+
if (this.liveTimer) {
|
|
81
|
+
clearTimeout(this.liveTimer);
|
|
82
|
+
this.liveTimer = null;
|
|
83
|
+
}
|
|
67
84
|
this.listeners.clear();
|
|
68
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* A realtime event in this record's collection. Only one naming THIS record
|
|
88
|
+
* counts: an update (or a create, for an id that reappears) re-reads it,
|
|
89
|
+
* silently -- no `refreshing` -- and once per burst (250 ms); a delete is
|
|
90
|
+
* notFound at once, and a read still in flight is dropped.
|
|
91
|
+
*/
|
|
92
|
+
onLive(event) {
|
|
93
|
+
if (this.disposed)
|
|
94
|
+
return;
|
|
95
|
+
const e = (event ?? {});
|
|
96
|
+
const target = e.type === 'deleted' ? e.recordId : e.record?.id;
|
|
97
|
+
if (target !== this.id)
|
|
98
|
+
return;
|
|
99
|
+
if (e.type === 'deleted') {
|
|
100
|
+
if (this.liveTimer) {
|
|
101
|
+
clearTimeout(this.liveTimer);
|
|
102
|
+
this.liveTimer = null;
|
|
103
|
+
}
|
|
104
|
+
this.generation += 1;
|
|
105
|
+
this.set({ status: 'notFound', record: null, error: null, refreshing: false });
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (this.liveTimer)
|
|
109
|
+
return;
|
|
110
|
+
this.liveTimer = setTimeout(() => {
|
|
111
|
+
this.liveTimer = null;
|
|
112
|
+
void this.fetch();
|
|
113
|
+
}, 250);
|
|
114
|
+
// A pending re-read must not be what keeps a Node process alive.
|
|
115
|
+
this.liveTimer.unref?.();
|
|
116
|
+
}
|
|
69
117
|
async fetch() {
|
|
70
118
|
const gen = ++this.generation;
|
|
71
119
|
if (!this.id) {
|
|
@@ -3,4 +3,6 @@ export interface UseRecordResult extends RecordState {
|
|
|
3
3
|
/** The record again, from the server. What is shown stays while it runs, and stays if it fails. */
|
|
4
4
|
refresh: () => Promise<void>;
|
|
5
5
|
}
|
|
6
|
-
export declare function createUseRecord(getDb: () => Pick<KoolbaseDatabase, 'get'>): (collection: string, id: string | null | undefined
|
|
6
|
+
export declare function createUseRecord(getDb: () => Pick<KoolbaseDatabase, 'get'>): (collection: string, id: string | null | undefined, options?: {
|
|
7
|
+
live?: boolean;
|
|
8
|
+
}) => UseRecordResult;
|
|
@@ -8,13 +8,15 @@ import { KoolbaseRecordController, initialRecordState, } from '../index.js';
|
|
|
8
8
|
const noop = () => { };
|
|
9
9
|
const done = () => Promise.resolve();
|
|
10
10
|
export function createUseRecord(getDb) {
|
|
11
|
-
return function useRecord(collection, id) {
|
|
12
|
-
|
|
11
|
+
return function useRecord(collection, id, options = {}) {
|
|
12
|
+
// live is part of the key: switching it starts a fresh controller.
|
|
13
|
+
const live = options.live === true;
|
|
14
|
+
const key = JSON.stringify([collection, id ?? '', ...(live ? ['live'] : [])]);
|
|
13
15
|
const [current, setCurrent] = useState(null);
|
|
14
16
|
useEffect(() => {
|
|
15
17
|
// Created here, not during render: under React strict mode an effect
|
|
16
18
|
// runs, cleans up and runs again, and each run gets its own controller.
|
|
17
|
-
const controller = new KoolbaseRecordController(getDb(), collection, id ?? '');
|
|
19
|
+
const controller = new KoolbaseRecordController(getDb(), collection, id ?? '', { live });
|
|
18
20
|
setCurrent({ key, controller });
|
|
19
21
|
void controller.load();
|
|
20
22
|
return () => controller.dispose();
|
|
@@ -19,7 +19,13 @@ export declare class KoolbaseRecordController {
|
|
|
19
19
|
private readonly listeners;
|
|
20
20
|
private generation;
|
|
21
21
|
private disposed;
|
|
22
|
-
|
|
22
|
+
/** Follow this record through Koolbase realtime (see onLive). */
|
|
23
|
+
private readonly live;
|
|
24
|
+
private unlive;
|
|
25
|
+
private liveTimer;
|
|
26
|
+
constructor(db: RecordReader, collection: string, id: string, options?: {
|
|
27
|
+
live?: boolean;
|
|
28
|
+
});
|
|
23
29
|
getState(): RecordState;
|
|
24
30
|
/** Called on every change. Returns the unsubscribe. */
|
|
25
31
|
subscribe(listener: () => void): () => void;
|
|
@@ -29,6 +35,13 @@ export declare class KoolbaseRecordController {
|
|
|
29
35
|
refresh(): Promise<void>;
|
|
30
36
|
/** Stops all updates. A result still in flight is dropped when it lands. */
|
|
31
37
|
dispose(): void;
|
|
38
|
+
/**
|
|
39
|
+
* A realtime event in this record's collection. Only one naming THIS record
|
|
40
|
+
* counts: an update (or a create, for an id that reappears) re-reads it,
|
|
41
|
+
* silently -- no `refreshing` -- and once per burst (250 ms); a delete is
|
|
42
|
+
* notFound at once, and a read still in flight is dropped.
|
|
43
|
+
*/
|
|
44
|
+
private onLive;
|
|
32
45
|
private fetch;
|
|
33
46
|
private isStale;
|
|
34
47
|
private set;
|
|
@@ -12,9 +12,12 @@
|
|
|
12
12
|
// - refresh() keeps the record shown while it runs, and if it fails.
|
|
13
13
|
// - every fetch has a generation: a result superseded by a later refresh,
|
|
14
14
|
// or landing after dispose(), is dropped.
|
|
15
|
+
// - live (the option): realtime events for THIS record only. An update
|
|
16
|
+
// re-reads it silently, once per burst; a delete is notFound at once.
|
|
15
17
|
// Not yet: an offline fallback from the record cache, which keeps data and
|
|
16
18
|
// revision but not createdAt or createdBy.
|
|
17
19
|
import { KoolbaseNotFoundError } from './database-errors.js';
|
|
20
|
+
import { liveSource } from './live-source.js';
|
|
18
21
|
export const initialRecordState = Object.freeze({
|
|
19
22
|
status: 'loading',
|
|
20
23
|
record: null,
|
|
@@ -22,14 +25,18 @@ export const initialRecordState = Object.freeze({
|
|
|
22
25
|
refreshing: false,
|
|
23
26
|
});
|
|
24
27
|
export class KoolbaseRecordController {
|
|
25
|
-
constructor(db, collection, id) {
|
|
28
|
+
constructor(db, collection, id, options = {}) {
|
|
26
29
|
this.state = initialRecordState;
|
|
27
30
|
this.listeners = new Set();
|
|
28
31
|
this.generation = 0;
|
|
29
32
|
this.disposed = false;
|
|
33
|
+
// The realtime subscription, and its pending debounced re-read.
|
|
34
|
+
this.unlive = null;
|
|
35
|
+
this.liveTimer = null;
|
|
30
36
|
this.db = db;
|
|
31
37
|
this.collection = collection;
|
|
32
38
|
this.id = id;
|
|
39
|
+
this.live = options.live === true;
|
|
33
40
|
}
|
|
34
41
|
getState() {
|
|
35
42
|
return this.state;
|
|
@@ -47,6 +54,10 @@ export class KoolbaseRecordController {
|
|
|
47
54
|
async load() {
|
|
48
55
|
if (this.disposed)
|
|
49
56
|
return;
|
|
57
|
+
// Live needs an id: with none there is nothing to follow.
|
|
58
|
+
if (this.live && this.id) {
|
|
59
|
+
this.unlive ?? (this.unlive = liveSource()?.subscribe(this.collection, (event) => this.onLive(event)) ?? null);
|
|
60
|
+
}
|
|
50
61
|
await this.fetch();
|
|
51
62
|
}
|
|
52
63
|
/** The record again, from the server. What is shown stays while it runs, and stays if it fails. */
|
|
@@ -61,8 +72,45 @@ export class KoolbaseRecordController {
|
|
|
61
72
|
/** Stops all updates. A result still in flight is dropped when it lands. */
|
|
62
73
|
dispose() {
|
|
63
74
|
this.disposed = true;
|
|
75
|
+
this.unlive?.();
|
|
76
|
+
this.unlive = null;
|
|
77
|
+
if (this.liveTimer) {
|
|
78
|
+
clearTimeout(this.liveTimer);
|
|
79
|
+
this.liveTimer = null;
|
|
80
|
+
}
|
|
64
81
|
this.listeners.clear();
|
|
65
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* A realtime event in this record's collection. Only one naming THIS record
|
|
85
|
+
* counts: an update (or a create, for an id that reappears) re-reads it,
|
|
86
|
+
* silently -- no `refreshing` -- and once per burst (250 ms); a delete is
|
|
87
|
+
* notFound at once, and a read still in flight is dropped.
|
|
88
|
+
*/
|
|
89
|
+
onLive(event) {
|
|
90
|
+
if (this.disposed)
|
|
91
|
+
return;
|
|
92
|
+
const e = (event ?? {});
|
|
93
|
+
const target = e.type === 'deleted' ? e.recordId : e.record?.id;
|
|
94
|
+
if (target !== this.id)
|
|
95
|
+
return;
|
|
96
|
+
if (e.type === 'deleted') {
|
|
97
|
+
if (this.liveTimer) {
|
|
98
|
+
clearTimeout(this.liveTimer);
|
|
99
|
+
this.liveTimer = null;
|
|
100
|
+
}
|
|
101
|
+
this.generation += 1;
|
|
102
|
+
this.set({ status: 'notFound', record: null, error: null, refreshing: false });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (this.liveTimer)
|
|
106
|
+
return;
|
|
107
|
+
this.liveTimer = setTimeout(() => {
|
|
108
|
+
this.liveTimer = null;
|
|
109
|
+
void this.fetch();
|
|
110
|
+
}, 250);
|
|
111
|
+
// A pending re-read must not be what keeps a Node process alive.
|
|
112
|
+
this.liveTimer.unref?.();
|
|
113
|
+
}
|
|
66
114
|
async fetch() {
|
|
67
115
|
const gen = ++this.generation;
|
|
68
116
|
if (!this.id) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koolbase/core",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.12.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",
|