@tanstack/solid-db 0.1.54 → 0.1.56
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/esm/useLiveQuery.js.map +1 -1
- package/package.json +20 -20
- package/src/index.ts +4 -4
- package/src/useLiveQuery.ts +19 -17
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLiveQuery.js","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["import {\n batch,\n createComputed,\n createMemo,\n createResource,\n createSignal,\n onCleanup,\n} from \"solid-js\"\nimport { ReactiveMap } from \"@solid-primitives/map\"\nimport {\n BaseQueryBuilder,\n CollectionImpl,\n createLiveQueryCollection,\n} from \"@tanstack/db\"\nimport { createStore, reconcile } from \"solid-js/store\"\nimport type { Accessor } from \"solid-js\"\nimport type {\n ChangeMessage,\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n QueryBuilder,\n} from \"@tanstack/db\"\n\n/**\n * Create a live query using a query function\n * @param queryFn - Query function that defines what data to fetch\n * @returns Object with reactive data, state, and status information\n * @example\n * // Basic query with object syntax\n * const todosQuery = useLiveQuery((q) =>\n * q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.completed, false))\n * .select(({ todos }) => ({ id: todos.id, text: todos.text }))\n * )\n *\n * @example\n * // With dependencies that trigger re-execution\n * const todosQuery = useLiveQuery(\n * (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => gt(todos.priority, minPriority())),\n * )\n *\n * @example\n * // Join pattern\n * const personIssues = useLiveQuery((q) =>\n * q.from({ issues: issueCollection })\n * .join({ persons: personCollection }, ({ issues, persons }) =>\n * eq(issues.userId, persons.id)\n * )\n * .select(({ issues, persons }) => ({\n * id: issues.id,\n * title: issues.title,\n * userName: persons.name\n * }))\n * )\n *\n * @example\n * // Handle loading and error states\n * const todosQuery = useLiveQuery((q) =>\n * q.from({ todos: todoCollection })\n * )\n *\n * return (\n * <Switch>\n * <Match when={todosQuery.isLoading()}>\n * <div>Loading...</div>\n * </Match>\n * <Match when={todosQuery.isError()}>\n * <div>Error: {todosQuery.status()}</div>\n * </Match>\n * <Match when={todosQuery.isReady()}>\n * <For each={todosQuery.data()}>\n * {(todo) => <li key={todo.id}>{todo.text}</li>}\n * </For>\n * </Match>\n * </Switch>\n * )\n */\n// Overload 1: Accept query function that always returns QueryBuilder\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>\n): {\n state: ReactiveMap<string | number, GetResult<TContext>>\n data: Array<GetResult<TContext>>\n collection: Accessor<Collection<GetResult<TContext>, string | number, {}>>\n status: Accessor<CollectionStatus>\n isLoading: Accessor<boolean>\n isReady: Accessor<boolean>\n isIdle: Accessor<boolean>\n isError: Accessor<boolean>\n isCleanedUp: Accessor<boolean>\n}\n\n// Overload 1b: Accept query function that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext> | undefined | null\n): {\n state: ReactiveMap<string | number, GetResult<TContext>>\n data: Array<GetResult<TContext>>\n collection: Accessor<Collection<\n GetResult<TContext>,\n string | number,\n {}\n > | null>\n status: Accessor<CollectionStatus | `disabled`>\n isLoading: Accessor<boolean>\n isReady: Accessor<boolean>\n isIdle: Accessor<boolean>\n isError: Accessor<boolean>\n isCleanedUp: Accessor<boolean>\n}\n\n/**\n * Create a live query using configuration object\n * @param config - Configuration object with query and options\n * @returns Object with reactive data, state, and status information\n * @example\n * // Basic config object usage\n * const todosQuery = useLiveQuery(() => ({\n * query: (q) => q.from({ todos: todosCollection }),\n * gcTime: 60000\n * }))\n *\n * @example\n * // With query builder and options\n * const queryBuilder = new Query()\n * .from({ persons: collection })\n * .where(({ persons }) => gt(persons.age, 30))\n * .select(({ persons }) => ({ id: persons.id, name: persons.name }))\n *\n * const personsQuery = useLiveQuery(() => ({ query: queryBuilder }))\n *\n * @example\n * // Handle all states uniformly\n * const itemsQuery = useLiveQuery(() => ({\n * query: (q) => q.from({ items: itemCollection })\n * }))\n *\n * return (\n * <Switch fallback={<div>{itemsQuery.data.length} items loaded</div>}>\n * <Match when={itemsQuery.isLoading()}>\n * <div>Loading...</div>\n * </Match>\n * <Match when={itemsQuery.isError()}>\n * <div>Something went wrong</div>\n * </Match>\n * <Match when={!itemsQuery.isReady()}>\n * <div>Preparing...</div>\n * </Match>\n * </Switch>\n * )\n */\n// Overload 2: Accept config object\nexport function useLiveQuery<TContext extends Context>(\n config: Accessor<LiveQueryCollectionConfig<TContext>>\n): {\n state: ReactiveMap<string | number, GetResult<TContext>>\n data: Array<GetResult<TContext>>\n collection: Accessor<Collection<GetResult<TContext>, string | number, {}>>\n status: Accessor<CollectionStatus>\n isLoading: Accessor<boolean>\n isReady: Accessor<boolean>\n isIdle: Accessor<boolean>\n isError: Accessor<boolean>\n isCleanedUp: Accessor<boolean>\n}\n\n/**\n * Subscribe to an existing live query collection\n * @param liveQueryCollection - Pre-created live query collection to subscribe to\n * @returns Object with reactive data, state, and status information\n * @example\n * // Using pre-created live query collection\n * const myLiveQuery = createLiveQueryCollection((q) =>\n * q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true))\n * )\n * const todosQuery = useLiveQuery(() => myLiveQuery)\n *\n * @example\n * // Access collection methods directly\n * const existingQuery = useLiveQuery(() => existingCollection)\n *\n * // Use collection for mutations\n * const handleToggle = (id) => {\n * existingQuery.collection().update(id, draft => { draft.completed = !draft.completed })\n * }\n *\n * @example\n * // Handle states consistently\n * const sharedQuery = useLiveQuery(() => sharedCollection)\n *\n * return (\n * <Switch fallback={<div><For each={sharedQuery.data()}>{(item) => <Item key={item.id} {...item} />}</For></div>}>\n * <Match when={sharedQuery.isLoading()}>\n * <div>Loading...</div>\n * </Match>\n * <Match when={sharedQuery.isError()}>\n * <div>Error loading data</div>\n * </Match>\n * </Switch>\n * )\n */\n// Overload 3: Accept pre-created live query collection\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Accessor<Collection<TResult, TKey, TUtils>>\n): {\n state: ReactiveMap<TKey, TResult>\n data: Array<TResult>\n collection: Accessor<Collection<TResult, TKey, TUtils>>\n status: Accessor<CollectionStatus>\n isLoading: Accessor<boolean>\n isReady: Accessor<boolean>\n isIdle: Accessor<boolean>\n isError: Accessor<boolean>\n isCleanedUp: Accessor<boolean>\n}\n\n// Implementation - use function overloads to infer the actual collection type\nexport function useLiveQuery(\n configOrQueryOrCollection: (queryFn?: any) => any\n) {\n const collection = createMemo(\n () => {\n if (configOrQueryOrCollection.length === 1) {\n // This is a query function - check if it returns null/undefined\n const queryBuilder = new BaseQueryBuilder() as InitialQueryBuilder\n const result = configOrQueryOrCollection(queryBuilder)\n\n if (result === undefined || result === null) {\n // Disabled query - return null\n return null\n }\n\n return createLiveQueryCollection({\n query: configOrQueryOrCollection,\n startSync: true,\n })\n }\n\n const innerCollection = configOrQueryOrCollection()\n\n if (innerCollection === undefined || innerCollection === null) {\n // Disabled query - return null\n return null\n }\n\n if (innerCollection instanceof CollectionImpl) {\n innerCollection.startSyncImmediate()\n return innerCollection as Collection\n }\n\n return createLiveQueryCollection({\n ...innerCollection,\n startSync: true,\n })\n },\n undefined,\n { name: `TanstackDBCollectionMemo` }\n )\n\n // Reactive state that gets updated granularly through change events\n const state = new ReactiveMap<string | number, any>()\n\n // Reactive data array that maintains sorted order\n const [data, setData] = createStore<Array<any>>([], {\n name: `TanstackDBData`,\n })\n\n // Track collection status reactively\n const [status, setStatus] = createSignal(\n collection() ? collection()!.status : (`disabled` as const),\n {\n name: `TanstackDBStatus`,\n }\n )\n\n // Helper to sync data array from collection in correct order\n const syncDataFromCollection = (\n currentCollection: Collection<any, any, any>\n ) => {\n setData((prev) =>\n reconcile(Array.from(currentCollection.values()))(prev).filter(Boolean)\n )\n }\n\n // Track current unsubscribe function\n let currentUnsubscribe: (() => void) | null = null\n\n createComputed(\n () => {\n const currentCollection = collection()\n\n // Handle null collection (disabled query)\n if (!currentCollection) {\n setStatus(`disabled` as const)\n state.clear()\n setData([])\n if (currentUnsubscribe) {\n currentUnsubscribe()\n currentUnsubscribe = null\n }\n return\n }\n\n // Update status ref whenever the effect runs\n setStatus(currentCollection.status)\n\n // Initialize state with current collection data\n state.clear()\n for (const [key, value] of currentCollection.entries()) {\n state.set(key, value)\n }\n\n // Subscribe to collection changes with granular updates\n const subscription = currentCollection.subscribeChanges(\n (changes: Array<ChangeMessage<any>>) => {\n // Apply each change individually to the reactive state\n batch(() => {\n for (const change of changes) {\n switch (change.type) {\n case `insert`:\n case `update`:\n state.set(change.key, change.value)\n break\n case `delete`:\n state.delete(change.key)\n break\n }\n }\n })\n\n // Update the data array to maintain sorted order\n syncDataFromCollection(currentCollection)\n\n // Update status ref on every change\n setStatus(currentCollection.status)\n },\n {\n includeInitialState: true,\n }\n )\n\n currentUnsubscribe = subscription.unsubscribe.bind(subscription)\n\n // Preload collection data if not already started\n if (currentCollection.status === `idle`) {\n createResource(() => currentCollection.preload())\n }\n\n // Cleanup when computed is invalidated\n onCleanup(() => {\n if (currentUnsubscribe) {\n currentUnsubscribe()\n currentUnsubscribe = null\n }\n })\n },\n undefined,\n { name: `TanstackDBSyncComputed` }\n )\n\n return {\n state,\n data,\n collection,\n status,\n isLoading: () => status() === `loading`,\n isReady: () => status() === `ready` || status() === `disabled`,\n isIdle: () => status() === `idle`,\n isError: () => status() === `error`,\n isCleanedUp: () => status() === `cleaned-up`,\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useLiveQuery.js","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["import {\n batch,\n createComputed,\n createMemo,\n createResource,\n createSignal,\n onCleanup,\n} from 'solid-js'\nimport { ReactiveMap } from '@solid-primitives/map'\nimport {\n BaseQueryBuilder,\n CollectionImpl,\n createLiveQueryCollection,\n} from '@tanstack/db'\nimport { createStore, reconcile } from 'solid-js/store'\nimport type { Accessor } from 'solid-js'\nimport type {\n ChangeMessage,\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n QueryBuilder,\n} from '@tanstack/db'\n\n/**\n * Create a live query using a query function\n * @param queryFn - Query function that defines what data to fetch\n * @returns Object with reactive data, state, and status information\n * @example\n * // Basic query with object syntax\n * const todosQuery = useLiveQuery((q) =>\n * q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.completed, false))\n * .select(({ todos }) => ({ id: todos.id, text: todos.text }))\n * )\n *\n * @example\n * // With dependencies that trigger re-execution\n * const todosQuery = useLiveQuery(\n * (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => gt(todos.priority, minPriority())),\n * )\n *\n * @example\n * // Join pattern\n * const personIssues = useLiveQuery((q) =>\n * q.from({ issues: issueCollection })\n * .join({ persons: personCollection }, ({ issues, persons }) =>\n * eq(issues.userId, persons.id)\n * )\n * .select(({ issues, persons }) => ({\n * id: issues.id,\n * title: issues.title,\n * userName: persons.name\n * }))\n * )\n *\n * @example\n * // Handle loading and error states\n * const todosQuery = useLiveQuery((q) =>\n * q.from({ todos: todoCollection })\n * )\n *\n * return (\n * <Switch>\n * <Match when={todosQuery.isLoading()}>\n * <div>Loading...</div>\n * </Match>\n * <Match when={todosQuery.isError()}>\n * <div>Error: {todosQuery.status()}</div>\n * </Match>\n * <Match when={todosQuery.isReady()}>\n * <For each={todosQuery.data()}>\n * {(todo) => <li key={todo.id}>{todo.text}</li>}\n * </For>\n * </Match>\n * </Switch>\n * )\n */\n// Overload 1: Accept query function that always returns QueryBuilder\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n): {\n state: ReactiveMap<string | number, GetResult<TContext>>\n data: Array<GetResult<TContext>>\n collection: Accessor<Collection<GetResult<TContext>, string | number, {}>>\n status: Accessor<CollectionStatus>\n isLoading: Accessor<boolean>\n isReady: Accessor<boolean>\n isIdle: Accessor<boolean>\n isError: Accessor<boolean>\n isCleanedUp: Accessor<boolean>\n}\n\n// Overload 1b: Accept query function that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> | undefined | null,\n): {\n state: ReactiveMap<string | number, GetResult<TContext>>\n data: Array<GetResult<TContext>>\n collection: Accessor<Collection<\n GetResult<TContext>,\n string | number,\n {}\n > | null>\n status: Accessor<CollectionStatus | `disabled`>\n isLoading: Accessor<boolean>\n isReady: Accessor<boolean>\n isIdle: Accessor<boolean>\n isError: Accessor<boolean>\n isCleanedUp: Accessor<boolean>\n}\n\n/**\n * Create a live query using configuration object\n * @param config - Configuration object with query and options\n * @returns Object with reactive data, state, and status information\n * @example\n * // Basic config object usage\n * const todosQuery = useLiveQuery(() => ({\n * query: (q) => q.from({ todos: todosCollection }),\n * gcTime: 60000\n * }))\n *\n * @example\n * // With query builder and options\n * const queryBuilder = new Query()\n * .from({ persons: collection })\n * .where(({ persons }) => gt(persons.age, 30))\n * .select(({ persons }) => ({ id: persons.id, name: persons.name }))\n *\n * const personsQuery = useLiveQuery(() => ({ query: queryBuilder }))\n *\n * @example\n * // Handle all states uniformly\n * const itemsQuery = useLiveQuery(() => ({\n * query: (q) => q.from({ items: itemCollection })\n * }))\n *\n * return (\n * <Switch fallback={<div>{itemsQuery.data.length} items loaded</div>}>\n * <Match when={itemsQuery.isLoading()}>\n * <div>Loading...</div>\n * </Match>\n * <Match when={itemsQuery.isError()}>\n * <div>Something went wrong</div>\n * </Match>\n * <Match when={!itemsQuery.isReady()}>\n * <div>Preparing...</div>\n * </Match>\n * </Switch>\n * )\n */\n// Overload 2: Accept config object\nexport function useLiveQuery<TContext extends Context>(\n config: Accessor<LiveQueryCollectionConfig<TContext>>,\n): {\n state: ReactiveMap<string | number, GetResult<TContext>>\n data: Array<GetResult<TContext>>\n collection: Accessor<Collection<GetResult<TContext>, string | number, {}>>\n status: Accessor<CollectionStatus>\n isLoading: Accessor<boolean>\n isReady: Accessor<boolean>\n isIdle: Accessor<boolean>\n isError: Accessor<boolean>\n isCleanedUp: Accessor<boolean>\n}\n\n/**\n * Subscribe to an existing live query collection\n * @param liveQueryCollection - Pre-created live query collection to subscribe to\n * @returns Object with reactive data, state, and status information\n * @example\n * // Using pre-created live query collection\n * const myLiveQuery = createLiveQueryCollection((q) =>\n * q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true))\n * )\n * const todosQuery = useLiveQuery(() => myLiveQuery)\n *\n * @example\n * // Access collection methods directly\n * const existingQuery = useLiveQuery(() => existingCollection)\n *\n * // Use collection for mutations\n * const handleToggle = (id) => {\n * existingQuery.collection().update(id, draft => { draft.completed = !draft.completed })\n * }\n *\n * @example\n * // Handle states consistently\n * const sharedQuery = useLiveQuery(() => sharedCollection)\n *\n * return (\n * <Switch fallback={<div><For each={sharedQuery.data()}>{(item) => <Item key={item.id} {...item} />}</For></div>}>\n * <Match when={sharedQuery.isLoading()}>\n * <div>Loading...</div>\n * </Match>\n * <Match when={sharedQuery.isError()}>\n * <div>Error loading data</div>\n * </Match>\n * </Switch>\n * )\n */\n// Overload 3: Accept pre-created live query collection\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Accessor<Collection<TResult, TKey, TUtils>>,\n): {\n state: ReactiveMap<TKey, TResult>\n data: Array<TResult>\n collection: Accessor<Collection<TResult, TKey, TUtils>>\n status: Accessor<CollectionStatus>\n isLoading: Accessor<boolean>\n isReady: Accessor<boolean>\n isIdle: Accessor<boolean>\n isError: Accessor<boolean>\n isCleanedUp: Accessor<boolean>\n}\n\n// Implementation - use function overloads to infer the actual collection type\nexport function useLiveQuery(\n configOrQueryOrCollection: (queryFn?: any) => any,\n) {\n const collection = createMemo(\n () => {\n if (configOrQueryOrCollection.length === 1) {\n // This is a query function - check if it returns null/undefined\n const queryBuilder = new BaseQueryBuilder() as InitialQueryBuilder\n const result = configOrQueryOrCollection(queryBuilder)\n\n if (result === undefined || result === null) {\n // Disabled query - return null\n return null\n }\n\n return createLiveQueryCollection({\n query: configOrQueryOrCollection,\n startSync: true,\n })\n }\n\n const innerCollection = configOrQueryOrCollection()\n\n if (innerCollection === undefined || innerCollection === null) {\n // Disabled query - return null\n return null\n }\n\n if (innerCollection instanceof CollectionImpl) {\n innerCollection.startSyncImmediate()\n return innerCollection as Collection\n }\n\n return createLiveQueryCollection({\n ...innerCollection,\n startSync: true,\n })\n },\n undefined,\n { name: `TanstackDBCollectionMemo` },\n )\n\n // Reactive state that gets updated granularly through change events\n const state = new ReactiveMap<string | number, any>()\n\n // Reactive data array that maintains sorted order\n const [data, setData] = createStore<Array<any>>([], {\n name: `TanstackDBData`,\n })\n\n // Track collection status reactively\n const [status, setStatus] = createSignal(\n collection() ? collection()!.status : (`disabled` as const),\n {\n name: `TanstackDBStatus`,\n },\n )\n\n // Helper to sync data array from collection in correct order\n const syncDataFromCollection = (\n currentCollection: Collection<any, any, any>,\n ) => {\n setData((prev) =>\n reconcile(Array.from(currentCollection.values()))(prev).filter(Boolean),\n )\n }\n\n // Track current unsubscribe function\n let currentUnsubscribe: (() => void) | null = null\n\n createComputed(\n () => {\n const currentCollection = collection()\n\n // Handle null collection (disabled query)\n if (!currentCollection) {\n setStatus(`disabled` as const)\n state.clear()\n setData([])\n if (currentUnsubscribe) {\n currentUnsubscribe()\n currentUnsubscribe = null\n }\n return\n }\n\n // Update status ref whenever the effect runs\n setStatus(currentCollection.status)\n\n // Initialize state with current collection data\n state.clear()\n for (const [key, value] of currentCollection.entries()) {\n state.set(key, value)\n }\n\n // Subscribe to collection changes with granular updates\n const subscription = currentCollection.subscribeChanges(\n (changes: Array<ChangeMessage<any>>) => {\n // Apply each change individually to the reactive state\n batch(() => {\n for (const change of changes) {\n switch (change.type) {\n case `insert`:\n case `update`:\n state.set(change.key, change.value)\n break\n case `delete`:\n state.delete(change.key)\n break\n }\n }\n })\n\n // Update the data array to maintain sorted order\n syncDataFromCollection(currentCollection)\n\n // Update status ref on every change\n setStatus(currentCollection.status)\n },\n {\n includeInitialState: true,\n },\n )\n\n currentUnsubscribe = subscription.unsubscribe.bind(subscription)\n\n // Preload collection data if not already started\n if (currentCollection.status === `idle`) {\n createResource(() => currentCollection.preload())\n }\n\n // Cleanup when computed is invalidated\n onCleanup(() => {\n if (currentUnsubscribe) {\n currentUnsubscribe()\n currentUnsubscribe = null\n }\n })\n },\n undefined,\n { name: `TanstackDBSyncComputed` },\n )\n\n return {\n state,\n data,\n collection,\n status,\n isLoading: () => status() === `loading`,\n isReady: () => status() === `ready` || status() === `disabled`,\n isIdle: () => status() === `idle`,\n isError: () => status() === `error`,\n isCleanedUp: () => status() === `cleaned-up`,\n }\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/solid-db",
|
|
3
|
+
"version": "0.1.56",
|
|
3
4
|
"description": "Solid integration for @tanstack/db",
|
|
4
|
-
"version": "0.1.54",
|
|
5
5
|
"author": "Kyle Mathews",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/TanStack/db.git",
|
|
9
|
+
"url": "git+https://github.com/TanStack/db.git",
|
|
10
10
|
"directory": "packages/solid-db"
|
|
11
11
|
},
|
|
12
12
|
"homepage": "https://tanstack.com/db",
|
|
@@ -15,19 +15,10 @@
|
|
|
15
15
|
"solid",
|
|
16
16
|
"typescript"
|
|
17
17
|
],
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"devDependencies": {
|
|
23
|
-
"@electric-sql/client": "1.2.0",
|
|
24
|
-
"@solidjs/testing-library": "^0.8.10",
|
|
25
|
-
"@vitest/coverage-istanbul": "^3.2.4",
|
|
26
|
-
"jsdom": "^27.2.0",
|
|
27
|
-
"solid-js": "^1.9.10",
|
|
28
|
-
"vite-plugin-solid": "^2.11.10",
|
|
29
|
-
"vitest": "^3.2.4"
|
|
30
|
-
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "dist/esm/index.js",
|
|
20
|
+
"module": "dist/esm/index.js",
|
|
21
|
+
"types": "dist/esm/index.d.ts",
|
|
31
22
|
"exports": {
|
|
32
23
|
".": {
|
|
33
24
|
"import": {
|
|
@@ -37,18 +28,27 @@
|
|
|
37
28
|
},
|
|
38
29
|
"./package.json": "./package.json"
|
|
39
30
|
},
|
|
31
|
+
"sideEffects": false,
|
|
40
32
|
"files": [
|
|
41
33
|
"dist",
|
|
42
34
|
"src"
|
|
43
35
|
],
|
|
44
|
-
"
|
|
45
|
-
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@solid-primitives/map": "^0.7.2",
|
|
38
|
+
"@tanstack/db": "0.5.13"
|
|
39
|
+
},
|
|
46
40
|
"peerDependencies": {
|
|
47
41
|
"solid-js": ">=1.9.0"
|
|
48
42
|
},
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@electric-sql/client": "^1.3.0",
|
|
45
|
+
"@solidjs/testing-library": "^0.8.10",
|
|
46
|
+
"@vitest/coverage-istanbul": "^3.2.4",
|
|
47
|
+
"jsdom": "^27.2.0",
|
|
48
|
+
"solid-js": "^1.9.10",
|
|
49
|
+
"vite-plugin-solid": "^2.11.10",
|
|
50
|
+
"vitest": "^3.2.4"
|
|
51
|
+
},
|
|
52
52
|
"scripts": {
|
|
53
53
|
"build": "vite build",
|
|
54
54
|
"dev": "vite build --watch",
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Re-export all public APIs
|
|
2
|
-
export * from
|
|
2
|
+
export * from './useLiveQuery'
|
|
3
3
|
|
|
4
4
|
// Re-export everything from @tanstack/db
|
|
5
|
-
export * from
|
|
5
|
+
export * from '@tanstack/db'
|
|
6
6
|
|
|
7
7
|
// Re-export some stuff explicitly to ensure the type & value is exported
|
|
8
|
-
export type { Collection } from
|
|
9
|
-
export { createTransaction } from
|
|
8
|
+
export type { Collection } from '@tanstack/db'
|
|
9
|
+
export { createTransaction } from '@tanstack/db'
|
package/src/useLiveQuery.ts
CHANGED
|
@@ -5,15 +5,15 @@ import {
|
|
|
5
5
|
createResource,
|
|
6
6
|
createSignal,
|
|
7
7
|
onCleanup,
|
|
8
|
-
} from
|
|
9
|
-
import { ReactiveMap } from
|
|
8
|
+
} from 'solid-js'
|
|
9
|
+
import { ReactiveMap } from '@solid-primitives/map'
|
|
10
10
|
import {
|
|
11
11
|
BaseQueryBuilder,
|
|
12
12
|
CollectionImpl,
|
|
13
13
|
createLiveQueryCollection,
|
|
14
|
-
} from
|
|
15
|
-
import { createStore, reconcile } from
|
|
16
|
-
import type { Accessor } from
|
|
14
|
+
} from '@tanstack/db'
|
|
15
|
+
import { createStore, reconcile } from 'solid-js/store'
|
|
16
|
+
import type { Accessor } from 'solid-js'
|
|
17
17
|
import type {
|
|
18
18
|
ChangeMessage,
|
|
19
19
|
Collection,
|
|
@@ -23,7 +23,7 @@ import type {
|
|
|
23
23
|
InitialQueryBuilder,
|
|
24
24
|
LiveQueryCollectionConfig,
|
|
25
25
|
QueryBuilder,
|
|
26
|
-
} from
|
|
26
|
+
} from '@tanstack/db'
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Create a live query using a query function
|
|
@@ -82,7 +82,7 @@ import type {
|
|
|
82
82
|
*/
|
|
83
83
|
// Overload 1: Accept query function that always returns QueryBuilder
|
|
84
84
|
export function useLiveQuery<TContext extends Context>(
|
|
85
|
-
queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext
|
|
85
|
+
queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,
|
|
86
86
|
): {
|
|
87
87
|
state: ReactiveMap<string | number, GetResult<TContext>>
|
|
88
88
|
data: Array<GetResult<TContext>>
|
|
@@ -97,7 +97,9 @@ export function useLiveQuery<TContext extends Context>(
|
|
|
97
97
|
|
|
98
98
|
// Overload 1b: Accept query function that can return undefined/null
|
|
99
99
|
export function useLiveQuery<TContext extends Context>(
|
|
100
|
-
queryFn: (
|
|
100
|
+
queryFn: (
|
|
101
|
+
q: InitialQueryBuilder,
|
|
102
|
+
) => QueryBuilder<TContext> | undefined | null,
|
|
101
103
|
): {
|
|
102
104
|
state: ReactiveMap<string | number, GetResult<TContext>>
|
|
103
105
|
data: Array<GetResult<TContext>>
|
|
@@ -156,7 +158,7 @@ export function useLiveQuery<TContext extends Context>(
|
|
|
156
158
|
*/
|
|
157
159
|
// Overload 2: Accept config object
|
|
158
160
|
export function useLiveQuery<TContext extends Context>(
|
|
159
|
-
config: Accessor<LiveQueryCollectionConfig<TContext
|
|
161
|
+
config: Accessor<LiveQueryCollectionConfig<TContext>>,
|
|
160
162
|
): {
|
|
161
163
|
state: ReactiveMap<string | number, GetResult<TContext>>
|
|
162
164
|
data: Array<GetResult<TContext>>
|
|
@@ -210,7 +212,7 @@ export function useLiveQuery<
|
|
|
210
212
|
TKey extends string | number,
|
|
211
213
|
TUtils extends Record<string, any>,
|
|
212
214
|
>(
|
|
213
|
-
liveQueryCollection: Accessor<Collection<TResult, TKey, TUtils
|
|
215
|
+
liveQueryCollection: Accessor<Collection<TResult, TKey, TUtils>>,
|
|
214
216
|
): {
|
|
215
217
|
state: ReactiveMap<TKey, TResult>
|
|
216
218
|
data: Array<TResult>
|
|
@@ -225,7 +227,7 @@ export function useLiveQuery<
|
|
|
225
227
|
|
|
226
228
|
// Implementation - use function overloads to infer the actual collection type
|
|
227
229
|
export function useLiveQuery(
|
|
228
|
-
configOrQueryOrCollection: (queryFn?: any) => any
|
|
230
|
+
configOrQueryOrCollection: (queryFn?: any) => any,
|
|
229
231
|
) {
|
|
230
232
|
const collection = createMemo(
|
|
231
233
|
() => {
|
|
@@ -263,7 +265,7 @@ export function useLiveQuery(
|
|
|
263
265
|
})
|
|
264
266
|
},
|
|
265
267
|
undefined,
|
|
266
|
-
{ name: `TanstackDBCollectionMemo` }
|
|
268
|
+
{ name: `TanstackDBCollectionMemo` },
|
|
267
269
|
)
|
|
268
270
|
|
|
269
271
|
// Reactive state that gets updated granularly through change events
|
|
@@ -279,15 +281,15 @@ export function useLiveQuery(
|
|
|
279
281
|
collection() ? collection()!.status : (`disabled` as const),
|
|
280
282
|
{
|
|
281
283
|
name: `TanstackDBStatus`,
|
|
282
|
-
}
|
|
284
|
+
},
|
|
283
285
|
)
|
|
284
286
|
|
|
285
287
|
// Helper to sync data array from collection in correct order
|
|
286
288
|
const syncDataFromCollection = (
|
|
287
|
-
currentCollection: Collection<any, any, any
|
|
289
|
+
currentCollection: Collection<any, any, any>,
|
|
288
290
|
) => {
|
|
289
291
|
setData((prev) =>
|
|
290
|
-
reconcile(Array.from(currentCollection.values()))(prev).filter(Boolean)
|
|
292
|
+
reconcile(Array.from(currentCollection.values()))(prev).filter(Boolean),
|
|
291
293
|
)
|
|
292
294
|
}
|
|
293
295
|
|
|
@@ -345,7 +347,7 @@ export function useLiveQuery(
|
|
|
345
347
|
},
|
|
346
348
|
{
|
|
347
349
|
includeInitialState: true,
|
|
348
|
-
}
|
|
350
|
+
},
|
|
349
351
|
)
|
|
350
352
|
|
|
351
353
|
currentUnsubscribe = subscription.unsubscribe.bind(subscription)
|
|
@@ -364,7 +366,7 @@ export function useLiveQuery(
|
|
|
364
366
|
})
|
|
365
367
|
},
|
|
366
368
|
undefined,
|
|
367
|
-
{ name: `TanstackDBSyncComputed` }
|
|
369
|
+
{ name: `TanstackDBSyncComputed` },
|
|
368
370
|
)
|
|
369
371
|
|
|
370
372
|
return {
|