@byearlybird/starling 0.6.1 → 0.7.2
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/index.d.ts +2 -74
- package/dist/index.js +53 -19
- package/dist/plugin-query.d.ts +19 -0
- package/dist/plugin-query.js +102 -0
- package/dist/plugin-unstorage.d.ts +16 -0
- package/dist/plugin-unstorage.js +68 -0
- package/dist/store-CMzcvcsT.d.ts +85 -0
- package/package.json +26 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,74 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
type
|
|
3
|
-
"~value": T$1;
|
|
4
|
-
"~eventstamp": string;
|
|
5
|
-
};
|
|
6
|
-
//#endregion
|
|
7
|
-
//#region src/record.d.ts
|
|
8
|
-
type EncodedRecord = {
|
|
9
|
-
[key: string]: EncodedValue<unknown> | EncodedRecord;
|
|
10
|
-
};
|
|
11
|
-
//#endregion
|
|
12
|
-
//#region src/document.d.ts
|
|
13
|
-
type EncodedDocument = {
|
|
14
|
-
"~id": string;
|
|
15
|
-
"~data": EncodedValue<unknown> | EncodedRecord;
|
|
16
|
-
"~deletedAt": string | null;
|
|
17
|
-
};
|
|
18
|
-
declare const processDocument: (doc: EncodedDocument, process: (value: EncodedValue<unknown>) => EncodedValue<unknown>) => EncodedDocument;
|
|
19
|
-
//#endregion
|
|
20
|
-
//#region src/transaction.d.ts
|
|
21
|
-
type DeepPartial<T$1> = T$1 extends object ? { [P in keyof T$1]?: DeepPartial<T$1[P]> } : T$1;
|
|
22
|
-
type StorePutOptions = {
|
|
23
|
-
withId?: string;
|
|
24
|
-
};
|
|
25
|
-
type StoreSetTransaction<T$1> = {
|
|
26
|
-
add: (value: T$1, options?: StorePutOptions) => string;
|
|
27
|
-
update: (key: string, value: DeepPartial<T$1>) => void;
|
|
28
|
-
merge: (doc: EncodedDocument) => void;
|
|
29
|
-
del: (key: string) => void;
|
|
30
|
-
get: (key: string) => T$1 | null;
|
|
31
|
-
rollback: () => void;
|
|
32
|
-
};
|
|
33
|
-
//#endregion
|
|
34
|
-
//#region src/store.d.ts
|
|
35
|
-
/**
|
|
36
|
-
* Type constraint to prevent Promise returns from set callbacks.
|
|
37
|
-
* Transactions must be synchronous operations.
|
|
38
|
-
*/
|
|
39
|
-
type NotPromise<T$1> = T$1 extends Promise<any> ? never : T$1;
|
|
40
|
-
/**
|
|
41
|
-
* Plugin lifecycle and event hooks.
|
|
42
|
-
* All hooks are optional except onInit and onDispose, which are required.
|
|
43
|
-
*/
|
|
44
|
-
type PluginHooks<T$1> = {
|
|
45
|
-
onInit: (store: Store<T$1>) => Promise<void> | void;
|
|
46
|
-
onDispose: () => Promise<void> | void;
|
|
47
|
-
onAdd?: (entries: ReadonlyArray<readonly [string, T$1]>) => void;
|
|
48
|
-
onUpdate?: (entries: ReadonlyArray<readonly [string, T$1]>) => void;
|
|
49
|
-
onDelete?: (keys: ReadonlyArray<string>) => void;
|
|
50
|
-
};
|
|
51
|
-
type PluginMethods = Record<string, (...args: any[]) => any>;
|
|
52
|
-
type Plugin<T$1, M$1 extends PluginMethods = {}> = {
|
|
53
|
-
hooks: PluginHooks<T$1>;
|
|
54
|
-
methods?: M$1;
|
|
55
|
-
};
|
|
56
|
-
type Store<T$1, Extended = {}> = {
|
|
57
|
-
get: (key: string) => T$1 | null;
|
|
58
|
-
begin: <R = void>(callback: (tx: StoreSetTransaction<T$1>) => NotPromise<R>, opts?: {
|
|
59
|
-
silent?: boolean;
|
|
60
|
-
}) => NotPromise<R>;
|
|
61
|
-
add: (value: T$1, options?: StorePutOptions) => string;
|
|
62
|
-
update: (key: string, value: DeepPartial<T$1>) => void;
|
|
63
|
-
del: (key: string) => void;
|
|
64
|
-
entries: () => IterableIterator<readonly [string, T$1]>;
|
|
65
|
-
snapshot: () => EncodedDocument[];
|
|
66
|
-
use: <M extends PluginMethods>(plugin: Plugin<T$1, M>) => Store<T$1, Extended & M>;
|
|
67
|
-
init: () => Promise<Store<T$1, Extended>>;
|
|
68
|
-
dispose: () => Promise<void>;
|
|
69
|
-
} & Extended;
|
|
70
|
-
declare const createStore: <T>(config?: {
|
|
71
|
-
getId?: () => string;
|
|
72
|
-
}) => Store<T, {}>;
|
|
73
|
-
//#endregion
|
|
74
|
-
export { type EncodedDocument, Plugin, PluginHooks, PluginMethods, Store, createStore, processDocument };
|
|
1
|
+
import { a as StoreSnapshot, c as processDocument, i as Store, n as PluginHooks, o as createStore, r as PluginMethods, s as EncodedDocument, t as Plugin } from "./store-CMzcvcsT.js";
|
|
2
|
+
export { type EncodedDocument, Plugin, PluginHooks, PluginMethods, Store, StoreSnapshot, createStore, processDocument };
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ const encodeValue = (value, eventstamp) => ({
|
|
|
8
8
|
"~eventstamp": eventstamp
|
|
9
9
|
});
|
|
10
10
|
const decodeValue = (value) => value["~value"];
|
|
11
|
-
const mergeValues = (into, from) => into["~eventstamp"] > from["~eventstamp"] ? into : from;
|
|
11
|
+
const mergeValues = (into, from) => into["~eventstamp"] > from["~eventstamp"] ? [into, into["~eventstamp"]] : [from, from["~eventstamp"]];
|
|
12
12
|
const isEncodedValue = (value) => !!(typeof value === "object" && value !== null && "~value" in value && "~eventstamp" in value);
|
|
13
13
|
|
|
14
14
|
//#endregion
|
|
@@ -62,14 +62,21 @@ const decodeRecord = (obj) => {
|
|
|
62
62
|
};
|
|
63
63
|
const mergeRecords = (into, from) => {
|
|
64
64
|
const result = {};
|
|
65
|
+
let greatestEventstamp = null;
|
|
65
66
|
const step = (v1, v2, output) => {
|
|
66
67
|
for (const key in v1) {
|
|
67
68
|
if (!Object.hasOwn(v1, key)) continue;
|
|
68
69
|
const value1 = v1[key];
|
|
69
70
|
const value2 = v2[key];
|
|
70
|
-
if (isEncodedValue(value1) && isEncodedValue(value2))
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
if (isEncodedValue(value1) && isEncodedValue(value2)) {
|
|
72
|
+
const [win, eventstamp] = mergeValues(value1, value2);
|
|
73
|
+
output[key] = win;
|
|
74
|
+
if (!greatestEventstamp || eventstamp > greatestEventstamp) greatestEventstamp = eventstamp;
|
|
75
|
+
} else if (isEncodedValue(value1)) {
|
|
76
|
+
output[key] = value1;
|
|
77
|
+
const eventstamp = value1["~eventstamp"];
|
|
78
|
+
if (!greatestEventstamp || eventstamp > greatestEventstamp) greatestEventstamp = eventstamp;
|
|
79
|
+
} else if (isObject(value1) && isObject(value2)) {
|
|
73
80
|
output[key] = {};
|
|
74
81
|
step(value1, value2, output[key]);
|
|
75
82
|
} else if (value1) output[key] = value1;
|
|
@@ -77,11 +84,17 @@ const mergeRecords = (into, from) => {
|
|
|
77
84
|
for (const key in v2) {
|
|
78
85
|
if (!Object.hasOwn(v2, key) || Object.hasOwn(output, key)) continue;
|
|
79
86
|
const value = v2[key];
|
|
80
|
-
if (value !== void 0)
|
|
87
|
+
if (value !== void 0) {
|
|
88
|
+
output[key] = value;
|
|
89
|
+
if (isEncodedValue(value)) {
|
|
90
|
+
const eventstamp = value["~eventstamp"];
|
|
91
|
+
if (!greatestEventstamp || eventstamp > greatestEventstamp) greatestEventstamp = eventstamp;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
81
94
|
}
|
|
82
95
|
};
|
|
83
96
|
step(into, from, result);
|
|
84
|
-
return result;
|
|
97
|
+
return [result, greatestEventstamp];
|
|
85
98
|
};
|
|
86
99
|
|
|
87
100
|
//#endregion
|
|
@@ -100,13 +113,17 @@ const mergeDocs = (into, from) => {
|
|
|
100
113
|
const intoIsValue = isEncodedValue(into["~data"]);
|
|
101
114
|
const fromIsValue = isEncodedValue(from["~data"]);
|
|
102
115
|
if (intoIsValue !== fromIsValue) throw new Error("Merge error: Incompatible types");
|
|
103
|
-
const mergedData = intoIsValue && fromIsValue ? mergeValues(into["~data"], from["~data"]) : mergeRecords(into["~data"], from["~data"]);
|
|
116
|
+
const [mergedData, dataEventstamp] = intoIsValue && fromIsValue ? mergeValues(into["~data"], from["~data"]) : mergeRecords(into["~data"], from["~data"]);
|
|
104
117
|
const mergedDeletedAt = into["~deletedAt"] && from["~deletedAt"] ? into["~deletedAt"] > from["~deletedAt"] ? into["~deletedAt"] : from["~deletedAt"] : into["~deletedAt"] || from["~deletedAt"] || null;
|
|
105
|
-
|
|
118
|
+
let greatestEventstamp = dataEventstamp;
|
|
119
|
+
if (mergedDeletedAt) {
|
|
120
|
+
if (!greatestEventstamp || mergedDeletedAt > greatestEventstamp) greatestEventstamp = mergedDeletedAt;
|
|
121
|
+
}
|
|
122
|
+
return [{
|
|
106
123
|
"~id": into["~id"],
|
|
107
124
|
"~data": mergedData,
|
|
108
125
|
"~deletedAt": mergedDeletedAt
|
|
109
|
-
};
|
|
126
|
+
}, greatestEventstamp];
|
|
110
127
|
};
|
|
111
128
|
const deleteDoc = (doc, eventstamp) => ({
|
|
112
129
|
"~id": doc["~id"],
|
|
@@ -144,13 +161,12 @@ const createClock = () => {
|
|
|
144
161
|
let lastMs = Date.now();
|
|
145
162
|
return {
|
|
146
163
|
now: () => {
|
|
147
|
-
const
|
|
148
|
-
if (
|
|
149
|
-
|
|
150
|
-
lastMs = nowMs;
|
|
164
|
+
const wallMs = Date.now();
|
|
165
|
+
if (wallMs > lastMs) {
|
|
166
|
+
lastMs = wallMs;
|
|
151
167
|
counter = 0;
|
|
152
|
-
}
|
|
153
|
-
return encodeEventstamp(
|
|
168
|
+
} else counter++;
|
|
169
|
+
return encodeEventstamp(lastMs, counter);
|
|
154
170
|
},
|
|
155
171
|
latest() {
|
|
156
172
|
return encodeEventstamp(lastMs, counter);
|
|
@@ -190,10 +206,19 @@ const createKV = (iterable) => {
|
|
|
190
206
|
return staging.get(key) ?? null;
|
|
191
207
|
},
|
|
192
208
|
set(key, value, opts) {
|
|
193
|
-
if (opts?.replace)
|
|
194
|
-
|
|
209
|
+
if (opts?.replace) {
|
|
210
|
+
staging.set(key, value);
|
|
211
|
+
return null;
|
|
212
|
+
} else {
|
|
195
213
|
const prev = staging.get(key);
|
|
196
|
-
|
|
214
|
+
if (prev) {
|
|
215
|
+
const [merged, eventstamp] = mergeDocs(prev, value);
|
|
216
|
+
staging.set(key, merged);
|
|
217
|
+
return eventstamp;
|
|
218
|
+
} else {
|
|
219
|
+
staging.set(key, value);
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
197
222
|
}
|
|
198
223
|
},
|
|
199
224
|
del(key, eventstamp) {
|
|
@@ -286,7 +311,10 @@ const createStore = (config = {}) => {
|
|
|
286
311
|
return iterator();
|
|
287
312
|
},
|
|
288
313
|
snapshot() {
|
|
289
|
-
return
|
|
314
|
+
return {
|
|
315
|
+
docs: Array.from(kv.values()),
|
|
316
|
+
latestEventstamp: clock.latest()
|
|
317
|
+
};
|
|
290
318
|
},
|
|
291
319
|
begin(callback, opts) {
|
|
292
320
|
const silent = opts?.silent ?? false;
|
|
@@ -360,6 +388,12 @@ const createStore = (config = {}) => {
|
|
|
360
388
|
const disposerArray = Array.from(onDisposeHandlers);
|
|
361
389
|
disposerArray.reverse();
|
|
362
390
|
for (const fn of disposerArray) await fn();
|
|
391
|
+
},
|
|
392
|
+
latestEventstamp() {
|
|
393
|
+
return clock.latest();
|
|
394
|
+
},
|
|
395
|
+
forwardClock(eventstamp) {
|
|
396
|
+
clock.forward(eventstamp);
|
|
363
397
|
}
|
|
364
398
|
};
|
|
365
399
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { t as Plugin } from "./store-CMzcvcsT.js";
|
|
2
|
+
|
|
3
|
+
//#region src/plugins/query/plugin.d.ts
|
|
4
|
+
type QueryConfig<T, U = T> = {
|
|
5
|
+
where: (data: T) => boolean;
|
|
6
|
+
select?: (data: T) => U;
|
|
7
|
+
order?: (a: U, b: U) => number;
|
|
8
|
+
};
|
|
9
|
+
type Query<U> = {
|
|
10
|
+
results: () => Map<string, U>;
|
|
11
|
+
onChange: (callback: () => void) => () => void;
|
|
12
|
+
dispose: () => void;
|
|
13
|
+
};
|
|
14
|
+
type QueryMethods<T> = {
|
|
15
|
+
query: <U = T>(config: QueryConfig<T, U>) => Query<U>;
|
|
16
|
+
};
|
|
17
|
+
declare const queryPlugin: <T>() => Plugin<T, QueryMethods<T>>;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { type Query, type QueryConfig, type QueryMethods, queryPlugin };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
//#region src/plugins/query/plugin.ts
|
|
2
|
+
const queryPlugin = () => {
|
|
3
|
+
const queries = /* @__PURE__ */ new Set();
|
|
4
|
+
let store = null;
|
|
5
|
+
const hydrateQuery = (query) => {
|
|
6
|
+
if (!store) return;
|
|
7
|
+
query.results.clear();
|
|
8
|
+
for (const [key, value] of store.entries()) if (query.where(value)) {
|
|
9
|
+
const selected = query.select ? query.select(value) : value;
|
|
10
|
+
query.results.set(key, selected);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const runCallbacks = (dirtyQueries) => {
|
|
14
|
+
for (const query of dirtyQueries) for (const callback of query.callbacks) callback();
|
|
15
|
+
dirtyQueries.clear();
|
|
16
|
+
};
|
|
17
|
+
const onAdd = (entries) => {
|
|
18
|
+
const dirtyQueries = /* @__PURE__ */ new Set();
|
|
19
|
+
for (const [key, value] of entries) for (const q of queries) if (q.where(value)) {
|
|
20
|
+
const selected = q.select ? q.select(value) : value;
|
|
21
|
+
q.results.set(key, selected);
|
|
22
|
+
dirtyQueries.add(q);
|
|
23
|
+
}
|
|
24
|
+
runCallbacks(dirtyQueries);
|
|
25
|
+
};
|
|
26
|
+
const onUpdate = (entries) => {
|
|
27
|
+
const dirtyQueries = /* @__PURE__ */ new Set();
|
|
28
|
+
for (const [key, value] of entries) for (const q of queries) {
|
|
29
|
+
const matches = q.where(value);
|
|
30
|
+
const inResults = q.results.has(key);
|
|
31
|
+
if (matches && !inResults) {
|
|
32
|
+
const selected = q.select ? q.select(value) : value;
|
|
33
|
+
q.results.set(key, selected);
|
|
34
|
+
dirtyQueries.add(q);
|
|
35
|
+
} else if (!matches && inResults) {
|
|
36
|
+
q.results.delete(key);
|
|
37
|
+
dirtyQueries.add(q);
|
|
38
|
+
} else if (matches && inResults) {
|
|
39
|
+
const selected = q.select ? q.select(value) : value;
|
|
40
|
+
q.results.set(key, selected);
|
|
41
|
+
dirtyQueries.add(q);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
runCallbacks(dirtyQueries);
|
|
45
|
+
};
|
|
46
|
+
const onDelete = (keys) => {
|
|
47
|
+
const dirtyQueries = /* @__PURE__ */ new Set();
|
|
48
|
+
for (const key of keys) for (const q of queries) if (q.results.has(key)) {
|
|
49
|
+
q.results.delete(key);
|
|
50
|
+
dirtyQueries.add(q);
|
|
51
|
+
}
|
|
52
|
+
runCallbacks(dirtyQueries);
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
hooks: {
|
|
56
|
+
onInit: (s) => {
|
|
57
|
+
store = s;
|
|
58
|
+
for (const q of queries) hydrateQuery(q);
|
|
59
|
+
},
|
|
60
|
+
onDispose: () => {
|
|
61
|
+
queries.clear();
|
|
62
|
+
store = null;
|
|
63
|
+
},
|
|
64
|
+
onAdd,
|
|
65
|
+
onUpdate,
|
|
66
|
+
onDelete
|
|
67
|
+
},
|
|
68
|
+
methods: { query: (config) => {
|
|
69
|
+
const query = {
|
|
70
|
+
where: config.where,
|
|
71
|
+
...config.select && { select: config.select },
|
|
72
|
+
...config.order && { order: config.order },
|
|
73
|
+
results: /* @__PURE__ */ new Map(),
|
|
74
|
+
callbacks: /* @__PURE__ */ new Set()
|
|
75
|
+
};
|
|
76
|
+
queries.add(query);
|
|
77
|
+
hydrateQuery(query);
|
|
78
|
+
return {
|
|
79
|
+
results: () => {
|
|
80
|
+
if (query.order) {
|
|
81
|
+
const orderFn = query.order;
|
|
82
|
+
const sorted = Array.from(query.results).sort(([, a], [, b]) => orderFn(a, b));
|
|
83
|
+
return new Map(sorted);
|
|
84
|
+
} else return new Map(query.results);
|
|
85
|
+
},
|
|
86
|
+
onChange: (callback) => {
|
|
87
|
+
query.callbacks.add(callback);
|
|
88
|
+
return () => {
|
|
89
|
+
query.callbacks.delete(callback);
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
dispose: () => {
|
|
93
|
+
queries.delete(query);
|
|
94
|
+
query.callbacks.clear();
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
} }
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
export { queryPlugin };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { a as StoreSnapshot, t as Plugin } from "./store-CMzcvcsT.js";
|
|
2
|
+
import { Storage } from "unstorage";
|
|
3
|
+
|
|
4
|
+
//#region src/plugins/unstorage/plugin.d.ts
|
|
5
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
6
|
+
type UnstorageOnBeforeSet = (data: StoreSnapshot) => MaybePromise<StoreSnapshot>;
|
|
7
|
+
type UnstorageOnAfterGet = (data: StoreSnapshot) => MaybePromise<StoreSnapshot>;
|
|
8
|
+
type UnstorageConfig = {
|
|
9
|
+
debounceMs?: number;
|
|
10
|
+
pollIntervalMs?: number;
|
|
11
|
+
onBeforeSet?: UnstorageOnBeforeSet;
|
|
12
|
+
onAfterGet?: UnstorageOnAfterGet;
|
|
13
|
+
};
|
|
14
|
+
declare const unstoragePlugin: <T>(key: string, storage: Storage<StoreSnapshot>, config?: UnstorageConfig) => Plugin<T>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { type UnstorageConfig, unstoragePlugin };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
//#region src/plugins/unstorage/plugin.ts
|
|
2
|
+
const unstoragePlugin = (key, storage, config = {}) => {
|
|
3
|
+
const { debounceMs = 0, pollIntervalMs, onBeforeSet, onAfterGet } = config;
|
|
4
|
+
let debounceTimer = null;
|
|
5
|
+
let pollInterval = null;
|
|
6
|
+
let store = null;
|
|
7
|
+
const persistSnapshot = async () => {
|
|
8
|
+
if (!store) return;
|
|
9
|
+
const data = store.snapshot();
|
|
10
|
+
const persisted = onBeforeSet !== void 0 ? await onBeforeSet(data) : data;
|
|
11
|
+
await storage.set(key, persisted);
|
|
12
|
+
};
|
|
13
|
+
const schedulePersist = () => {
|
|
14
|
+
const runPersist = () => {
|
|
15
|
+
debounceTimer = null;
|
|
16
|
+
persistSnapshot();
|
|
17
|
+
};
|
|
18
|
+
if (debounceMs === 0) {
|
|
19
|
+
runPersist();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (debounceTimer !== null) clearTimeout(debounceTimer);
|
|
23
|
+
debounceTimer = setTimeout(runPersist, debounceMs);
|
|
24
|
+
};
|
|
25
|
+
const pollStorage = async () => {
|
|
26
|
+
if (!store) return;
|
|
27
|
+
const persisted = await storage.get(key);
|
|
28
|
+
if (!persisted) return;
|
|
29
|
+
const data = onAfterGet !== void 0 ? await onAfterGet(persisted) : persisted;
|
|
30
|
+
if (!data || !data.docs || data.docs.length === 0) return;
|
|
31
|
+
store.forwardClock(data.latestEventstamp);
|
|
32
|
+
store.begin((tx) => {
|
|
33
|
+
for (const doc of data.docs) tx.merge(doc);
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
return { hooks: {
|
|
37
|
+
onInit: async (s) => {
|
|
38
|
+
store = s;
|
|
39
|
+
await pollStorage();
|
|
40
|
+
if (pollIntervalMs !== void 0 && pollIntervalMs > 0) pollInterval = setInterval(() => {
|
|
41
|
+
pollStorage();
|
|
42
|
+
}, pollIntervalMs);
|
|
43
|
+
},
|
|
44
|
+
onDispose: () => {
|
|
45
|
+
if (debounceTimer !== null) {
|
|
46
|
+
clearTimeout(debounceTimer);
|
|
47
|
+
debounceTimer = null;
|
|
48
|
+
}
|
|
49
|
+
if (pollInterval !== null) {
|
|
50
|
+
clearInterval(pollInterval);
|
|
51
|
+
pollInterval = null;
|
|
52
|
+
}
|
|
53
|
+
store = null;
|
|
54
|
+
},
|
|
55
|
+
onAdd: () => {
|
|
56
|
+
schedulePersist();
|
|
57
|
+
},
|
|
58
|
+
onUpdate: () => {
|
|
59
|
+
schedulePersist();
|
|
60
|
+
},
|
|
61
|
+
onDelete: () => {
|
|
62
|
+
schedulePersist();
|
|
63
|
+
}
|
|
64
|
+
} };
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
export { unstoragePlugin };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
//#region src/value.d.ts
|
|
2
|
+
type EncodedValue<T> = {
|
|
3
|
+
"~value": T;
|
|
4
|
+
"~eventstamp": string;
|
|
5
|
+
};
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/record.d.ts
|
|
8
|
+
type EncodedRecord = {
|
|
9
|
+
[key: string]: EncodedValue<unknown> | EncodedRecord;
|
|
10
|
+
};
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/document.d.ts
|
|
13
|
+
type EncodedDocument = {
|
|
14
|
+
"~id": string;
|
|
15
|
+
"~data": EncodedValue<unknown> | EncodedRecord;
|
|
16
|
+
"~deletedAt": string | null;
|
|
17
|
+
};
|
|
18
|
+
declare const processDocument: (doc: EncodedDocument, process: (value: EncodedValue<unknown>) => EncodedValue<unknown>) => EncodedDocument;
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/transaction.d.ts
|
|
21
|
+
type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T;
|
|
22
|
+
type StorePutOptions = {
|
|
23
|
+
withId?: string;
|
|
24
|
+
};
|
|
25
|
+
type StoreSetTransaction<T> = {
|
|
26
|
+
add: (value: T, options?: StorePutOptions) => string;
|
|
27
|
+
update: (key: string, value: DeepPartial<T>) => void;
|
|
28
|
+
merge: (doc: EncodedDocument) => void;
|
|
29
|
+
del: (key: string) => void;
|
|
30
|
+
get: (key: string) => T | null;
|
|
31
|
+
rollback: () => void;
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/store.d.ts
|
|
35
|
+
/**
|
|
36
|
+
* Type constraint to prevent Promise returns from set callbacks.
|
|
37
|
+
* Transactions must be synchronous operations.
|
|
38
|
+
*/
|
|
39
|
+
type NotPromise<T> = T extends Promise<any> ? never : T;
|
|
40
|
+
/**
|
|
41
|
+
* Plugin lifecycle and event hooks.
|
|
42
|
+
* All hooks are optional except onInit and onDispose, which are required.
|
|
43
|
+
*/
|
|
44
|
+
type PluginHooks<T> = {
|
|
45
|
+
onInit: (store: Store<T>) => Promise<void> | void;
|
|
46
|
+
onDispose: () => Promise<void> | void;
|
|
47
|
+
onAdd?: (entries: ReadonlyArray<readonly [string, T]>) => void;
|
|
48
|
+
onUpdate?: (entries: ReadonlyArray<readonly [string, T]>) => void;
|
|
49
|
+
onDelete?: (keys: ReadonlyArray<string>) => void;
|
|
50
|
+
};
|
|
51
|
+
type PluginMethods = Record<string, (...args: any[]) => any>;
|
|
52
|
+
type Plugin<T, M extends PluginMethods = {}> = {
|
|
53
|
+
hooks: PluginHooks<T>;
|
|
54
|
+
methods?: M;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Complete persistent state of a store.
|
|
58
|
+
* Contains all encoded documents (including deleted ones with ~deletedAt metadata)
|
|
59
|
+
* and the latest eventstamp for clock synchronization during merges.
|
|
60
|
+
*/
|
|
61
|
+
type StoreSnapshot = {
|
|
62
|
+
docs: EncodedDocument[];
|
|
63
|
+
latestEventstamp: string;
|
|
64
|
+
};
|
|
65
|
+
type Store<T, Extended = {}> = {
|
|
66
|
+
get: (key: string) => T | null;
|
|
67
|
+
begin: <R = void>(callback: (tx: StoreSetTransaction<T>) => NotPromise<R>, opts?: {
|
|
68
|
+
silent?: boolean;
|
|
69
|
+
}) => NotPromise<R>;
|
|
70
|
+
add: (value: T, options?: StorePutOptions) => string;
|
|
71
|
+
update: (key: string, value: DeepPartial<T>) => void;
|
|
72
|
+
del: (key: string) => void;
|
|
73
|
+
entries: () => IterableIterator<readonly [string, T]>;
|
|
74
|
+
snapshot: () => StoreSnapshot;
|
|
75
|
+
use: <M extends PluginMethods>(plugin: Plugin<T, M>) => Store<T, Extended & M>;
|
|
76
|
+
init: () => Promise<Store<T, Extended>>;
|
|
77
|
+
dispose: () => Promise<void>;
|
|
78
|
+
latestEventstamp: () => string;
|
|
79
|
+
forwardClock: (eventstamp: string) => void;
|
|
80
|
+
} & Extended;
|
|
81
|
+
declare const createStore: <T>(config?: {
|
|
82
|
+
getId?: () => string;
|
|
83
|
+
}) => Store<T, {}>;
|
|
84
|
+
//#endregion
|
|
85
|
+
export { StoreSnapshot as a, processDocument as c, Store as i, PluginHooks as n, createStore as o, PluginMethods as r, EncodedDocument as s, Plugin as t };
|
package/package.json
CHANGED
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@byearlybird/starling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./plugin-query": {
|
|
15
|
+
"types": "./dist/plugin-query.d.ts",
|
|
16
|
+
"import": "./dist/plugin-query.js",
|
|
17
|
+
"default": "./dist/plugin-query.js"
|
|
18
|
+
},
|
|
19
|
+
"./plugin-unstorage": {
|
|
20
|
+
"types": "./dist/plugin-unstorage.d.ts",
|
|
21
|
+
"import": "./dist/plugin-unstorage.js",
|
|
22
|
+
"default": "./dist/plugin-unstorage.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
8
25
|
"files": [
|
|
9
26
|
"dist"
|
|
10
27
|
],
|
|
@@ -12,6 +29,14 @@
|
|
|
12
29
|
"build": "bun run build.ts",
|
|
13
30
|
"prepublishOnly": "bun run build.ts"
|
|
14
31
|
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"unstorage": "^1.17.1"
|
|
34
|
+
},
|
|
35
|
+
"peerDependenciesMeta": {
|
|
36
|
+
"unstorage": {
|
|
37
|
+
"optional": true
|
|
38
|
+
}
|
|
39
|
+
},
|
|
15
40
|
"publishConfig": {
|
|
16
41
|
"access": "public"
|
|
17
42
|
}
|