@lunora/svelte 1.0.0-alpha.32 → 1.0.0-alpha.34

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.mts CHANGED
@@ -307,8 +307,11 @@ interface PresenceHandle<L extends ListPresentReference> {
307
307
  * Svelte context (requires calling inside a component's `&lt;script>` block or
308
308
  * inside a function called during component initialisation).
309
309
  *
310
- * Call `teardown()` when the component is destroyed to stop heartbeats and
311
- * remove the visibility listener (`onDestroy(handle.teardown)`).
310
+ * Teardown (stop heartbeats, remove the visibility listener, release the
311
+ * connection context) is wired automatically to the component's `onDestroy`
312
+ * when called during component initialisation. Outside a component call
313
+ * `handle.teardown()` yourself. `teardown()` is idempotent, so an explicit
314
+ * `onDestroy(handle.teardown)` on top of the auto-wiring is safe.
312
315
  */
313
316
  declare function presence<H extends HeartbeatReference, L extends ListPresentReference>(roomId: string, options: PresenceOptions<H, L>): PresenceHandle<L>;
314
317
  declare function presence<H extends HeartbeatReference, L extends ListPresentReference>(client: LunoraClient, roomId: string, options: PresenceOptions<H, L>): PresenceHandle<L>;
@@ -337,12 +340,16 @@ type QueryStore<F extends FunctionReference> = Readable<ReturnOf<F> | undefined>
337
340
  * store across several components shares a single underlying subscription
338
341
  * (the `LunoraClient` de-dupes by `(fn, args, shardKey)`).
339
342
  *
343
+ * Pass `"skip"` as `args` to keep the store connected but the subscription
344
+ * dormant (the value stays `undefined`, no socket opens) — useful for a query
345
+ * gated on auth or a route param, matching React/Vue/Solid's `useQuery`.
346
+ *
340
347
  * Pass `client` explicitly, or omit it to resolve the ambient client published
341
348
  * by `setLunoraClient` (which must therefore be called during component init,
342
349
  * before this runs).
343
350
  */
344
- declare function query<F extends FunctionReference>(function_: F, args: ArgsOf<F>, options?: QueryStoreOptions): QueryStore<F>;
345
- declare function query<F extends FunctionReference>(client: LunoraClient, function_: F, args: ArgsOf<F>, options?: QueryStoreOptions): QueryStore<F>;
351
+ declare function query<F extends FunctionReference>(function_: F, args: ArgsOf<F> | "skip", options?: QueryStoreOptions): QueryStore<F>;
352
+ declare function query<F extends FunctionReference>(client: LunoraClient, function_: F, args: ArgsOf<F> | "skip", options?: QueryStoreOptions): QueryStore<F>;
346
353
  interface RateLimitOptions {
347
354
  /** Clock injection for tests. Defaults to `Date.now`. */
348
355
  now?: () => number;
package/dist/index.d.ts CHANGED
@@ -307,8 +307,11 @@ interface PresenceHandle<L extends ListPresentReference> {
307
307
  * Svelte context (requires calling inside a component's `&lt;script>` block or
308
308
  * inside a function called during component initialisation).
309
309
  *
310
- * Call `teardown()` when the component is destroyed to stop heartbeats and
311
- * remove the visibility listener (`onDestroy(handle.teardown)`).
310
+ * Teardown (stop heartbeats, remove the visibility listener, release the
311
+ * connection context) is wired automatically to the component's `onDestroy`
312
+ * when called during component initialisation. Outside a component call
313
+ * `handle.teardown()` yourself. `teardown()` is idempotent, so an explicit
314
+ * `onDestroy(handle.teardown)` on top of the auto-wiring is safe.
312
315
  */
313
316
  declare function presence<H extends HeartbeatReference, L extends ListPresentReference>(roomId: string, options: PresenceOptions<H, L>): PresenceHandle<L>;
314
317
  declare function presence<H extends HeartbeatReference, L extends ListPresentReference>(client: LunoraClient, roomId: string, options: PresenceOptions<H, L>): PresenceHandle<L>;
@@ -337,12 +340,16 @@ type QueryStore<F extends FunctionReference> = Readable<ReturnOf<F> | undefined>
337
340
  * store across several components shares a single underlying subscription
338
341
  * (the `LunoraClient` de-dupes by `(fn, args, shardKey)`).
339
342
  *
343
+ * Pass `"skip"` as `args` to keep the store connected but the subscription
344
+ * dormant (the value stays `undefined`, no socket opens) — useful for a query
345
+ * gated on auth or a route param, matching React/Vue/Solid's `useQuery`.
346
+ *
340
347
  * Pass `client` explicitly, or omit it to resolve the ambient client published
341
348
  * by `setLunoraClient` (which must therefore be called during component init,
342
349
  * before this runs).
343
350
  */
344
- declare function query<F extends FunctionReference>(function_: F, args: ArgsOf<F>, options?: QueryStoreOptions): QueryStore<F>;
345
- declare function query<F extends FunctionReference>(client: LunoraClient, function_: F, args: ArgsOf<F>, options?: QueryStoreOptions): QueryStore<F>;
351
+ declare function query<F extends FunctionReference>(function_: F, args: ArgsOf<F> | "skip", options?: QueryStoreOptions): QueryStore<F>;
352
+ declare function query<F extends FunctionReference>(client: LunoraClient, function_: F, args: ArgsOf<F> | "skip", options?: QueryStoreOptions): QueryStore<F>;
346
353
  interface RateLimitOptions {
347
354
  /** Clock injection for tests. Defaults to `Date.now`. */
348
355
  now?: () => number;
package/dist/index.mjs CHANGED
@@ -5,8 +5,8 @@ export { flag, flags } from './packem_shared/flag-CqMU07WH.mjs';
5
5
  export { hydratePreloaded } from './packem_shared/hydratePreloaded-Djsf_C8Q.mjs';
6
6
  export { mutation } from './packem_shared/mutation-BmkCZnTd.mjs';
7
7
  export { mutator } from './packem_shared/mutator-B5LchgMS.mjs';
8
- export { infiniteQuery, paginatedQuery } from './packem_shared/infiniteQuery-BFsBpXnS.mjs';
9
- export { presence } from './packem_shared/presence-UJKjdcKg.mjs';
10
- export { query } from './packem_shared/query-BsJHHnAK.mjs';
8
+ export { infiniteQuery, paginatedQuery } from './packem_shared/infiniteQuery-DlnFVv-f.mjs';
9
+ export { presence } from './packem_shared/presence-BvChXqiB.mjs';
10
+ export { query } from './packem_shared/query-B-Gu6KQw.mjs';
11
11
  export { rateLimit } from './packem_shared/rateLimit-Cdw1kp8t.mjs';
12
- export { subscription } from './packem_shared/subscription-C121AbM_.mjs';
12
+ export { subscription } from './packem_shared/subscription-COqqiMPX.mjs';
@@ -1,5 +1,5 @@
1
1
  import { initialPages, derivePaginationStatus, rebalance, applyLoadMore } from '@lunora/client/pagination';
2
- import { derived, writable, readable } from 'svelte/store';
2
+ import { derived, writable, readable, get } from 'svelte/store';
3
3
  import { getLunoraClient } from './getLunoraClient-Prczj1gU.mjs';
4
4
  import { i as isFunctionReference } from './is-function-reference-ycLAcI79.mjs';
5
5
 
@@ -17,22 +17,37 @@ const createPaginatedEngine = (client, function_, baseArgs, options) => {
17
17
  const resultsByKey = /* @__PURE__ */ new Map();
18
18
  const activeSubs = /* @__PURE__ */ new Map();
19
19
  const pendingPageKeys = /* @__PURE__ */ new Set();
20
- let currentPages = initialPages(initialNumItems);
21
20
  const currentBaseArgs = baseArgs;
22
- pagesStore.subscribe((pages) => {
23
- currentPages = pages;
24
- });
25
21
  const rebuildPageResults = () => {
26
22
  if (currentBaseArgs === "skip") {
27
23
  pageResultsInternal.set([]);
28
24
  return;
29
25
  }
30
- const updated = currentPages.map((page) => {
26
+ const updated = get(pagesStore).map((page) => {
31
27
  const key = buildPageKey(function_["__lunoraRef"], buildPageArgs(page, currentBaseArgs));
32
28
  return resultsByKey.get(key);
33
29
  });
34
30
  pageResultsInternal.set(updated);
35
31
  };
32
+ const migrateResultsForRebalance = (oldPages, newPages) => {
33
+ if (currentBaseArgs === "skip") {
34
+ return;
35
+ }
36
+ const keyOf = (page) => buildPageKey(function_["__lunoraRef"], buildPageArgs(page, currentBaseArgs));
37
+ for (const newPage of newPages) {
38
+ const newKey = keyOf(newPage);
39
+ if (resultsByKey.has(newKey)) {
40
+ continue;
41
+ }
42
+ const donor = oldPages.find((op) => op.lower === newPage.lower);
43
+ if (donor) {
44
+ const carried = resultsByKey.get(keyOf(donor));
45
+ if (carried) {
46
+ resultsByKey.set(newKey, carried);
47
+ }
48
+ }
49
+ }
50
+ };
36
51
  const syncSubscriptions = () => {
37
52
  if (currentBaseArgs === "skip") {
38
53
  for (const unsub of activeSubs.values()) {
@@ -43,8 +58,9 @@ const createPaginatedEngine = (client, function_, baseArgs, options) => {
43
58
  return;
44
59
  }
45
60
  const baseArgsRecord = currentBaseArgs;
61
+ const pages = get(pagesStore);
46
62
  const wantedKeys = /* @__PURE__ */ new Set();
47
- for (const page of currentPages) {
63
+ for (const page of pages) {
48
64
  wantedKeys.add(buildPageKey(function_["__lunoraRef"], buildPageArgs(page, baseArgsRecord)));
49
65
  }
50
66
  for (const [key, unsub] of activeSubs) {
@@ -52,9 +68,10 @@ const createPaginatedEngine = (client, function_, baseArgs, options) => {
52
68
  unsub();
53
69
  activeSubs.delete(key);
54
70
  pendingPageKeys.delete(key);
71
+ resultsByKey.delete(key);
55
72
  }
56
73
  }
57
- for (const page of currentPages) {
74
+ for (const page of pages) {
58
75
  const pageArgs = buildPageArgs(page, baseArgsRecord);
59
76
  const key = buildPageKey(function_["__lunoraRef"], pageArgs);
60
77
  if (activeSubs.has(key)) {
@@ -69,12 +86,10 @@ const createPaginatedEngine = (client, function_, baseArgs, options) => {
69
86
  pendingPageKeys.delete(key);
70
87
  rebuildPageResults();
71
88
  if (pendingPageKeys.size === 0) {
72
- let updatedResults = [];
73
- pageResultsInternal.subscribe((results) => {
74
- updatedResults = results;
75
- })();
76
- const next = rebalance(currentPages, updatedResults);
89
+ const latestPages = get(pagesStore);
90
+ const next = rebalance(latestPages, get(pageResultsInternal));
77
91
  if (next) {
92
+ migrateResultsForRebalance(latestPages, next);
78
93
  pagesStore.set(next);
79
94
  syncSubscriptions();
80
95
  rebuildPageResults();
@@ -115,14 +130,12 @@ const createPaginatedEngine = (client, function_, baseArgs, options) => {
115
130
  if (currentBaseArgs === "skip") {
116
131
  return;
117
132
  }
118
- let currentResults = [];
119
- pageResultsInternal.subscribe((results) => {
120
- currentResults = results;
121
- })();
133
+ const currentResults = get(pageResultsInternal);
122
134
  const { nextCursor, status: currentStatus } = derivePaginationStatus(false, currentResults);
123
135
  if (currentStatus !== "CanLoadMore") {
124
136
  return;
125
137
  }
138
+ const currentPages = get(pagesStore);
126
139
  const next = applyLoadMore(currentPages, nextCursor, numberItems);
127
140
  if (!next) {
128
141
  return;
@@ -136,6 +149,7 @@ const createPaginatedEngine = (client, function_, baseArgs, options) => {
136
149
  const carried = resultsByKey.get(oldKey);
137
150
  if (carried) {
138
151
  resultsByKey.set(newKey, carried);
152
+ resultsByKey.delete(oldKey);
139
153
  }
140
154
  }
141
155
  }
@@ -1,16 +1,24 @@
1
+ import { onDestroy } from 'svelte';
1
2
  import { readable } from 'svelte/store';
2
3
  import { getLunoraClient } from './getLunoraClient-Prczj1gU.mjs';
3
4
 
4
- const makeSessionId = () => {
5
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
6
- return crypto.randomUUID();
5
+ const randomSessionId = (prefix = "sess") => {
6
+ if (typeof crypto !== "undefined") {
7
+ if (typeof crypto.randomUUID === "function") {
8
+ return crypto.randomUUID();
9
+ }
10
+ if (typeof crypto.getRandomValues === "function") {
11
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
12
+ return `${prefix}-${Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
13
+ }
7
14
  }
8
- return `sess-${Math.random().toString(36).slice(2)}-${String(Date.now())}`;
15
+ return `${prefix}-${Date.now().toString(36)}`;
9
16
  };
17
+
10
18
  const DEFAULT_INTERVAL_MS = 1e4;
11
19
  const createPresenceHandle = (client, roomId, options) => {
12
20
  const { heartbeat, intervalMs = DEFAULT_INTERVAL_MS, listPresent, shardKey } = options;
13
- const sessionId = options.sessionId ?? makeSessionId();
21
+ const sessionId = options.sessionId ?? randomSessionId();
14
22
  let latestData = options.data;
15
23
  const sendHeartbeat = () => {
16
24
  const args = {
@@ -46,13 +54,22 @@ const createPresenceHandle = (client, roomId, options) => {
46
54
  );
47
55
  return unsubscribe;
48
56
  });
57
+ let torndown = false;
49
58
  const teardown = () => {
59
+ if (torndown) {
60
+ return;
61
+ }
62
+ torndown = true;
50
63
  clearInterval(intervalHandle);
51
64
  if (typeof document !== "undefined") {
52
65
  document.removeEventListener("visibilitychange", onVisible);
53
66
  }
54
67
  releaseConnectionContext();
55
68
  };
69
+ try {
70
+ onDestroy(teardown);
71
+ } catch {
72
+ }
56
73
  return { present, sessionId, setData, teardown };
57
74
  };
58
75
  function presence(clientOrRoomId, roomIdOrOptions, maybeOptions) {
@@ -25,7 +25,13 @@ function query(clientOrFunction, functionOrArguments, argumentsOrOptions, maybeO
25
25
  onData: (value) => {
26
26
  set(value);
27
27
  },
28
- onError: options.onError
28
+ onError: options.onError,
29
+ // `args === "skip"` short-circuits inside the shared helper and
30
+ // fires this reset — clear any prior value so a store that flips
31
+ // to `"skip"` does not retain stale data.
32
+ onReset: () => {
33
+ set(void 0);
34
+ }
29
35
  },
30
36
  { shardKey: options.shardKey }
31
37
  )
@@ -12,9 +12,6 @@ function subscription(clientOrFunction, functionOrArgs, argsOrOptions, maybeOpti
12
12
  const { shardKey, onError } = options;
13
13
  const errorStore = writable();
14
14
  const data = readable(void 0, (set) => {
15
- if (args === "skip") {
16
- return () => void 0;
17
- }
18
15
  const unsubscribe = createQuerySubscription(
19
16
  client,
20
17
  functionRef,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/svelte",
3
- "version": "1.0.0-alpha.32",
3
+ "version": "1.0.0-alpha.34",
4
4
  "description": "Svelte adapter for Lunora — live stores, optimistic mutations, and reactive loaders",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -54,10 +54,10 @@
54
54
  "access": "public"
55
55
  },
56
56
  "dependencies": {
57
- "@lunora/client": "1.0.0-alpha.20",
58
- "@lunora/errors": "1.0.0-alpha.3",
59
- "@lunora/ratelimit": "1.0.0-alpha.6",
60
- "@lunora/runtime": "1.0.0-alpha.22"
57
+ "@lunora/client": "1.0.0-alpha.21",
58
+ "@lunora/errors": "1.0.0-alpha.4",
59
+ "@lunora/ratelimit": "1.0.0-alpha.7",
60
+ "@lunora/runtime": "1.0.0-alpha.24"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "svelte": "^5.0.0"