@tanstack/svelte-db 0.2.0 → 0.2.1

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.
@@ -1,85 +1,72 @@
1
- import { createLiveQueryCollection, createLiveQueryWindowController, isCollection, } from '@tanstack/db';
2
- import { untrack } from 'svelte';
3
- const DEFAULT_PAGE_SIZE = 20;
1
+ import { assertLiveQueryWindowManyResult, compareLiveQueryWindowDependencies, createLiveQueryCollection, createLiveQueryWindowController, fetchNextLiveQueryWindowPage, getLiveQueryWindowCollectionWarning, normalizeLiveQueryWindowPageSize, resolveLiveQueryWindowInput, shouldPreserveLiveQueryWindowPageCount, } from '@tanstack/db';
2
+ import { tick, untrack } from 'svelte';
4
3
  const DEFAULT_GC_TIME_MS = 1;
5
- function hasSetWindow(collection) {
6
- return typeof collection.utils.setWindow === `function`;
7
- }
8
- function normalizePageSize(pageSize) {
9
- if (pageSize === undefined ||
10
- !Number.isSafeInteger(pageSize) ||
11
- pageSize <= 0) {
12
- return DEFAULT_PAGE_SIZE;
13
- }
14
- return pageSize;
15
- }
16
- function resolveInput(input) {
17
- if (isCollection(input)) {
18
- return { kind: `collection`, collection: input };
19
- }
20
- if (typeof input !== `function`) {
21
- throw new Error(`useLiveInfiniteQuery: First argument must be either a pre-created live query collection or a query function. ` +
22
- `Received: ${typeof input}`);
23
- }
24
- // A zero-argument function can be a reactive collection getter. Query
25
- // callbacks conventionally accept the builder, so avoid probing those.
26
- if (input.length === 0) {
27
- try {
28
- const collection = input();
29
- if (isCollection(collection)) {
30
- return { kind: `collection`, collection };
31
- }
32
- }
33
- catch {
34
- // Query callbacks that close over their builder can still have arity 0.
35
- }
36
- }
37
- return {
38
- kind: `query`,
39
- query: input,
40
- };
41
- }
42
4
  export function useLiveInfiniteQuery(queryFnOrCollection, config, deps = []) {
43
5
  let validatedCollection = null;
44
- const pageSize = $derived(normalizePageSize(config.pageSize));
6
+ let previousController = null;
7
+ let previousInput = null;
8
+ let previousDependencies = null;
9
+ let previousPageSize = null;
10
+ let previousInitialPageParam = null;
11
+ const pageSize = $derived(normalizeLiveQueryWindowPageSize(config.pageSize));
45
12
  const initialPageParam = $derived(config.initialPageParam ?? 0);
46
13
  const controller = $derived.by(() => {
47
- for (const dependency of deps)
48
- dependency();
49
- const input = resolveInput(queryFnOrCollection);
14
+ const dependencies = deps.map((dependency) => dependency());
15
+ const input = resolveLiveQueryWindowInput(queryFnOrCollection);
16
+ const dependencyComparison = compareLiveQueryWindowDependencies(previousDependencies, dependencies);
17
+ const dependenciesChanged = dependencyComparison.changed;
18
+ const dependenciesStructurallyEqual = dependencyComparison.structurallyEqual;
19
+ const pageShapeChanged = previousPageSize !== pageSize ||
20
+ previousInitialPageParam !== initialPageParam;
21
+ const sameCollection = input.kind === `collection` &&
22
+ previousInput?.kind === `collection` &&
23
+ previousInput.collection === input.collection;
24
+ const canPreservePageCount = shouldPreserveLiveQueryWindowPageCount({
25
+ hasPreviousController: previousController !== null,
26
+ previousInputKind: previousInput?.kind,
27
+ inputKind: input.kind,
28
+ sameCollection,
29
+ dependenciesChanged,
30
+ dependenciesStructurallyEqual,
31
+ pageShapeChanged,
32
+ });
33
+ const previousPageCount = previousController
34
+ ? Math.max(1, previousController.getSnapshot().pages.length)
35
+ : 1;
36
+ const initialPageCount = canPreservePageCount ? previousPageCount : 1;
37
+ previousInput = input;
38
+ previousDependencies = [...dependencies];
39
+ previousPageSize = pageSize;
40
+ previousInitialPageParam = initialPageParam;
50
41
  if (input.kind === `collection`) {
51
42
  const collection = input.collection;
52
- if (!hasSetWindow(collection)) {
53
- throw new Error(`useLiveInfiniteQuery: Pre-created live query collection must have an orderBy clause for infinite pagination to work. ` +
54
- `Please add .orderBy() to your createLiveQueryCollection query.`);
55
- }
43
+ const warning = getLiveQueryWindowCollectionWarning(collection, pageSize + 1);
56
44
  if (validatedCollection !== collection) {
57
45
  validatedCollection = collection;
58
- const currentWindow = collection.utils.getWindow?.();
59
- const expectedLimit = pageSize + 1;
60
- if (currentWindow &&
61
- (currentWindow.offset !== 0 || currentWindow.limit !== expectedLimit)) {
62
- console.warn(`useLiveInfiniteQuery: Pre-created collection has window {offset: ${currentWindow.offset}, limit: ${currentWindow.limit}} ` +
63
- `but the hook expects {offset: 0, limit: ${expectedLimit}}. Adjusting window now.`);
64
- }
46
+ if (warning)
47
+ console.warn(warning);
65
48
  }
66
- return createLiveQueryWindowController(collection, {
49
+ const currentController = createLiveQueryWindowController(collection, {
67
50
  pageSize,
68
51
  initialPageParam,
52
+ initialPageCount,
69
53
  });
54
+ previousController = currentController;
55
+ return currentController;
70
56
  }
71
57
  const collection = createLiveQueryCollection({
72
- query: (q) => input
73
- .query(q)
74
- .limit(pageSize + 1)
75
- .offset(0),
58
+ query: input.query.limit(pageSize + 1).offset(0),
76
59
  startSync: false,
77
60
  gcTime: DEFAULT_GC_TIME_MS,
78
61
  });
79
- return createLiveQueryWindowController(collection, {
62
+ assertLiveQueryWindowManyResult(collection);
63
+ const currentController = createLiveQueryWindowController(collection, {
80
64
  pageSize,
81
65
  initialPageParam,
66
+ initialPageCount,
82
67
  });
68
+ previousController = currentController;
69
+ return currentController;
83
70
  });
84
71
  let snapshot = $state.raw(untrack(() => controller.getSnapshot()));
85
72
  $effect(() => {
@@ -94,7 +81,13 @@ export function useLiveInfiniteQuery(queryFnOrCollection, config, deps = []) {
94
81
  currentController.dispose();
95
82
  };
96
83
  });
97
- const fetchNextPage = () => controller.fetchNextPage();
84
+ const fetchNextPage = async () => {
85
+ // A dependency can invalidate the derived controller before Svelte runs the
86
+ // effect that subscribes it. Queue the imperative call until that handoff
87
+ // has completed so it cannot target an inactive controller.
88
+ await tick();
89
+ await fetchNextLiveQueryWindowPage(controller);
90
+ };
98
91
  return {
99
92
  get state() {
100
93
  return snapshot.state;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/svelte-db",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Svelte integration for @tanstack/db",
5
5
  "author": "Kyle Mathews",
6
6
  "license": "MIT",
@@ -35,7 +35,7 @@
35
35
  "skills"
36
36
  ],
37
37
  "dependencies": {
38
- "@tanstack/db": "0.7.0"
38
+ "@tanstack/db": "0.7.1"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "svelte": "^5.0.0"