@lunora/client 1.0.0-alpha.1 → 1.0.0-alpha.10
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/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 +109 -5
- package/dist/index.d.ts +109 -5
- package/dist/index.mjs +8 -7
- package/dist/packem_shared/{LunoraClient-B00f7VUM.mjs → LunoraClient-CgZ6FhKP.mjs} +741 -185
- package/dist/packem_shared/OfflineQueue-BI0FNNvc.mjs +1 -0
- package/dist/packem_shared/SubscriptionRegistry-Dn-7k7eo.mjs +1 -0
- package/dist/packem_shared/createLocalStore-IOur0jHF.mjs +1 -0
- package/dist/packem_shared/createMutatorRunner-BETvCd0p.mjs +31 -0
- package/dist/packem_shared/{createServerClient-DyFIHnWZ.mjs → createServerClient-BxkNcRlR.mjs} +1 -1
- package/dist/packem_shared/local-store-BNgN3Dw3.mjs +111 -0
- package/dist/packem_shared/{lunora-client.d-DGvyuJ_p.d.mts → lunora-client.d-B5vWSgvD.d.mts} +637 -38
- package/dist/packem_shared/{lunora-client.d-DGvyuJ_p.d.ts → lunora-client.d-B5vWSgvD.d.ts} +637 -38
- package/dist/packem_shared/{OfflineQueue-D5p_QgF_.mjs → offline-queue-7Wc4onA0.mjs} +42 -5
- package/dist/packem_shared/{preload.d-dSaRMuhL.d.mts → preload.d-3XJD-2hM.d.mts} +1 -1
- package/dist/packem_shared/{preload.d-BoDmFqSG.d.ts → preload.d-CKZR675M.d.ts} +1 -1
- package/dist/packem_shared/subscription-C1Jy7HiF.mjs +55 -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 +2 -2
- package/package.json +2 -2
- package/dist/packem_shared/SubscriptionRegistry-B-Qx_Gux.mjs +0 -26
- package/dist/packem_shared/createLocalStore-DSUfoLqY.mjs +0 -36
- /package/dist/packem_shared/{createStream-BDkqO5PW.mjs → DEFAULT_MAX_BUFFER-BDkqO5PW.mjs} +0 -0
- /package/dist/packem_shared/{createIndexedDbPersistence-CW82inU5.mjs → createInMemoryPersistence-CW82inU5.mjs} +0 -0
- /package/dist/packem_shared/{createIndexedDbQueryCache-B1PQ9Twl.mjs → createInMemoryQueryCache-B1PQ9Twl.mjs} +0 -0
- /package/dist/packem_shared/{serializePreloaded-C0eJTY_W.mjs → deserializePreloaded-C0eJTY_W.mjs} +0 -0
|
@@ -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
|
|
@@ -68,6 +90,12 @@ class OfflineQueue {
|
|
|
68
90
|
if (this.items.some((item) => item.id === mutation.id)) {
|
|
69
91
|
continue;
|
|
70
92
|
}
|
|
93
|
+
if (isStaleVersion(this.version, mutation.version)) {
|
|
94
|
+
this.persistence.remove(mutation.id).catch((error) => {
|
|
95
|
+
reportPersistenceError(this.onPersistenceError, "remove", error, mutation.id);
|
|
96
|
+
});
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
71
99
|
this.items.push({
|
|
72
100
|
args: mutation.args,
|
|
73
101
|
functionPath: mutation.functionPath,
|
|
@@ -79,6 +107,7 @@ class OfflineQueue {
|
|
|
79
107
|
});
|
|
80
108
|
shardKeys.add(mutation.shardKey);
|
|
81
109
|
}
|
|
110
|
+
this.notifySize();
|
|
82
111
|
return [...shardKeys];
|
|
83
112
|
}
|
|
84
113
|
/**
|
|
@@ -91,6 +120,7 @@ class OfflineQueue {
|
|
|
91
120
|
if (!predicate) {
|
|
92
121
|
const drained2 = [...this.items];
|
|
93
122
|
this.items.length = 0;
|
|
123
|
+
this.notifySize();
|
|
94
124
|
return drained2;
|
|
95
125
|
}
|
|
96
126
|
const drained = [];
|
|
@@ -100,6 +130,7 @@ class OfflineQueue {
|
|
|
100
130
|
}
|
|
101
131
|
this.items.length = 0;
|
|
102
132
|
this.items.push(...kept);
|
|
133
|
+
this.notifySize();
|
|
103
134
|
return drained;
|
|
104
135
|
}
|
|
105
136
|
/**
|
|
@@ -113,6 +144,7 @@ class OfflineQueue {
|
|
|
113
144
|
return;
|
|
114
145
|
}
|
|
115
146
|
this.items.unshift(...items);
|
|
147
|
+
this.notifySize();
|
|
116
148
|
}
|
|
117
149
|
clear() {
|
|
118
150
|
for (const item of this.items) {
|
|
@@ -121,7 +153,12 @@ class OfflineQueue {
|
|
|
121
153
|
item.reject(error);
|
|
122
154
|
}
|
|
123
155
|
this.items.length = 0;
|
|
156
|
+
this.notifySize();
|
|
157
|
+
}
|
|
158
|
+
/** Notify the size observer (the client's pending-sync count) after any change. */
|
|
159
|
+
notifySize() {
|
|
160
|
+
this.onSizeChange?.(this.items.length);
|
|
124
161
|
}
|
|
125
162
|
}
|
|
126
163
|
|
|
127
|
-
export { OfflineQueue, nextId, reportPersistenceError };
|
|
164
|
+
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-B5vWSgvD.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
|
|
@@ -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-B5vWSgvD.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
|
|
@@ -0,0 +1,55 @@
|
|
|
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 (value === null || typeof value !== "object") {
|
|
12
|
+
return JSON.stringify(value);
|
|
13
|
+
}
|
|
14
|
+
if (Array.isArray(value)) {
|
|
15
|
+
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
16
|
+
}
|
|
17
|
+
const record = value;
|
|
18
|
+
const keys = Object.keys(record).toSorted(compareKeys);
|
|
19
|
+
const parts = [];
|
|
20
|
+
for (const key of keys) {
|
|
21
|
+
const raw = record[key];
|
|
22
|
+
if (raw === void 0) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
parts.push(`${JSON.stringify(key)}:${stableStringify(raw)}`);
|
|
26
|
+
}
|
|
27
|
+
return `{${parts.join(",")}}`;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
class SubscriptionRegistry {
|
|
31
|
+
static key(functionPath, args, shardKey) {
|
|
32
|
+
return `${functionPath}::${stableStringify(args)}::${shardKey ?? ""}`;
|
|
33
|
+
}
|
|
34
|
+
byKey = /* @__PURE__ */ new Map();
|
|
35
|
+
byId = /* @__PURE__ */ new Map();
|
|
36
|
+
get(key) {
|
|
37
|
+
return this.byKey.get(key);
|
|
38
|
+
}
|
|
39
|
+
getById(id) {
|
|
40
|
+
return this.byId.get(id);
|
|
41
|
+
}
|
|
42
|
+
add(state) {
|
|
43
|
+
this.byKey.set(SubscriptionRegistry.key(state.fn.__lunoraRef, state.args, state.shardKey), state);
|
|
44
|
+
this.byId.set(state.id, state);
|
|
45
|
+
}
|
|
46
|
+
remove(state) {
|
|
47
|
+
this.byKey.delete(SubscriptionRegistry.key(state.fn.__lunoraRef, state.args, state.shardKey));
|
|
48
|
+
this.byId.delete(state.id);
|
|
49
|
+
}
|
|
50
|
+
all() {
|
|
51
|
+
return [...this.byKey.values()];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
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-B5vWSgvD.mjs";
|
|
2
|
+
export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-B5vWSgvD.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-B5vWSgvD.js";
|
|
2
|
+
export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-B5vWSgvD.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-B5vWSgvD.mjs";
|
|
2
|
+
export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-B5vWSgvD.mjs";
|
|
3
|
+
export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-3XJD-2hM.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-B5vWSgvD.js";
|
|
2
|
+
export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-B5vWSgvD.js";
|
|
3
|
+
export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-CKZR675M.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
|
-
export { deserializePreloaded, serializePreloaded } from '../packem_shared/
|
|
3
|
-
export { createServerClient } from '../packem_shared/createServerClient-
|
|
2
|
+
export { deserializePreloaded, serializePreloaded } from '../packem_shared/deserializePreloaded-C0eJTY_W.mjs';
|
|
3
|
+
export { createServerClient } from '../packem_shared/createServerClient-BxkNcRlR.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.10",
|
|
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__"
|
|
@@ -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,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 };
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/dist/packem_shared/{serializePreloaded-C0eJTY_W.mjs → deserializePreloaded-C0eJTY_W.mjs}
RENAMED
|
File without changes
|