@lunora/client 1.0.0-alpha.2 → 1.0.0-alpha.21
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/LICENSE.md +6 -0
- package/README.md +2 -0
- package/__assets__/package-og.svg +1 -1
- package/dist/auth/index.d.mts +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/index.d.mts +144 -24
- package/dist/index.d.ts +144 -24
- package/dist/index.mjs +10 -9
- package/dist/packem_shared/CONFLICT_ERROR_CODE-B8gQ8tyU.mjs +33 -0
- package/dist/packem_shared/{DEFAULT_MAX_BUFFER-BDkqO5PW.mjs → DEFAULT_MAX_BUFFER-7hFnzNk9.mjs} +6 -3
- package/dist/packem_shared/{LunoraClient-UiULzH_1.mjs → LunoraClient-kXpHNyaE.mjs} +1562 -267
- package/dist/packem_shared/OfflineQueue-GGYJRmhF.mjs +1 -0
- package/dist/packem_shared/SubscriptionRegistry-DjGKZsqq.mjs +1 -0
- package/dist/packem_shared/{applyDelta-4jFGTPA3.mjs → applyDelta-CRKZ1PBt.mjs} +21 -1
- package/dist/packem_shared/createInMemoryPersistence-DZ2VHWgm.mjs +79 -0
- package/dist/packem_shared/{createInMemoryQueryCache-B1PQ9Twl.mjs → createInMemoryQueryCache-DiaGZkA2.mjs} +25 -51
- package/dist/packem_shared/createLocalStore-jRoqmazl.mjs +2 -0
- package/dist/packem_shared/createMutatorRunner-BETvCd0p.mjs +31 -0
- package/dist/packem_shared/{createServerClient-BjZc3gD8.mjs → createServerClient-DF-3mLmb.mjs} +1 -1
- package/dist/packem_shared/idb-utility-DrSVX43Q.mjs +48 -0
- package/dist/packem_shared/local-store-BveBeFEo.mjs +106 -0
- package/dist/packem_shared/{lunora-client.d-DGvyuJ_p.d.mts → lunora-client.d-BYkEjCEJ.d.mts} +1001 -58
- package/dist/packem_shared/{lunora-client.d-DGvyuJ_p.d.ts → lunora-client.d-BYkEjCEJ.d.ts} +1001 -58
- package/dist/packem_shared/{OfflineQueue-D5p_QgF_.mjs → offline-queue-B9vfdSqp.mjs} +49 -6
- package/dist/packem_shared/{preload.d-BoDmFqSG.d.ts → preload.d-B-vyHnml.d.ts} +1 -1
- package/dist/packem_shared/{preload.d-dSaRMuhL.d.mts → preload.d-DrfuisCE.d.mts} +1 -1
- package/dist/packem_shared/subscription-BjynOXCU.mjs +68 -0
- package/dist/query/index.d.mts +2 -2
- package/dist/query/index.d.ts +2 -2
- package/dist/ssr/index.d.mts +3 -3
- package/dist/ssr/index.d.ts +3 -3
- package/dist/ssr/index.mjs +1 -1
- package/package.json +5 -2
- package/dist/packem_shared/CONFLICT_ERROR_CODE-aUdVbEDw.mjs +0 -4
- package/dist/packem_shared/SubscriptionRegistry-B-Qx_Gux.mjs +0 -26
- package/dist/packem_shared/createInMemoryPersistence-CW82inU5.mjs +0 -105
- package/dist/packem_shared/createLocalStore-DSUfoLqY.mjs +0 -36
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
const isStaleVersion = (current, stamped) => current !== void 0 && stamped !== current;
|
|
2
|
+
|
|
1
3
|
let idCounter = 0;
|
|
2
4
|
const nextId = () => {
|
|
3
5
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
4
6
|
return crypto.randomUUID();
|
|
5
7
|
}
|
|
6
8
|
idCounter += 1;
|
|
7
|
-
|
|
9
|
+
const entropy = typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function" ? [...crypto.getRandomValues(new Uint8Array(8))].map((byte) => byte.toString(16).padStart(2, "0")).join("") : (
|
|
10
|
+
// eslint-disable-next-line sonarjs/pseudo-random -- non-cryptographic uniqueness entropy, not a security token; only reached when neither crypto.randomUUID nor crypto.getRandomValues exists
|
|
11
|
+
Math.random().toString(16).slice(2, 12)
|
|
12
|
+
);
|
|
13
|
+
return `m_${Date.now().toString(36)}_${idCounter.toString(36)}_${entropy}`;
|
|
8
14
|
};
|
|
9
15
|
const reportPersistenceError = (handler, operation, error, mutationId) => {
|
|
10
16
|
if (handler) {
|
|
@@ -19,12 +25,19 @@ class OfflineQueue {
|
|
|
19
25
|
maxItems;
|
|
20
26
|
onPersistenceError;
|
|
21
27
|
persistence;
|
|
28
|
+
onEvict;
|
|
29
|
+
onSizeChange;
|
|
30
|
+
/** App/schema version stamped on persisted writes; mismatched records are purged on hydrate. */
|
|
31
|
+
version;
|
|
22
32
|
items = [];
|
|
23
|
-
constructor(options = {},
|
|
33
|
+
constructor(options = {}, deps = {}) {
|
|
24
34
|
this.maxItems = options.maxItems ?? 1e3;
|
|
25
35
|
this.queueBeforeFirstConnect = options.queueBeforeFirstConnect ?? false;
|
|
26
36
|
this.onPersistenceError = options.onPersistenceError;
|
|
27
|
-
this.persistence = persistence;
|
|
37
|
+
this.persistence = deps.persistence;
|
|
38
|
+
this.onEvict = deps.onEvict;
|
|
39
|
+
this.onSizeChange = deps.onSizeChange;
|
|
40
|
+
this.version = deps.version;
|
|
28
41
|
}
|
|
29
42
|
get size() {
|
|
30
43
|
return this.items.length;
|
|
@@ -33,7 +46,14 @@ class OfflineQueue {
|
|
|
33
46
|
const item = entry;
|
|
34
47
|
item.id ??= nextId();
|
|
35
48
|
this.items.push(item);
|
|
36
|
-
this.persistence?.append({
|
|
49
|
+
this.persistence?.append({
|
|
50
|
+
args: item.args,
|
|
51
|
+
functionPath: item.functionPath,
|
|
52
|
+
id: item.id,
|
|
53
|
+
identity: item.identity,
|
|
54
|
+
shardKey: item.shardKey,
|
|
55
|
+
...this.version === void 0 ? {} : { version: this.version }
|
|
56
|
+
}).catch((error) => {
|
|
37
57
|
reportPersistenceError(this.onPersistenceError, "append", error, item.id);
|
|
38
58
|
});
|
|
39
59
|
while (this.items.length > this.maxItems) {
|
|
@@ -47,8 +67,10 @@ class OfflineQueue {
|
|
|
47
67
|
const error = new Error("offline queue overflow");
|
|
48
68
|
error.code = "OFFLINE_QUEUE_OVERFLOW";
|
|
49
69
|
dropped.reject(error);
|
|
70
|
+
this.onEvict?.(dropped, error);
|
|
50
71
|
}
|
|
51
72
|
}
|
|
73
|
+
this.notifySize();
|
|
52
74
|
}
|
|
53
75
|
/**
|
|
54
76
|
* Restore mutations persisted in a prior session and re-queue them in FIFO
|
|
@@ -62,12 +84,24 @@ class OfflineQueue {
|
|
|
62
84
|
if (!this.persistence) {
|
|
63
85
|
return [];
|
|
64
86
|
}
|
|
65
|
-
|
|
87
|
+
let persisted;
|
|
88
|
+
try {
|
|
89
|
+
persisted = await this.persistence.load();
|
|
90
|
+
} catch (error) {
|
|
91
|
+
reportPersistenceError(this.onPersistenceError, "load", error);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
66
94
|
const shardKeys = /* @__PURE__ */ new Set();
|
|
67
95
|
for (const mutation of persisted) {
|
|
68
96
|
if (this.items.some((item) => item.id === mutation.id)) {
|
|
69
97
|
continue;
|
|
70
98
|
}
|
|
99
|
+
if (isStaleVersion(this.version, mutation.version)) {
|
|
100
|
+
this.persistence.remove(mutation.id).catch((error) => {
|
|
101
|
+
reportPersistenceError(this.onPersistenceError, "remove", error, mutation.id);
|
|
102
|
+
});
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
71
105
|
this.items.push({
|
|
72
106
|
args: mutation.args,
|
|
73
107
|
functionPath: mutation.functionPath,
|
|
@@ -79,6 +113,7 @@ class OfflineQueue {
|
|
|
79
113
|
});
|
|
80
114
|
shardKeys.add(mutation.shardKey);
|
|
81
115
|
}
|
|
116
|
+
this.notifySize();
|
|
82
117
|
return [...shardKeys];
|
|
83
118
|
}
|
|
84
119
|
/**
|
|
@@ -91,6 +126,7 @@ class OfflineQueue {
|
|
|
91
126
|
if (!predicate) {
|
|
92
127
|
const drained2 = [...this.items];
|
|
93
128
|
this.items.length = 0;
|
|
129
|
+
this.notifySize();
|
|
94
130
|
return drained2;
|
|
95
131
|
}
|
|
96
132
|
const drained = [];
|
|
@@ -100,6 +136,7 @@ class OfflineQueue {
|
|
|
100
136
|
}
|
|
101
137
|
this.items.length = 0;
|
|
102
138
|
this.items.push(...kept);
|
|
139
|
+
this.notifySize();
|
|
103
140
|
return drained;
|
|
104
141
|
}
|
|
105
142
|
/**
|
|
@@ -113,6 +150,7 @@ class OfflineQueue {
|
|
|
113
150
|
return;
|
|
114
151
|
}
|
|
115
152
|
this.items.unshift(...items);
|
|
153
|
+
this.notifySize();
|
|
116
154
|
}
|
|
117
155
|
clear() {
|
|
118
156
|
for (const item of this.items) {
|
|
@@ -121,7 +159,12 @@ class OfflineQueue {
|
|
|
121
159
|
item.reject(error);
|
|
122
160
|
}
|
|
123
161
|
this.items.length = 0;
|
|
162
|
+
this.notifySize();
|
|
163
|
+
}
|
|
164
|
+
/** Notify the size observer (the client's pending-sync count) after any change. */
|
|
165
|
+
notifySize() {
|
|
166
|
+
this.onSizeChange?.(this.items.length);
|
|
124
167
|
}
|
|
125
168
|
}
|
|
126
169
|
|
|
127
|
-
export { OfflineQueue, nextId, reportPersistenceError };
|
|
170
|
+
export { OfflineQueue as O, isStaleVersion as i, nextId as n, reportPersistenceError as r };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as FunctionReference, L as LunoraClient, A as ArgsOf, R as ReturnOf, P as Preloaded } from "./lunora-client.d-
|
|
1
|
+
import { F as FunctionReference, L as LunoraClient, A as ArgsOf, R as ReturnOf, P as Preloaded } from "./lunora-client.d-BYkEjCEJ.js";
|
|
2
2
|
/**
|
|
3
3
|
* Run a query once on the server (during SSR) and capture its result in a
|
|
4
4
|
* serializable {@link Preloaded} token. Embed the token in the rendered HTML and
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as FunctionReference, L as LunoraClient, A as ArgsOf, R as ReturnOf, P as Preloaded } from "./lunora-client.d-
|
|
1
|
+
import { F as FunctionReference, L as LunoraClient, A as ArgsOf, R as ReturnOf, P as Preloaded } from "./lunora-client.d-BYkEjCEJ.mjs";
|
|
2
2
|
/**
|
|
3
3
|
* Run a query once on the server (during SSR) and capture its result in a
|
|
4
4
|
* serializable {@link Preloaded} token. Embed the token in the rendered HTML and
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const compareKeys = (a, b) => {
|
|
2
|
+
if (a < b) {
|
|
3
|
+
return -1;
|
|
4
|
+
}
|
|
5
|
+
return a > b ? 1 : 0;
|
|
6
|
+
};
|
|
7
|
+
const stableStringify = (value) => {
|
|
8
|
+
if (value === void 0) {
|
|
9
|
+
return "null";
|
|
10
|
+
}
|
|
11
|
+
if (typeof value === "bigint") {
|
|
12
|
+
throw new TypeError("stableStringify: cannot use a bigint in a cache key (query/subscription/shape args) — pass it as a string");
|
|
13
|
+
}
|
|
14
|
+
if (value === null || typeof value !== "object") {
|
|
15
|
+
return JSON.stringify(value);
|
|
16
|
+
}
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
19
|
+
}
|
|
20
|
+
const proto = Object.getPrototypeOf(value);
|
|
21
|
+
if (proto !== null && proto !== Object.prototype) {
|
|
22
|
+
const name = value.constructor?.name ?? "value";
|
|
23
|
+
throw new TypeError(
|
|
24
|
+
`stableStringify: cannot use a ${name} in a cache key (query/subscription/shape args) — only plain objects, arrays, and JSON primitives are supported`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
const record = value;
|
|
28
|
+
const keys = Object.keys(record).toSorted(compareKeys);
|
|
29
|
+
const parts = [];
|
|
30
|
+
for (const key of keys) {
|
|
31
|
+
const raw = record[key];
|
|
32
|
+
if (raw === void 0) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
parts.push(`${JSON.stringify(key)}:${stableStringify(raw)}`);
|
|
36
|
+
}
|
|
37
|
+
return `{${parts.join(",")}}`;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
class SubscriptionRegistry {
|
|
41
|
+
static key(functionPath, args, shardKey) {
|
|
42
|
+
return `${functionPath}::${stableStringify(args)}::${shardKey ?? ""}`;
|
|
43
|
+
}
|
|
44
|
+
byKey = /* @__PURE__ */ new Map();
|
|
45
|
+
byId = /* @__PURE__ */ new Map();
|
|
46
|
+
get(key) {
|
|
47
|
+
return this.byKey.get(key);
|
|
48
|
+
}
|
|
49
|
+
getById(id) {
|
|
50
|
+
return this.byId.get(id);
|
|
51
|
+
}
|
|
52
|
+
add(state) {
|
|
53
|
+
this.byKey.set(SubscriptionRegistry.key(state.fn.__lunoraRef, state.args, state.shardKey), state);
|
|
54
|
+
this.byId.set(state.id, state);
|
|
55
|
+
}
|
|
56
|
+
remove(state) {
|
|
57
|
+
const key = SubscriptionRegistry.key(state.fn.__lunoraRef, state.args, state.shardKey);
|
|
58
|
+
if (this.byKey.get(key) === state) {
|
|
59
|
+
this.byKey.delete(key);
|
|
60
|
+
}
|
|
61
|
+
this.byId.delete(state.id);
|
|
62
|
+
}
|
|
63
|
+
all() {
|
|
64
|
+
return [...this.byKey.values()];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { SubscriptionRegistry as S, stableStringify as s };
|
package/dist/query/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as SubscriptionError, F as FunctionReference, A as ArgsOf, R as ReturnOf, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-
|
|
2
|
-
export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-
|
|
1
|
+
import { S as SubscriptionError, F as FunctionReference, A as ArgsOf, R as ReturnOf, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-BYkEjCEJ.mjs";
|
|
2
|
+
export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-BYkEjCEJ.mjs";
|
|
3
3
|
import '@lunora/runtime';
|
|
4
4
|
/**
|
|
5
5
|
* The sentinel a framework adapter resolves its reactive args to when it wants
|
package/dist/query/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as SubscriptionError, F as FunctionReference, A as ArgsOf, R as ReturnOf, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-
|
|
2
|
-
export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-
|
|
1
|
+
import { S as SubscriptionError, F as FunctionReference, A as ArgsOf, R as ReturnOf, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-BYkEjCEJ.js";
|
|
2
|
+
export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-BYkEjCEJ.js";
|
|
3
3
|
import '@lunora/runtime';
|
|
4
4
|
/**
|
|
5
5
|
* The sentinel a framework adapter resolves its reactive args to when it wants
|
package/dist/ssr/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-
|
|
2
|
-
export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-
|
|
3
|
-
export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-
|
|
1
|
+
import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-BYkEjCEJ.mjs";
|
|
2
|
+
export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-BYkEjCEJ.mjs";
|
|
3
|
+
export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-DrfuisCE.mjs";
|
|
4
4
|
import '@lunora/runtime';
|
|
5
5
|
/**
|
|
6
6
|
* Structural shape of a better-auth `getSession` call's resolved value.
|
package/dist/ssr/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-
|
|
2
|
-
export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-
|
|
3
|
-
export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-
|
|
1
|
+
import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-BYkEjCEJ.js";
|
|
2
|
+
export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-BYkEjCEJ.js";
|
|
3
|
+
export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-B-vyHnml.js";
|
|
4
4
|
import '@lunora/runtime';
|
|
5
5
|
/**
|
|
6
6
|
* Structural shape of a better-auth `getSession` call's resolved value.
|
package/dist/ssr/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { getServerSession } from '../packem_shared/getServerSession-8jXewqxd.mjs';
|
|
2
2
|
export { deserializePreloaded, serializePreloaded } from '../packem_shared/deserializePreloaded-C0eJTY_W.mjs';
|
|
3
|
-
export { createServerClient } from '../packem_shared/createServerClient-
|
|
3
|
+
export { createServerClient } from '../packem_shared/createServerClient-DF-3mLmb.mjs';
|
|
4
4
|
export { preloadQuery, preloadedQueryResult } from '../packem_shared/preloadQuery-lobFkD2Z.mjs';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/client",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.21",
|
|
4
4
|
"description": "Lunora browser SDK: WebSocket transport, optimistic updates, and an offline mutation queue",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"directory": "packages/client"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
|
-
"dist",
|
|
28
|
+
"./dist",
|
|
29
29
|
"README.md",
|
|
30
30
|
"LICENSE.md",
|
|
31
31
|
"__assets__"
|
|
@@ -61,6 +61,9 @@
|
|
|
61
61
|
"publishConfig": {
|
|
62
62
|
"access": "public"
|
|
63
63
|
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@lunora/errors": "1.0.0-alpha.4"
|
|
66
|
+
},
|
|
64
67
|
"engines": {
|
|
65
68
|
"node": "^22.15.0 || >=24.11.0"
|
|
66
69
|
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
class SubscriptionRegistry {
|
|
2
|
-
static key(functionPath, args, shardKey) {
|
|
3
|
-
return `${functionPath}::${JSON.stringify(args)}::${shardKey ?? ""}`;
|
|
4
|
-
}
|
|
5
|
-
byKey = /* @__PURE__ */ new Map();
|
|
6
|
-
byId = /* @__PURE__ */ new Map();
|
|
7
|
-
get(key) {
|
|
8
|
-
return this.byKey.get(key);
|
|
9
|
-
}
|
|
10
|
-
getById(id) {
|
|
11
|
-
return this.byId.get(id);
|
|
12
|
-
}
|
|
13
|
-
add(state) {
|
|
14
|
-
this.byKey.set(SubscriptionRegistry.key(state.fn.__lunoraRef, state.args, state.shardKey), state);
|
|
15
|
-
this.byId.set(state.id, state);
|
|
16
|
-
}
|
|
17
|
-
remove(state) {
|
|
18
|
-
this.byKey.delete(SubscriptionRegistry.key(state.fn.__lunoraRef, state.args, state.shardKey));
|
|
19
|
-
this.byId.delete(state.id);
|
|
20
|
-
}
|
|
21
|
-
all() {
|
|
22
|
-
return [...this.byKey.values()];
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export { SubscriptionRegistry };
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
const createInMemoryPersistence = () => {
|
|
2
|
-
const entries = /* @__PURE__ */ new Map();
|
|
3
|
-
const clone = (mutation) => {
|
|
4
|
-
return {
|
|
5
|
-
args: { ...mutation.args },
|
|
6
|
-
functionPath: mutation.functionPath,
|
|
7
|
-
id: mutation.id,
|
|
8
|
-
identity: mutation.identity,
|
|
9
|
-
shardKey: mutation.shardKey
|
|
10
|
-
};
|
|
11
|
-
};
|
|
12
|
-
return {
|
|
13
|
-
append: (mutation) => {
|
|
14
|
-
entries.set(mutation.id, clone(mutation));
|
|
15
|
-
return Promise.resolve();
|
|
16
|
-
},
|
|
17
|
-
clear: () => {
|
|
18
|
-
entries.clear();
|
|
19
|
-
return Promise.resolve();
|
|
20
|
-
},
|
|
21
|
-
load: () => Promise.resolve([...entries.values()].map((mutation) => clone(mutation))),
|
|
22
|
-
remove: (id) => {
|
|
23
|
-
entries.delete(id);
|
|
24
|
-
return Promise.resolve();
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
const DEFAULT_DATABASE = "lunora";
|
|
29
|
-
const DEFAULT_STORE = "offline-mutations";
|
|
30
|
-
const ID_INDEX = "by_id";
|
|
31
|
-
const promisifyRequest = (request) => new Promise((resolve, reject) => {
|
|
32
|
-
request.addEventListener("success", () => {
|
|
33
|
-
resolve(request.result);
|
|
34
|
-
});
|
|
35
|
-
request.addEventListener("error", () => {
|
|
36
|
-
reject(request.error ?? new Error("IndexedDB request failed"));
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
const createIndexedDbPersistence = (options = {}) => {
|
|
40
|
-
const factory = options.indexedDB ?? (typeof indexedDB === "undefined" ? void 0 : indexedDB);
|
|
41
|
-
if (!factory) {
|
|
42
|
-
throw new Error("createIndexedDbPersistence: no IndexedDB available — pass `indexedDB` or use createInMemoryPersistence()");
|
|
43
|
-
}
|
|
44
|
-
const databaseName = options.databaseName ?? DEFAULT_DATABASE;
|
|
45
|
-
const storeName = options.storeName ?? DEFAULT_STORE;
|
|
46
|
-
let databasePromise;
|
|
47
|
-
const openDatabase = () => {
|
|
48
|
-
if (databasePromise) {
|
|
49
|
-
return databasePromise;
|
|
50
|
-
}
|
|
51
|
-
databasePromise = new Promise((resolve, reject) => {
|
|
52
|
-
const request = factory.open(databaseName, 1);
|
|
53
|
-
request.addEventListener("upgradeneeded", () => {
|
|
54
|
-
const database = request.result;
|
|
55
|
-
if (!database.objectStoreNames.contains(storeName)) {
|
|
56
|
-
const store = database.createObjectStore(storeName, { autoIncrement: true });
|
|
57
|
-
store.createIndex(ID_INDEX, "id", { unique: true });
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
request.addEventListener("success", () => {
|
|
61
|
-
resolve(request.result);
|
|
62
|
-
});
|
|
63
|
-
request.addEventListener("error", () => {
|
|
64
|
-
reject(request.error ?? new Error("IndexedDB open failed"));
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
return databasePromise;
|
|
68
|
-
};
|
|
69
|
-
const withStore = async (mode, run) => {
|
|
70
|
-
const database = await openDatabase();
|
|
71
|
-
const transaction = database.transaction(storeName, mode);
|
|
72
|
-
const result = await run(transaction.objectStore(storeName));
|
|
73
|
-
await new Promise((resolve, reject) => {
|
|
74
|
-
transaction.addEventListener("complete", () => {
|
|
75
|
-
resolve();
|
|
76
|
-
});
|
|
77
|
-
transaction.addEventListener("error", () => {
|
|
78
|
-
reject(transaction.error ?? new Error("IndexedDB transaction failed"));
|
|
79
|
-
});
|
|
80
|
-
transaction.addEventListener("abort", () => {
|
|
81
|
-
reject(transaction.error ?? new Error("IndexedDB transaction aborted"));
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
return result;
|
|
85
|
-
};
|
|
86
|
-
return {
|
|
87
|
-
append: async (mutation) => {
|
|
88
|
-
await withStore("readwrite", (store) => promisifyRequest(store.add(mutation)));
|
|
89
|
-
},
|
|
90
|
-
clear: async () => {
|
|
91
|
-
await withStore("readwrite", (store) => promisifyRequest(store.clear()));
|
|
92
|
-
},
|
|
93
|
-
load: async () => withStore("readonly", (store) => promisifyRequest(store.getAll())),
|
|
94
|
-
remove: async (id) => {
|
|
95
|
-
await withStore("readwrite", async (store) => {
|
|
96
|
-
const key = await promisifyRequest(store.index(ID_INDEX).getKey(id));
|
|
97
|
-
if (key !== void 0) {
|
|
98
|
-
await promisifyRequest(store.delete(key));
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
export { createInMemoryPersistence, createIndexedDbPersistence };
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
const createLocalStore = (subscriptions, shardKey, write, stableStringify) => {
|
|
2
|
-
const rollbacks = [];
|
|
3
|
-
const findState = (functionRef, argsKey) => {
|
|
4
|
-
for (const state of subscriptions.all()) {
|
|
5
|
-
if (state.fn.__lunoraRef === functionRef && state.shardKey === shardKey && state.argsKey === argsKey) {
|
|
6
|
-
return state;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
return void 0;
|
|
10
|
-
};
|
|
11
|
-
const store = {
|
|
12
|
-
getAllQueries: (function_) => {
|
|
13
|
-
const matches = [];
|
|
14
|
-
for (const state of subscriptions.all()) {
|
|
15
|
-
if (state.fn.__lunoraRef === function_.__lunoraRef && state.shardKey === shardKey) {
|
|
16
|
-
matches.push({ args: state.args, value: state.lastValue });
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return matches;
|
|
20
|
-
},
|
|
21
|
-
getQuery: (function_, args) => {
|
|
22
|
-
const state = findState(function_.__lunoraRef, stableStringify(args ?? {}));
|
|
23
|
-
return state?.lastValue;
|
|
24
|
-
},
|
|
25
|
-
setQuery: (function_, args, value) => {
|
|
26
|
-
const state = findState(function_.__lunoraRef, stableStringify(args ?? {}));
|
|
27
|
-
if (!state) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
rollbacks.push(write(state, value));
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
return { rollbacks, store };
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export { createLocalStore };
|