@lunora/client 1.0.0-alpha.1 → 1.0.0-alpha.11

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.
Files changed (32) hide show
  1. package/README.md +2 -0
  2. package/__assets__/package-og.svg +1 -1
  3. package/dist/auth/index.d.mts +1 -1
  4. package/dist/auth/index.d.ts +1 -1
  5. package/dist/index.d.mts +136 -5
  6. package/dist/index.d.ts +136 -5
  7. package/dist/index.mjs +8 -7
  8. package/dist/packem_shared/{LunoraClient-B00f7VUM.mjs → LunoraClient-6J-4BGWK.mjs} +744 -187
  9. package/dist/packem_shared/OfflineQueue-BI0FNNvc.mjs +1 -0
  10. package/dist/packem_shared/SubscriptionRegistry-Dn-7k7eo.mjs +1 -0
  11. package/dist/packem_shared/{createIndexedDbPersistence-CW82inU5.mjs → createInMemoryPersistence-DlFjWtOm.mjs} +13 -1
  12. package/dist/packem_shared/{createIndexedDbQueryCache-B1PQ9Twl.mjs → createInMemoryQueryCache-4AkTV38-.mjs} +13 -1
  13. package/dist/packem_shared/createLocalStore-IOur0jHF.mjs +1 -0
  14. package/dist/packem_shared/createMutatorRunner-BETvCd0p.mjs +31 -0
  15. package/dist/packem_shared/{createServerClient-DyFIHnWZ.mjs → createServerClient-CtklXrs2.mjs} +1 -1
  16. package/dist/packem_shared/local-store-BNgN3Dw3.mjs +111 -0
  17. package/dist/packem_shared/{lunora-client.d-DGvyuJ_p.d.mts → lunora-client.d-Dm1BYq19.d.mts} +652 -43
  18. package/dist/packem_shared/{lunora-client.d-DGvyuJ_p.d.ts → lunora-client.d-Dm1BYq19.d.ts} +652 -43
  19. package/dist/packem_shared/{OfflineQueue-D5p_QgF_.mjs → offline-queue-7Wc4onA0.mjs} +42 -5
  20. package/dist/packem_shared/{preload.d-dSaRMuhL.d.mts → preload.d-BfWzGJWV.d.mts} +1 -1
  21. package/dist/packem_shared/{preload.d-BoDmFqSG.d.ts → preload.d-Sp_Ef-_u.d.ts} +1 -1
  22. package/dist/packem_shared/subscription-C1Jy7HiF.mjs +55 -0
  23. package/dist/query/index.d.mts +2 -2
  24. package/dist/query/index.d.ts +2 -2
  25. package/dist/ssr/index.d.mts +3 -3
  26. package/dist/ssr/index.d.ts +3 -3
  27. package/dist/ssr/index.mjs +2 -2
  28. package/package.json +2 -2
  29. package/dist/packem_shared/SubscriptionRegistry-B-Qx_Gux.mjs +0 -26
  30. package/dist/packem_shared/createLocalStore-DSUfoLqY.mjs +0 -36
  31. /package/dist/packem_shared/{createStream-BDkqO5PW.mjs → DEFAULT_MAX_BUFFER-BDkqO5PW.mjs} +0 -0
  32. /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
- return `m_${Date.now().toString(36)}_${idCounter.toString(36)}`;
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 = {}, persistence) {
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({ args: item.args, functionPath: item.functionPath, id: item.id, identity: item.identity, shardKey: item.shardKey }).catch((error) => {
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-DGvyuJ_p.mjs";
1
+ import { F as FunctionReference, L as LunoraClient, A as ArgsOf, R as ReturnOf, P as Preloaded } from "./lunora-client.d-Dm1BYq19.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-DGvyuJ_p.js";
1
+ import { F as FunctionReference, L as LunoraClient, A as ArgsOf, R as ReturnOf, P as Preloaded } from "./lunora-client.d-Dm1BYq19.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 };
@@ -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-DGvyuJ_p.mjs";
2
- export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-DGvyuJ_p.mjs";
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-Dm1BYq19.mjs";
2
+ export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-Dm1BYq19.mjs";
3
3
  import '@lunora/runtime';
4
4
  /**
5
5
  * The sentinel a framework adapter resolves its reactive args to when it wants
@@ -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-DGvyuJ_p.js";
2
- export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-DGvyuJ_p.js";
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-Dm1BYq19.js";
2
+ export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-Dm1BYq19.js";
3
3
  import '@lunora/runtime';
4
4
  /**
5
5
  * The sentinel a framework adapter resolves its reactive args to when it wants
@@ -1,6 +1,6 @@
1
- import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-DGvyuJ_p.mjs";
2
- export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-DGvyuJ_p.mjs";
3
- export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-dSaRMuhL.mjs";
1
+ import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-Dm1BYq19.mjs";
2
+ export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-Dm1BYq19.mjs";
3
+ export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-BfWzGJWV.mjs";
4
4
  import '@lunora/runtime';
5
5
  /**
6
6
  * Structural shape of a better-auth `getSession` call's resolved value.
@@ -1,6 +1,6 @@
1
- import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-DGvyuJ_p.js";
2
- export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-DGvyuJ_p.js";
3
- export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-BoDmFqSG.js";
1
+ import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-Dm1BYq19.js";
2
+ export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-Dm1BYq19.js";
3
+ export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-Sp_Ef-_u.js";
4
4
  import '@lunora/runtime';
5
5
  /**
6
6
  * Structural shape of a better-auth `getSession` call's resolved value.
@@ -1,4 +1,4 @@
1
1
  export { getServerSession } from '../packem_shared/getServerSession-8jXewqxd.mjs';
2
- export { deserializePreloaded, serializePreloaded } from '../packem_shared/serializePreloaded-C0eJTY_W.mjs';
3
- export { createServerClient } from '../packem_shared/createServerClient-DyFIHnWZ.mjs';
2
+ export { deserializePreloaded, serializePreloaded } from '../packem_shared/deserializePreloaded-C0eJTY_W.mjs';
3
+ export { createServerClient } from '../packem_shared/createServerClient-CtklXrs2.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.1",
3
+ "version": "1.0.0-alpha.11",
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 };