@koolbase/core 12.5.0 → 12.6.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.
@@ -12,6 +12,8 @@ export { KoolbaseAuth } from './auth.js';
12
12
  export { KoolbaseDatabase } from './database.js';
13
13
  export { KoolbaseCollectionController, collectionQueryKey, initialCollectionState, } from './collection-controller.js';
14
14
  export type { CollectionQuery, CollectionState, CollectionStatus } from './collection-controller.js';
15
+ export { KoolbaseRecordController, initialRecordState } from './record-controller.js';
16
+ export type { RecordState, RecordStatus } from './record-controller.js';
15
17
  export { KoolbaseFlags } from './flags.js';
16
18
  export { KoolbaseFunctions } from './functions.js';
17
19
  export { KoolbaseRealtime } from './realtime.js';
package/dist/cjs/index.js CHANGED
@@ -19,7 +19,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
19
19
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
20
20
  };
21
21
  Object.defineProperty(exports, "__esModule", { value: true });
22
- exports.memoryPlatform = exports.getPlatform = exports.setPlatform = exports.RestoreResult = exports.koolbaseSdkVersion = exports.getOrCreateDeviceId = exports.KoolbaseMessaging = exports.isPending = exports.isFiscalized = exports.decodeFiscalResponse = exports.KoolbaseFiscalError = exports.KoolbaseFiscal = exports.disabledAnalytics = exports.KoolbaseAnalytics = exports.KoolbaseStorage = exports.KoolbaseRealtime = exports.KoolbaseFunctions = exports.KoolbaseFlags = exports.initialCollectionState = exports.collectionQueryKey = exports.KoolbaseCollectionController = exports.KoolbaseDatabase = exports.KoolbaseAuth = void 0;
22
+ exports.memoryPlatform = exports.getPlatform = exports.setPlatform = exports.RestoreResult = exports.koolbaseSdkVersion = exports.getOrCreateDeviceId = exports.KoolbaseMessaging = exports.isPending = exports.isFiscalized = exports.decodeFiscalResponse = exports.KoolbaseFiscalError = exports.KoolbaseFiscal = exports.disabledAnalytics = exports.KoolbaseAnalytics = exports.KoolbaseStorage = exports.KoolbaseRealtime = exports.KoolbaseFunctions = exports.KoolbaseFlags = exports.initialRecordState = exports.KoolbaseRecordController = exports.initialCollectionState = exports.collectionQueryKey = exports.KoolbaseCollectionController = exports.KoolbaseDatabase = exports.KoolbaseAuth = void 0;
23
23
  __exportStar(require("./types.js"), exports);
24
24
  __exportStar(require("./errors.js"), exports);
25
25
  __exportStar(require("./network.js"), exports);
@@ -38,6 +38,9 @@ var collection_controller_js_1 = require("./collection-controller.js");
38
38
  Object.defineProperty(exports, "KoolbaseCollectionController", { enumerable: true, get: function () { return collection_controller_js_1.KoolbaseCollectionController; } });
39
39
  Object.defineProperty(exports, "collectionQueryKey", { enumerable: true, get: function () { return collection_controller_js_1.collectionQueryKey; } });
40
40
  Object.defineProperty(exports, "initialCollectionState", { enumerable: true, get: function () { return collection_controller_js_1.initialCollectionState; } });
41
+ var record_controller_js_1 = require("./record-controller.js");
42
+ Object.defineProperty(exports, "KoolbaseRecordController", { enumerable: true, get: function () { return record_controller_js_1.KoolbaseRecordController; } });
43
+ Object.defineProperty(exports, "initialRecordState", { enumerable: true, get: function () { return record_controller_js_1.initialRecordState; } });
41
44
  var flags_js_1 = require("./flags.js");
42
45
  Object.defineProperty(exports, "KoolbaseFlags", { enumerable: true, get: function () { return flags_js_1.KoolbaseFlags; } });
43
46
  var functions_js_1 = require("./functions.js");
@@ -0,0 +1,36 @@
1
+ import type { KoolbaseDatabase } from './database.js';
2
+ import type { KoolbaseRecord } from './types.js';
3
+ export type RecordStatus = 'loading' | 'loaded' | 'notFound' | 'error';
4
+ export interface RecordState {
5
+ readonly status: RecordStatus;
6
+ readonly record: KoolbaseRecord | null;
7
+ /** Why the first load failed, while status is 'error'. */
8
+ readonly error: unknown;
9
+ /** True while refresh() runs. */
10
+ readonly refreshing: boolean;
11
+ }
12
+ export declare const initialRecordState: RecordState;
13
+ type RecordReader = Pick<KoolbaseDatabase, 'get'>;
14
+ export declare class KoolbaseRecordController {
15
+ readonly collection: string;
16
+ readonly id: string;
17
+ private readonly db;
18
+ private state;
19
+ private readonly listeners;
20
+ private generation;
21
+ private disposed;
22
+ constructor(db: RecordReader, collection: string, id: string);
23
+ getState(): RecordState;
24
+ /** Called on every change. Returns the unsubscribe. */
25
+ subscribe(listener: () => void): () => void;
26
+ /** The first load. Call once; refresh() for later loads. */
27
+ load(): Promise<void>;
28
+ /** The record again, from the server. What is shown stays while it runs, and stays if it fails. */
29
+ refresh(): Promise<void>;
30
+ /** Stops all updates. A result still in flight is dropped when it lands. */
31
+ dispose(): void;
32
+ private fetch;
33
+ private isStale;
34
+ private set;
35
+ }
36
+ export {};
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ // One record, read by id, framework-free: the detail-screen counterpart of
3
+ // KoolbaseCollectionController. @koolbase/react-native's useRecord wraps it.
4
+ //
5
+ // - loading, loaded, notFound or error. notFound is its own state because
6
+ // the API answers a denied read as "not found" too (it does not reveal
7
+ // whether a record exists): a detail screen says "not available", not
8
+ // "something went wrong".
9
+ // - an empty id is notFound with no request: a screen opened without one.
10
+ // - a record from a different collection is notFound: the API names each
11
+ // record's collection, and a mistyped or crafted id must not put another
12
+ // collection's record on this screen.
13
+ // - refresh() keeps the record shown while it runs, and if it fails.
14
+ // - every fetch has a generation: a result superseded by a later refresh,
15
+ // or landing after dispose(), is dropped.
16
+ // Not yet: an offline fallback from the record cache, which keeps data and
17
+ // revision but not createdAt or createdBy.
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.KoolbaseRecordController = exports.initialRecordState = void 0;
20
+ const database_errors_js_1 = require("./database-errors.js");
21
+ exports.initialRecordState = Object.freeze({
22
+ status: 'loading',
23
+ record: null,
24
+ error: null,
25
+ refreshing: false,
26
+ });
27
+ class KoolbaseRecordController {
28
+ constructor(db, collection, id) {
29
+ this.state = exports.initialRecordState;
30
+ this.listeners = new Set();
31
+ this.generation = 0;
32
+ this.disposed = false;
33
+ this.db = db;
34
+ this.collection = collection;
35
+ this.id = id;
36
+ }
37
+ getState() {
38
+ return this.state;
39
+ }
40
+ /** Called on every change. Returns the unsubscribe. */
41
+ subscribe(listener) {
42
+ if (this.disposed)
43
+ return () => { };
44
+ this.listeners.add(listener);
45
+ return () => {
46
+ this.listeners.delete(listener);
47
+ };
48
+ }
49
+ /** The first load. Call once; refresh() for later loads. */
50
+ async load() {
51
+ if (this.disposed)
52
+ return;
53
+ await this.fetch();
54
+ }
55
+ /** The record again, from the server. What is shown stays while it runs, and stays if it fails. */
56
+ async refresh() {
57
+ if (this.disposed)
58
+ return;
59
+ this.set({ refreshing: true });
60
+ const gen = await this.fetch();
61
+ if (!this.isStale(gen))
62
+ this.set({ refreshing: false });
63
+ }
64
+ /** Stops all updates. A result still in flight is dropped when it lands. */
65
+ dispose() {
66
+ this.disposed = true;
67
+ this.listeners.clear();
68
+ }
69
+ async fetch() {
70
+ const gen = ++this.generation;
71
+ if (!this.id) {
72
+ this.set({ status: 'notFound', record: null, error: null });
73
+ return gen;
74
+ }
75
+ try {
76
+ const record = await this.db.get(this.id);
77
+ if (this.isStale(gen))
78
+ return gen;
79
+ if (record.collection !== undefined && record.collection !== this.collection) {
80
+ this.set({ status: 'notFound', record: null, error: null });
81
+ }
82
+ else {
83
+ this.set({ status: 'loaded', record, error: null });
84
+ }
85
+ }
86
+ catch (error) {
87
+ if (this.isStale(gen))
88
+ return gen;
89
+ if (error instanceof database_errors_js_1.KoolbaseNotFoundError) {
90
+ this.set({ status: 'notFound', record: null, error: null });
91
+ }
92
+ else if (this.state.record === null) {
93
+ this.set({ status: 'error', error });
94
+ }
95
+ // Otherwise keep the record shown: stale beats blank.
96
+ }
97
+ return gen;
98
+ }
99
+ isStale(gen) {
100
+ return this.disposed || gen !== this.generation;
101
+ }
102
+ set(patch) {
103
+ if (this.disposed)
104
+ return;
105
+ this.state = Object.freeze({ ...this.state, ...patch });
106
+ for (const listener of [...this.listeners])
107
+ listener();
108
+ }
109
+ }
110
+ exports.KoolbaseRecordController = KoolbaseRecordController;
@@ -12,6 +12,8 @@ export { KoolbaseAuth } from './auth.js';
12
12
  export { KoolbaseDatabase } from './database.js';
13
13
  export { KoolbaseCollectionController, collectionQueryKey, initialCollectionState, } from './collection-controller.js';
14
14
  export type { CollectionQuery, CollectionState, CollectionStatus } from './collection-controller.js';
15
+ export { KoolbaseRecordController, initialRecordState } from './record-controller.js';
16
+ export type { RecordState, RecordStatus } from './record-controller.js';
15
17
  export { KoolbaseFlags } from './flags.js';
16
18
  export { KoolbaseFunctions } from './functions.js';
17
19
  export { KoolbaseRealtime } from './realtime.js';
package/dist/esm/index.js CHANGED
@@ -16,6 +16,7 @@ export * from './storage-errors.js';
16
16
  export { KoolbaseAuth } from './auth.js';
17
17
  export { KoolbaseDatabase } from './database.js';
18
18
  export { KoolbaseCollectionController, collectionQueryKey, initialCollectionState, } from './collection-controller.js';
19
+ export { KoolbaseRecordController, initialRecordState } from './record-controller.js';
19
20
  export { KoolbaseFlags } from './flags.js';
20
21
  export { KoolbaseFunctions } from './functions.js';
21
22
  export { KoolbaseRealtime } from './realtime.js';
@@ -0,0 +1,36 @@
1
+ import type { KoolbaseDatabase } from './database.js';
2
+ import type { KoolbaseRecord } from './types.js';
3
+ export type RecordStatus = 'loading' | 'loaded' | 'notFound' | 'error';
4
+ export interface RecordState {
5
+ readonly status: RecordStatus;
6
+ readonly record: KoolbaseRecord | null;
7
+ /** Why the first load failed, while status is 'error'. */
8
+ readonly error: unknown;
9
+ /** True while refresh() runs. */
10
+ readonly refreshing: boolean;
11
+ }
12
+ export declare const initialRecordState: RecordState;
13
+ type RecordReader = Pick<KoolbaseDatabase, 'get'>;
14
+ export declare class KoolbaseRecordController {
15
+ readonly collection: string;
16
+ readonly id: string;
17
+ private readonly db;
18
+ private state;
19
+ private readonly listeners;
20
+ private generation;
21
+ private disposed;
22
+ constructor(db: RecordReader, collection: string, id: string);
23
+ getState(): RecordState;
24
+ /** Called on every change. Returns the unsubscribe. */
25
+ subscribe(listener: () => void): () => void;
26
+ /** The first load. Call once; refresh() for later loads. */
27
+ load(): Promise<void>;
28
+ /** The record again, from the server. What is shown stays while it runs, and stays if it fails. */
29
+ refresh(): Promise<void>;
30
+ /** Stops all updates. A result still in flight is dropped when it lands. */
31
+ dispose(): void;
32
+ private fetch;
33
+ private isStale;
34
+ private set;
35
+ }
36
+ export {};
@@ -0,0 +1,106 @@
1
+ // One record, read by id, framework-free: the detail-screen counterpart of
2
+ // KoolbaseCollectionController. @koolbase/react-native's useRecord wraps it.
3
+ //
4
+ // - loading, loaded, notFound or error. notFound is its own state because
5
+ // the API answers a denied read as "not found" too (it does not reveal
6
+ // whether a record exists): a detail screen says "not available", not
7
+ // "something went wrong".
8
+ // - an empty id is notFound with no request: a screen opened without one.
9
+ // - a record from a different collection is notFound: the API names each
10
+ // record's collection, and a mistyped or crafted id must not put another
11
+ // collection's record on this screen.
12
+ // - refresh() keeps the record shown while it runs, and if it fails.
13
+ // - every fetch has a generation: a result superseded by a later refresh,
14
+ // or landing after dispose(), is dropped.
15
+ // Not yet: an offline fallback from the record cache, which keeps data and
16
+ // revision but not createdAt or createdBy.
17
+ import { KoolbaseNotFoundError } from './database-errors.js';
18
+ export const initialRecordState = Object.freeze({
19
+ status: 'loading',
20
+ record: null,
21
+ error: null,
22
+ refreshing: false,
23
+ });
24
+ export class KoolbaseRecordController {
25
+ constructor(db, collection, id) {
26
+ this.state = initialRecordState;
27
+ this.listeners = new Set();
28
+ this.generation = 0;
29
+ this.disposed = false;
30
+ this.db = db;
31
+ this.collection = collection;
32
+ this.id = id;
33
+ }
34
+ getState() {
35
+ return this.state;
36
+ }
37
+ /** Called on every change. Returns the unsubscribe. */
38
+ subscribe(listener) {
39
+ if (this.disposed)
40
+ return () => { };
41
+ this.listeners.add(listener);
42
+ return () => {
43
+ this.listeners.delete(listener);
44
+ };
45
+ }
46
+ /** The first load. Call once; refresh() for later loads. */
47
+ async load() {
48
+ if (this.disposed)
49
+ return;
50
+ await this.fetch();
51
+ }
52
+ /** The record again, from the server. What is shown stays while it runs, and stays if it fails. */
53
+ async refresh() {
54
+ if (this.disposed)
55
+ return;
56
+ this.set({ refreshing: true });
57
+ const gen = await this.fetch();
58
+ if (!this.isStale(gen))
59
+ this.set({ refreshing: false });
60
+ }
61
+ /** Stops all updates. A result still in flight is dropped when it lands. */
62
+ dispose() {
63
+ this.disposed = true;
64
+ this.listeners.clear();
65
+ }
66
+ async fetch() {
67
+ const gen = ++this.generation;
68
+ if (!this.id) {
69
+ this.set({ status: 'notFound', record: null, error: null });
70
+ return gen;
71
+ }
72
+ try {
73
+ const record = await this.db.get(this.id);
74
+ if (this.isStale(gen))
75
+ return gen;
76
+ if (record.collection !== undefined && record.collection !== this.collection) {
77
+ this.set({ status: 'notFound', record: null, error: null });
78
+ }
79
+ else {
80
+ this.set({ status: 'loaded', record, error: null });
81
+ }
82
+ }
83
+ catch (error) {
84
+ if (this.isStale(gen))
85
+ return gen;
86
+ if (error instanceof KoolbaseNotFoundError) {
87
+ this.set({ status: 'notFound', record: null, error: null });
88
+ }
89
+ else if (this.state.record === null) {
90
+ this.set({ status: 'error', error });
91
+ }
92
+ // Otherwise keep the record shown: stale beats blank.
93
+ }
94
+ return gen;
95
+ }
96
+ isStale(gen) {
97
+ return this.disposed || gen !== this.generation;
98
+ }
99
+ set(patch) {
100
+ if (this.disposed)
101
+ return;
102
+ this.state = Object.freeze({ ...this.state, ...patch });
103
+ for (const listener of [...this.listeners])
104
+ listener();
105
+ }
106
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koolbase/core",
3
- "version": "12.5.0",
3
+ "version": "12.6.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",