@tanstack/angular-db 0.2.12 → 1.0.0-rc.1.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.
- package/README.md +11 -3
- package/dist/cjs/index.cjs +23 -22
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +16 -38
- package/dist/esm/index.d.ts +16 -38
- package/dist/esm/index.js +24 -23
- package/dist/esm/index.js.map +1 -1
- package/package.json +8 -8
- package/skills/angular-db/SKILL.md +12 -6
- package/src/index.ts +50 -66
package/README.md
CHANGED
|
@@ -226,11 +226,16 @@ Template Usage
|
|
|
226
226
|
Use Angular's new control flow syntax for best performance:
|
|
227
227
|
|
|
228
228
|
@if (query.isReady()) {
|
|
229
|
+
@if (query.isSyncError()) {
|
|
230
|
+
<div>Data may be out of date</div>
|
|
231
|
+
}
|
|
229
232
|
@for (item of query.data(); track item.id) {
|
|
230
233
|
<div>{{ item.text }}</div>
|
|
231
234
|
}
|
|
232
|
-
} @else if (query.
|
|
235
|
+
} @else if (query.isLoadingError()) {
|
|
233
236
|
<div>Error loading data</div>
|
|
237
|
+
} @else if (query.isCrashed()) {
|
|
238
|
+
<div>Local storage failed</div>
|
|
234
239
|
} @else {
|
|
235
240
|
<div>Loading...</div>
|
|
236
241
|
}
|
|
@@ -274,12 +279,15 @@ An object with Angular signals:
|
|
|
274
279
|
- data: Signal<Array<T>> - Array of query results, automatically updates
|
|
275
280
|
- state: Signal<Map<Key, T>> - Map of results by key, automatically updates
|
|
276
281
|
- collection: Signal<Collection> - The underlying collection instance
|
|
277
|
-
- status: Signal<CollectionStatus> - Current status ('idle' | 'loading' | 'ready' | 'error' | 'cleaned-up')
|
|
282
|
+
- status: Signal<CollectionStatus | 'disabled'> - Current status ('idle' | 'loading' | 'ready' | 'error' | 'crashed' | 'cleaned-up' | 'disabled')
|
|
278
283
|
- isLoading: Signal<boolean> - true when status is 'loading'
|
|
279
284
|
- isReady: Signal<boolean> - true when status is 'ready'
|
|
280
285
|
- isIdle: Signal<boolean> - true when status is 'idle'
|
|
281
|
-
-
|
|
286
|
+
- isLoadingError: Signal<boolean> - true when status is 'error': the first load failed
|
|
287
|
+
- isCrashed: Signal<boolean> - true when status is 'crashed': local storage broke
|
|
282
288
|
- isCleanedUp: Signal<boolean> - true when status is 'cleaned-up'
|
|
289
|
+
- isSyncError: Signal<boolean> - true when status is 'ready' and the health tree has a problem
|
|
290
|
+
- health: Signal<HealthNode | undefined> - The health tree: one node for the query and one per source; undefined for a disabled query
|
|
283
291
|
|
|
284
292
|
Parameters
|
|
285
293
|
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -20,14 +20,15 @@ function injectLiveQuery(opts) {
|
|
|
20
20
|
}
|
|
21
21
|
const isReactiveQueryOptions = opts && typeof opts === `object` && typeof opts.query === `function` && typeof opts.params === `function`;
|
|
22
22
|
if (isReactiveQueryOptions) {
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
const pool = db.configPool(opts);
|
|
24
|
+
const result = opts.query({
|
|
25
|
+
params: opts.params(),
|
|
26
|
+
q: new db.BaseQueryBuilder()
|
|
27
|
+
});
|
|
28
|
+
return db.resolveLiveQueryValue(result, {
|
|
29
|
+
gcTime: DEFAULT_GC_TIME_MS,
|
|
30
|
+
pool
|
|
31
|
+
});
|
|
31
32
|
}
|
|
32
33
|
if (opts && typeof opts === `object` && typeof opts.query === `function`) {
|
|
33
34
|
return db.resolveLiveQueryValue(opts, { gcTime: DEFAULT_GC_TIME_MS });
|
|
@@ -41,8 +42,10 @@ function injectLiveQuery(opts) {
|
|
|
41
42
|
const value = statusValue();
|
|
42
43
|
return value === `idle` && !collection() ? `disabled` : value;
|
|
43
44
|
});
|
|
44
|
-
const
|
|
45
|
-
const
|
|
45
|
+
const health = core.signal(void 0);
|
|
46
|
+
const flags = core.computed(
|
|
47
|
+
() => db.getLiveQueryStatusFlags(status(), health())
|
|
48
|
+
);
|
|
46
49
|
const data = core.computed(() => {
|
|
47
50
|
const currentCollection = collection();
|
|
48
51
|
if (!currentCollection) {
|
|
@@ -57,8 +60,7 @@ function injectLiveQuery(opts) {
|
|
|
57
60
|
internalData.set(newData);
|
|
58
61
|
const snapshot = observer.getSnapshot();
|
|
59
62
|
statusValue.set(snapshot.status);
|
|
60
|
-
|
|
61
|
-
persistedError.set(snapshot.persistedError);
|
|
63
|
+
health.set(snapshot.health);
|
|
62
64
|
};
|
|
63
65
|
let unsub = null;
|
|
64
66
|
const cleanup = () => {
|
|
@@ -69,8 +71,7 @@ function injectLiveQuery(opts) {
|
|
|
69
71
|
const currentCollection = collection();
|
|
70
72
|
if (!currentCollection) {
|
|
71
73
|
statusValue.set(`disabled`);
|
|
72
|
-
|
|
73
|
-
persistedError.set(void 0);
|
|
74
|
+
health.set(void 0);
|
|
74
75
|
state.set(/* @__PURE__ */ new Map());
|
|
75
76
|
internalData.set([]);
|
|
76
77
|
cleanup();
|
|
@@ -100,14 +101,14 @@ function injectLiveQuery(opts) {
|
|
|
100
101
|
() => db.getPublicCollection(collection())
|
|
101
102
|
),
|
|
102
103
|
status,
|
|
103
|
-
isLoading: core.computed(() =>
|
|
104
|
-
isReady: core.computed(() =>
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
isLoading: core.computed(() => flags().isLoading),
|
|
105
|
+
isReady: core.computed(() => flags().isReady || status() === `disabled`),
|
|
106
|
+
isIdle: core.computed(() => flags().isIdle),
|
|
107
|
+
isLoadingError: core.computed(() => flags().isLoadingError),
|
|
108
|
+
isCrashed: core.computed(() => flags().isCrashed),
|
|
109
|
+
isCleanedUp: core.computed(() => flags().isCleanedUp),
|
|
110
|
+
isSyncError: core.computed(() => flags().isSyncError),
|
|
111
|
+
health
|
|
111
112
|
};
|
|
112
113
|
}
|
|
113
114
|
exports.injectLiveQuery = injectLiveQuery;
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../../src/index.ts"],"sourcesContent":["import {\n DestroyRef,\n assertInInjectionContext,\n computed,\n effect,\n inject,\n signal,\n} from '@angular/core'\nimport {\n BaseQueryBuilder,\n createLiveQueryObserver,\n getPublicCollection,\n isCollection,\n isSingleResultCollection,\n resolveLiveQueryValue,\n} from '@tanstack/db'\nimport type {\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n LiveQueryObserver,\n LiveQueryPersistedStatus,\n NonSingleResult,\n QueryBuilder,\n SingleResult,\n} from '@tanstack/db'\nimport type { Signal } from '@angular/core'\n\nconst DEFAULT_GC_TIME_MS = 1\n\nexport * from '@tanstack/db'\n\n/**\n * The result of calling `injectLiveQuery`.\n * Contains reactive signals for the query state and data.\n */\nexport interface InjectLiveQueryResult<TContext extends Context> {\n /** A signal containing the complete state map of results keyed by their ID */\n state: Signal<Map<string | number, GetResult<TContext>>>\n /** A signal containing the results as an array, or single result for findOne queries */\n data: Signal<InferResultType<TContext>>\n /** A signal containing the underlying collection instance (null for disabled queries) */\n collection: Signal<Collection<\n GetResult<TContext>,\n string | number,\n {}\n > | null>\n /** A signal containing the current status of the collection */\n status: Signal<CollectionStatus | `disabled`>\n /** A signal indicating whether the collection is currently loading */\n isLoading: Signal<boolean>\n /** A signal indicating whether the collection is ready */\n isReady: Signal<boolean>\n persistedStatus: Signal<LiveQueryPersistedStatus>\n isPersistedReady: Signal<boolean>\n persistedError: Signal<unknown | undefined>\n /** A signal indicating whether the collection is idle */\n isIdle: Signal<boolean>\n /** A signal indicating whether the collection has an error */\n isError: Signal<boolean>\n /** A signal indicating whether the collection has been cleaned up */\n isCleanedUp: Signal<boolean>\n}\n\ntype InferConditionalResultType<TContext extends Context> =\n TContext extends SingleResult\n ? InferResultType<TContext> | []\n : InferResultType<TContext>\n\nexport type InjectConditionalLiveQueryResult<TContext extends Context> = Omit<\n InjectLiveQueryResult<TContext>,\n `data`\n> & {\n data: Signal<InferConditionalResultType<TContext>>\n}\n\nexport interface InjectLiveQueryResultWithCollection<\n TResult extends object = any,\n TKey extends string | number = string | number,\n TUtils extends Record<string, any> = {},\n> {\n state: Signal<Map<TKey, TResult>>\n data: Signal<Array<TResult>>\n collection: Signal<Collection<TResult, TKey, TUtils> | null>\n status: Signal<CollectionStatus | `disabled`>\n isLoading: Signal<boolean>\n isReady: Signal<boolean>\n persistedStatus: Signal<LiveQueryPersistedStatus>\n isPersistedReady: Signal<boolean>\n persistedError: Signal<unknown | undefined>\n isIdle: Signal<boolean>\n isError: Signal<boolean>\n isCleanedUp: Signal<boolean>\n}\n\nexport interface InjectLiveQueryResultWithSingleResultCollection<\n TResult extends object = any,\n TKey extends string | number = string | number,\n TUtils extends Record<string, any> = {},\n> {\n state: Signal<Map<TKey, TResult>>\n data: Signal<TResult | undefined>\n collection: Signal<(Collection<TResult, TKey, TUtils> & SingleResult) | null>\n status: Signal<CollectionStatus | `disabled`>\n isLoading: Signal<boolean>\n isReady: Signal<boolean>\n persistedStatus: Signal<LiveQueryPersistedStatus>\n isPersistedReady: Signal<boolean>\n persistedError: Signal<unknown | undefined>\n isIdle: Signal<boolean>\n isError: Signal<boolean>\n isCleanedUp: Signal<boolean>\n}\n\nexport function injectLiveQuery<\n TContext extends Context,\n TParams extends any,\n>(options: {\n params: () => TParams\n query: (args: {\n params: TParams\n q: InitialQueryBuilder\n }) => QueryBuilder<TContext>\n}): InjectLiveQueryResult<TContext>\nexport function injectLiveQuery<\n TContext extends Context,\n TParams extends any,\n>(options: {\n params: () => TParams\n query: (args: {\n params: TParams\n q: InitialQueryBuilder\n }) => QueryBuilder<TContext> | undefined | null\n}): InjectConditionalLiveQueryResult<TContext>\nexport function injectLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n): InjectLiveQueryResult<TContext>\nexport function injectLiveQuery<TContext extends Context>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> | undefined | null,\n): InjectConditionalLiveQueryResult<TContext>\nexport function injectLiveQuery<TContext extends Context>(\n config: LiveQueryCollectionConfig<TContext>,\n): InjectLiveQueryResult<TContext>\n// Pre-created collection without singleResult\nexport function injectLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult,\n): InjectLiveQueryResultWithCollection<TResult, TKey, TUtils>\n// Pre-created collection with singleResult\nexport function injectLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & SingleResult,\n): InjectLiveQueryResultWithSingleResultCollection<TResult, TKey, TUtils>\nexport function injectLiveQuery(opts: any) {\n assertInInjectionContext(injectLiveQuery)\n const destroyRef = inject(DestroyRef)\n\n const collection = computed(() => {\n // Check if it's an existing collection\n if (isCollection(opts)) {\n return opts\n }\n\n if (typeof opts === `function`) {\n // Check if query function returns null/undefined (disabled query)\n const queryBuilder = new BaseQueryBuilder() as InitialQueryBuilder\n const result = opts(queryBuilder)\n\n if (result === undefined || result === null) {\n // Disabled query - return null\n return null\n }\n\n return resolveLiveQueryValue(result, { gcTime: DEFAULT_GC_TIME_MS })\n }\n\n // Check if it's reactive query options\n const isReactiveQueryOptions =\n opts &&\n typeof opts === `object` &&\n typeof opts.query === `function` &&\n typeof opts.params === `function`\n\n if (isReactiveQueryOptions) {\n const { params, query } = opts\n const currentParams = params()\n\n // Check if query function returns null/undefined (disabled query)\n const queryBuilder = new BaseQueryBuilder() as InitialQueryBuilder\n const result = query({ params: currentParams, q: queryBuilder })\n\n if (result === undefined || result === null) {\n // Disabled query - return null\n return null\n }\n\n return resolveLiveQueryValue(result, { gcTime: DEFAULT_GC_TIME_MS })\n }\n\n // Handle LiveQueryCollectionConfig objects. Default startSync/gcTime to\n // match the query-fn and reactive-options paths, but let an explicit value\n // in the config win — otherwise a bare `{ query }` never syncs.\n if (opts && typeof opts === `object` && typeof opts.query === `function`) {\n return resolveLiveQueryValue(opts, { gcTime: DEFAULT_GC_TIME_MS })\n }\n\n throw new Error(`Invalid options provided to injectLiveQuery`)\n })\n\n const state = signal(new Map<string | number, any>())\n const internalData = signal<Array<any>>([])\n const statusValue = signal<CollectionStatus | `disabled`>(`idle`)\n const status = computed(() => {\n const value = statusValue()\n return value === `idle` && !collection() ? `disabled` : value\n })\n const persistedStatus = signal<LiveQueryPersistedStatus>(`unavailable`)\n const persistedError = signal<unknown>(undefined)\n\n // Returns single item for singleResult collections, array otherwise\n const data = computed(() => {\n const currentCollection = collection()\n if (!currentCollection) {\n return internalData()\n }\n return isSingleResultCollection(currentCollection)\n ? internalData()[0]\n : internalData()\n })\n\n const syncDataFromCollection = (\n currentCollection: Collection<any, any, any>,\n observer: LiveQueryObserver<any, any>,\n ) => {\n const newState = new Map(currentCollection.entries())\n const newData = Array.from(newState.values())\n\n state.set(newState)\n internalData.set(newData)\n const snapshot = observer.getSnapshot()\n statusValue.set(snapshot.status)\n persistedStatus.set(snapshot.persistedStatus)\n persistedError.set(snapshot.persistedError)\n }\n\n let unsub: (() => void) | null = null\n const cleanup = () => {\n unsub?.()\n unsub = null\n }\n\n effect((onCleanup) => {\n const currentCollection = collection()\n\n // Handle null collection (disabled query)\n if (!currentCollection) {\n statusValue.set(`disabled` as const)\n persistedStatus.set(`unavailable`)\n persistedError.set(undefined)\n state.set(new Map())\n internalData.set([])\n cleanup()\n return\n }\n\n cleanup()\n\n // The shared observer owns sync start, subscription, the ready-race, and\n // status transitions; Angular re-reads the whole collection on each notify\n // (wholesale) into its signals.\n // Angular re-reads the collection on notify; wholesale mode preserves its\n // pre-observer loading policy (no initial-state snapshot request).\n const observer = createLiveQueryObserver(currentCollection, {\n mode: `wholesale`,\n })\n\n const unsubscribe = observer.subscribe(() => {\n syncDataFromCollection(currentCollection, observer)\n })\n // Wholesale attach suppresses listener calls raised by synchronous sync\n // startup. Read once after subscribe returns to capture that final state.\n syncDataFromCollection(currentCollection, observer)\n unsub = () => {\n unsubscribe()\n observer.dispose()\n }\n\n onCleanup(cleanup)\n })\n\n destroyRef.onDestroy(cleanup)\n\n return {\n state,\n data,\n // Loosely typed so the impl return stays compatible with every overload\n // (the shared `isCollection` guard narrows the computed to `Collection | null`).\n collection: computed(() =>\n getPublicCollection(collection()),\n ) as Signal<any>,\n status,\n isLoading: computed(() => status() === `loading`),\n isReady: computed(() => status() === `ready` || status() === `disabled`),\n persistedStatus,\n isPersistedReady: computed(() => persistedStatus() === `ready`),\n persistedError,\n isIdle: computed(() => status() === `idle`),\n isError: computed(() => status() === `error`),\n isCleanedUp: computed(() => status() === `cleaned-up`),\n }\n}\n"],"names":["assertInInjectionContext","inject","DestroyRef","computed","isCollection","BaseQueryBuilder","resolveLiveQueryValue","signal","isSingleResultCollection","effect","createLiveQueryObserver","getPublicCollection"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../../src/index.ts"],"sourcesContent":["import {\n DestroyRef,\n assertInInjectionContext,\n computed,\n effect,\n inject,\n signal,\n} from '@angular/core'\nimport {\n BaseQueryBuilder,\n configPool,\n createLiveQueryObserver,\n getLiveQueryStatusFlags,\n getPublicCollection,\n isCollection,\n isSingleResultCollection,\n resolveLiveQueryValue,\n} from '@tanstack/db'\nimport type {\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n HealthNode,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryHookConfig,\n LiveQueryObserver,\n LiveQueryStatusFlags,\n NonSingleResult,\n PooledLiveQueries,\n QueryBuilder,\n SingleResult,\n} from '@tanstack/db'\nimport type { Signal } from '@angular/core'\n\nconst DEFAULT_GC_TIME_MS = 1\n\nexport * from '@tanstack/db'\n\n/**\n * The result of calling `injectLiveQuery`.\n * Contains reactive signals for the query state and data.\n */\n/** The status and flag signals every `injectLiveQuery` result has. */\nexport type InjectLiveQueryFlags = {\n /** A signal containing the current status of the collection */\n status: Signal<CollectionStatus | `disabled`>\n} & {\n [K in keyof LiveQueryStatusFlags]: Signal<LiveQueryStatusFlags[K]>\n}\n\nexport interface InjectLiveQueryResult<\n TContext extends Context,\n> extends InjectLiveQueryFlags {\n /** A signal containing the complete state map of results keyed by their ID */\n state: Signal<Map<string | number, GetResult<TContext>>>\n /** A signal containing the results as an array, or single result for findOne queries */\n data: Signal<InferResultType<TContext>>\n /** A signal containing the underlying collection instance (null for disabled queries) */\n collection: Signal<Collection<\n GetResult<TContext>,\n string | number,\n {}\n > | null>\n}\n\ntype InferConditionalResultType<TContext extends Context> =\n TContext extends SingleResult\n ? InferResultType<TContext> | []\n : InferResultType<TContext>\n\nexport type InjectConditionalLiveQueryResult<TContext extends Context> = Omit<\n InjectLiveQueryResult<TContext>,\n `data`\n> & {\n data: Signal<InferConditionalResultType<TContext>>\n}\n\nexport interface InjectLiveQueryResultWithCollection<\n TResult extends object = any,\n TKey extends string | number = string | number,\n TUtils extends Record<string, any> = {},\n> extends InjectLiveQueryFlags {\n state: Signal<Map<TKey, TResult>>\n data: Signal<Array<TResult>>\n collection: Signal<Collection<TResult, TKey, TUtils> | null>\n}\n\nexport interface InjectLiveQueryResultWithSingleResultCollection<\n TResult extends object = any,\n TKey extends string | number = string | number,\n TUtils extends Record<string, any> = {},\n> extends InjectLiveQueryFlags {\n state: Signal<Map<TKey, TResult>>\n data: Signal<TResult | undefined>\n collection: Signal<(Collection<TResult, TKey, TUtils> & SingleResult) | null>\n}\n\nexport function injectLiveQuery<\n TContext extends Context,\n TParams extends any,\n>(options: {\n params: () => TParams\n /** Pass `pooledLiveQueries` to share one partition among equal-shape queries. */\n pool?: PooledLiveQueries\n query: (args: {\n params: TParams\n q: InitialQueryBuilder\n }) => QueryBuilder<TContext>\n}): InjectLiveQueryResult<TContext>\nexport function injectLiveQuery<\n TContext extends Context,\n TParams extends any,\n>(options: {\n params: () => TParams\n /** Pass `pooledLiveQueries` to share one partition among equal-shape queries. */\n pool?: PooledLiveQueries\n query: (args: {\n params: TParams\n q: InitialQueryBuilder\n }) => QueryBuilder<TContext> | undefined | null\n}): InjectConditionalLiveQueryResult<TContext>\nexport function injectLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n): InjectLiveQueryResult<TContext>\nexport function injectLiveQuery<TContext extends Context>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> | undefined | null,\n): InjectConditionalLiveQueryResult<TContext>\nexport function injectLiveQuery<TContext extends Context>(\n config: LiveQueryHookConfig<TContext>,\n): InjectLiveQueryResult<TContext>\n// Pre-created collection without singleResult\nexport function injectLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult,\n): InjectLiveQueryResultWithCollection<TResult, TKey, TUtils>\n// Pre-created collection with singleResult\nexport function injectLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & SingleResult,\n): InjectLiveQueryResultWithSingleResultCollection<TResult, TKey, TUtils>\nexport function injectLiveQuery(opts: any) {\n assertInInjectionContext(injectLiveQuery)\n const destroyRef = inject(DestroyRef)\n\n const collection = computed(() => {\n // Check if it's an existing collection\n if (isCollection(opts)) {\n return opts\n }\n\n if (typeof opts === `function`) {\n // Check if query function returns null/undefined (disabled query)\n const queryBuilder = new BaseQueryBuilder() as InitialQueryBuilder\n const result = opts(queryBuilder)\n\n if (result === undefined || result === null) {\n // Disabled query - return null\n return null\n }\n\n return resolveLiveQueryValue(result, { gcTime: DEFAULT_GC_TIME_MS })\n }\n\n // Check if it's reactive query options\n const isReactiveQueryOptions =\n opts &&\n typeof opts === `object` &&\n typeof opts.query === `function` &&\n typeof opts.params === `function`\n\n if (isReactiveQueryOptions) {\n // The pool is checked first, so a disabled query rejects it too.\n const pool = configPool(opts)\n // A query function returning null or undefined disables the query.\n const result = opts.query({\n params: opts.params(),\n q: new BaseQueryBuilder() as InitialQueryBuilder,\n })\n return resolveLiveQueryValue(result, {\n gcTime: DEFAULT_GC_TIME_MS,\n pool,\n })\n }\n\n // Handle LiveQueryCollectionConfig objects. Default startSync/gcTime to\n // match the query-fn and reactive-options paths, but let an explicit value\n // in the config win — otherwise a bare `{ query }` never syncs.\n if (opts && typeof opts === `object` && typeof opts.query === `function`) {\n return resolveLiveQueryValue(opts, { gcTime: DEFAULT_GC_TIME_MS })\n }\n\n throw new Error(`Invalid options provided to injectLiveQuery`)\n })\n\n const state = signal(new Map<string | number, any>())\n const internalData = signal<Array<any>>([])\n const statusValue = signal<CollectionStatus | `disabled`>(`idle`)\n const status = computed(() => {\n const value = statusValue()\n return value === `idle` && !collection() ? `disabled` : value\n })\n const health = signal<HealthNode | undefined>(undefined)\n // Every status flag comes from the shared rule; a disabled query is ready.\n const flags = computed(() =>\n getLiveQueryStatusFlags(status() as CollectionStatus, health()),\n )\n\n // Returns single item for singleResult collections, array otherwise\n const data = computed(() => {\n const currentCollection = collection()\n if (!currentCollection) {\n return internalData()\n }\n return isSingleResultCollection(currentCollection)\n ? internalData()[0]\n : internalData()\n })\n\n const syncDataFromCollection = (\n currentCollection: Collection<any, any, any>,\n observer: LiveQueryObserver<any, any>,\n ) => {\n const newState = new Map(currentCollection.entries())\n const newData = Array.from(newState.values())\n\n state.set(newState)\n internalData.set(newData)\n const snapshot = observer.getSnapshot()\n statusValue.set(snapshot.status)\n health.set(snapshot.health)\n }\n\n let unsub: (() => void) | null = null\n const cleanup = () => {\n unsub?.()\n unsub = null\n }\n\n effect((onCleanup) => {\n const currentCollection = collection()\n\n // Handle null collection (disabled query)\n if (!currentCollection) {\n statusValue.set(`disabled` as const)\n health.set(undefined)\n state.set(new Map())\n internalData.set([])\n cleanup()\n return\n }\n\n cleanup()\n\n // The shared observer owns sync start, subscription, the ready-race, and\n // status transitions; Angular re-reads the whole collection on each notify\n // (wholesale) into its signals.\n // Angular re-reads the collection on notify; wholesale mode preserves its\n // pre-observer loading policy (no initial-state snapshot request).\n const observer = createLiveQueryObserver(currentCollection, {\n mode: `wholesale`,\n })\n\n const unsubscribe = observer.subscribe(() => {\n syncDataFromCollection(currentCollection, observer)\n })\n // Wholesale attach suppresses listener calls raised by synchronous sync\n // startup. Read once after subscribe returns to capture that final state.\n syncDataFromCollection(currentCollection, observer)\n unsub = () => {\n unsubscribe()\n observer.dispose()\n }\n\n onCleanup(cleanup)\n })\n\n destroyRef.onDestroy(cleanup)\n\n return {\n state,\n data,\n // Loosely typed so the impl return stays compatible with every overload\n // (the shared `isCollection` guard narrows the computed to `Collection | null`).\n collection: computed(() =>\n getPublicCollection(collection()),\n ) as Signal<any>,\n status,\n isLoading: computed(() => flags().isLoading),\n isReady: computed(() => flags().isReady || status() === `disabled`),\n isIdle: computed(() => flags().isIdle),\n isLoadingError: computed(() => flags().isLoadingError),\n isCrashed: computed(() => flags().isCrashed),\n isCleanedUp: computed(() => flags().isCleanedUp),\n isSyncError: computed(() => flags().isSyncError),\n health,\n }\n}\n"],"names":["assertInInjectionContext","inject","DestroyRef","computed","isCollection","BaseQueryBuilder","resolveLiveQueryValue","configPool","signal","getLiveQueryStatusFlags","isSingleResultCollection","effect","createLiveQueryObserver","getPublicCollection"],"mappings":"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"}
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -1,67 +1,43 @@
|
|
|
1
|
-
import { Collection, CollectionStatus, Context, GetResult, InferResultType, InitialQueryBuilder,
|
|
1
|
+
import { Collection, CollectionStatus, Context, GetResult, InferResultType, InitialQueryBuilder, LiveQueryHookConfig, LiveQueryStatusFlags, NonSingleResult, PooledLiveQueries, QueryBuilder, SingleResult } from '@tanstack/db';
|
|
2
2
|
import { Signal } from '@angular/core';
|
|
3
3
|
export * from '@tanstack/db';
|
|
4
4
|
/**
|
|
5
5
|
* The result of calling `injectLiveQuery`.
|
|
6
6
|
* Contains reactive signals for the query state and data.
|
|
7
7
|
*/
|
|
8
|
-
|
|
8
|
+
/** The status and flag signals every `injectLiveQuery` result has. */
|
|
9
|
+
export type InjectLiveQueryFlags = {
|
|
10
|
+
/** A signal containing the current status of the collection */
|
|
11
|
+
status: Signal<CollectionStatus | `disabled`>;
|
|
12
|
+
} & {
|
|
13
|
+
[K in keyof LiveQueryStatusFlags]: Signal<LiveQueryStatusFlags[K]>;
|
|
14
|
+
};
|
|
15
|
+
export interface InjectLiveQueryResult<TContext extends Context> extends InjectLiveQueryFlags {
|
|
9
16
|
/** A signal containing the complete state map of results keyed by their ID */
|
|
10
17
|
state: Signal<Map<string | number, GetResult<TContext>>>;
|
|
11
18
|
/** A signal containing the results as an array, or single result for findOne queries */
|
|
12
19
|
data: Signal<InferResultType<TContext>>;
|
|
13
20
|
/** A signal containing the underlying collection instance (null for disabled queries) */
|
|
14
21
|
collection: Signal<Collection<GetResult<TContext>, string | number, {}> | null>;
|
|
15
|
-
/** A signal containing the current status of the collection */
|
|
16
|
-
status: Signal<CollectionStatus | `disabled`>;
|
|
17
|
-
/** A signal indicating whether the collection is currently loading */
|
|
18
|
-
isLoading: Signal<boolean>;
|
|
19
|
-
/** A signal indicating whether the collection is ready */
|
|
20
|
-
isReady: Signal<boolean>;
|
|
21
|
-
persistedStatus: Signal<LiveQueryPersistedStatus>;
|
|
22
|
-
isPersistedReady: Signal<boolean>;
|
|
23
|
-
persistedError: Signal<unknown | undefined>;
|
|
24
|
-
/** A signal indicating whether the collection is idle */
|
|
25
|
-
isIdle: Signal<boolean>;
|
|
26
|
-
/** A signal indicating whether the collection has an error */
|
|
27
|
-
isError: Signal<boolean>;
|
|
28
|
-
/** A signal indicating whether the collection has been cleaned up */
|
|
29
|
-
isCleanedUp: Signal<boolean>;
|
|
30
22
|
}
|
|
31
23
|
type InferConditionalResultType<TContext extends Context> = TContext extends SingleResult ? InferResultType<TContext> | [] : InferResultType<TContext>;
|
|
32
24
|
export type InjectConditionalLiveQueryResult<TContext extends Context> = Omit<InjectLiveQueryResult<TContext>, `data`> & {
|
|
33
25
|
data: Signal<InferConditionalResultType<TContext>>;
|
|
34
26
|
};
|
|
35
|
-
export interface InjectLiveQueryResultWithCollection<TResult extends object = any, TKey extends string | number = string | number, TUtils extends Record<string, any> = {}> {
|
|
27
|
+
export interface InjectLiveQueryResultWithCollection<TResult extends object = any, TKey extends string | number = string | number, TUtils extends Record<string, any> = {}> extends InjectLiveQueryFlags {
|
|
36
28
|
state: Signal<Map<TKey, TResult>>;
|
|
37
29
|
data: Signal<Array<TResult>>;
|
|
38
30
|
collection: Signal<Collection<TResult, TKey, TUtils> | null>;
|
|
39
|
-
status: Signal<CollectionStatus | `disabled`>;
|
|
40
|
-
isLoading: Signal<boolean>;
|
|
41
|
-
isReady: Signal<boolean>;
|
|
42
|
-
persistedStatus: Signal<LiveQueryPersistedStatus>;
|
|
43
|
-
isPersistedReady: Signal<boolean>;
|
|
44
|
-
persistedError: Signal<unknown | undefined>;
|
|
45
|
-
isIdle: Signal<boolean>;
|
|
46
|
-
isError: Signal<boolean>;
|
|
47
|
-
isCleanedUp: Signal<boolean>;
|
|
48
31
|
}
|
|
49
|
-
export interface InjectLiveQueryResultWithSingleResultCollection<TResult extends object = any, TKey extends string | number = string | number, TUtils extends Record<string, any> = {}> {
|
|
32
|
+
export interface InjectLiveQueryResultWithSingleResultCollection<TResult extends object = any, TKey extends string | number = string | number, TUtils extends Record<string, any> = {}> extends InjectLiveQueryFlags {
|
|
50
33
|
state: Signal<Map<TKey, TResult>>;
|
|
51
34
|
data: Signal<TResult | undefined>;
|
|
52
35
|
collection: Signal<(Collection<TResult, TKey, TUtils> & SingleResult) | null>;
|
|
53
|
-
status: Signal<CollectionStatus | `disabled`>;
|
|
54
|
-
isLoading: Signal<boolean>;
|
|
55
|
-
isReady: Signal<boolean>;
|
|
56
|
-
persistedStatus: Signal<LiveQueryPersistedStatus>;
|
|
57
|
-
isPersistedReady: Signal<boolean>;
|
|
58
|
-
persistedError: Signal<unknown | undefined>;
|
|
59
|
-
isIdle: Signal<boolean>;
|
|
60
|
-
isError: Signal<boolean>;
|
|
61
|
-
isCleanedUp: Signal<boolean>;
|
|
62
36
|
}
|
|
63
37
|
export declare function injectLiveQuery<TContext extends Context, TParams extends any>(options: {
|
|
64
38
|
params: () => TParams;
|
|
39
|
+
/** Pass `pooledLiveQueries` to share one partition among equal-shape queries. */
|
|
40
|
+
pool?: PooledLiveQueries;
|
|
65
41
|
query: (args: {
|
|
66
42
|
params: TParams;
|
|
67
43
|
q: InitialQueryBuilder;
|
|
@@ -69,6 +45,8 @@ export declare function injectLiveQuery<TContext extends Context, TParams extend
|
|
|
69
45
|
}): InjectLiveQueryResult<TContext>;
|
|
70
46
|
export declare function injectLiveQuery<TContext extends Context, TParams extends any>(options: {
|
|
71
47
|
params: () => TParams;
|
|
48
|
+
/** Pass `pooledLiveQueries` to share one partition among equal-shape queries. */
|
|
49
|
+
pool?: PooledLiveQueries;
|
|
72
50
|
query: (args: {
|
|
73
51
|
params: TParams;
|
|
74
52
|
q: InitialQueryBuilder;
|
|
@@ -76,6 +54,6 @@ export declare function injectLiveQuery<TContext extends Context, TParams extend
|
|
|
76
54
|
}): InjectConditionalLiveQueryResult<TContext>;
|
|
77
55
|
export declare function injectLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>): InjectLiveQueryResult<TContext>;
|
|
78
56
|
export declare function injectLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext> | undefined | null): InjectConditionalLiveQueryResult<TContext>;
|
|
79
|
-
export declare function injectLiveQuery<TContext extends Context>(config:
|
|
57
|
+
export declare function injectLiveQuery<TContext extends Context>(config: LiveQueryHookConfig<TContext>): InjectLiveQueryResult<TContext>;
|
|
80
58
|
export declare function injectLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult): InjectLiveQueryResultWithCollection<TResult, TKey, TUtils>;
|
|
81
59
|
export declare function injectLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: Collection<TResult, TKey, TUtils> & SingleResult): InjectLiveQueryResultWithSingleResultCollection<TResult, TKey, TUtils>;
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,67 +1,43 @@
|
|
|
1
|
-
import { Collection, CollectionStatus, Context, GetResult, InferResultType, InitialQueryBuilder,
|
|
1
|
+
import { Collection, CollectionStatus, Context, GetResult, InferResultType, InitialQueryBuilder, LiveQueryHookConfig, LiveQueryStatusFlags, NonSingleResult, PooledLiveQueries, QueryBuilder, SingleResult } from '@tanstack/db';
|
|
2
2
|
import { Signal } from '@angular/core';
|
|
3
3
|
export * from '@tanstack/db';
|
|
4
4
|
/**
|
|
5
5
|
* The result of calling `injectLiveQuery`.
|
|
6
6
|
* Contains reactive signals for the query state and data.
|
|
7
7
|
*/
|
|
8
|
-
|
|
8
|
+
/** The status and flag signals every `injectLiveQuery` result has. */
|
|
9
|
+
export type InjectLiveQueryFlags = {
|
|
10
|
+
/** A signal containing the current status of the collection */
|
|
11
|
+
status: Signal<CollectionStatus | `disabled`>;
|
|
12
|
+
} & {
|
|
13
|
+
[K in keyof LiveQueryStatusFlags]: Signal<LiveQueryStatusFlags[K]>;
|
|
14
|
+
};
|
|
15
|
+
export interface InjectLiveQueryResult<TContext extends Context> extends InjectLiveQueryFlags {
|
|
9
16
|
/** A signal containing the complete state map of results keyed by their ID */
|
|
10
17
|
state: Signal<Map<string | number, GetResult<TContext>>>;
|
|
11
18
|
/** A signal containing the results as an array, or single result for findOne queries */
|
|
12
19
|
data: Signal<InferResultType<TContext>>;
|
|
13
20
|
/** A signal containing the underlying collection instance (null for disabled queries) */
|
|
14
21
|
collection: Signal<Collection<GetResult<TContext>, string | number, {}> | null>;
|
|
15
|
-
/** A signal containing the current status of the collection */
|
|
16
|
-
status: Signal<CollectionStatus | `disabled`>;
|
|
17
|
-
/** A signal indicating whether the collection is currently loading */
|
|
18
|
-
isLoading: Signal<boolean>;
|
|
19
|
-
/** A signal indicating whether the collection is ready */
|
|
20
|
-
isReady: Signal<boolean>;
|
|
21
|
-
persistedStatus: Signal<LiveQueryPersistedStatus>;
|
|
22
|
-
isPersistedReady: Signal<boolean>;
|
|
23
|
-
persistedError: Signal<unknown | undefined>;
|
|
24
|
-
/** A signal indicating whether the collection is idle */
|
|
25
|
-
isIdle: Signal<boolean>;
|
|
26
|
-
/** A signal indicating whether the collection has an error */
|
|
27
|
-
isError: Signal<boolean>;
|
|
28
|
-
/** A signal indicating whether the collection has been cleaned up */
|
|
29
|
-
isCleanedUp: Signal<boolean>;
|
|
30
22
|
}
|
|
31
23
|
type InferConditionalResultType<TContext extends Context> = TContext extends SingleResult ? InferResultType<TContext> | [] : InferResultType<TContext>;
|
|
32
24
|
export type InjectConditionalLiveQueryResult<TContext extends Context> = Omit<InjectLiveQueryResult<TContext>, `data`> & {
|
|
33
25
|
data: Signal<InferConditionalResultType<TContext>>;
|
|
34
26
|
};
|
|
35
|
-
export interface InjectLiveQueryResultWithCollection<TResult extends object = any, TKey extends string | number = string | number, TUtils extends Record<string, any> = {}> {
|
|
27
|
+
export interface InjectLiveQueryResultWithCollection<TResult extends object = any, TKey extends string | number = string | number, TUtils extends Record<string, any> = {}> extends InjectLiveQueryFlags {
|
|
36
28
|
state: Signal<Map<TKey, TResult>>;
|
|
37
29
|
data: Signal<Array<TResult>>;
|
|
38
30
|
collection: Signal<Collection<TResult, TKey, TUtils> | null>;
|
|
39
|
-
status: Signal<CollectionStatus | `disabled`>;
|
|
40
|
-
isLoading: Signal<boolean>;
|
|
41
|
-
isReady: Signal<boolean>;
|
|
42
|
-
persistedStatus: Signal<LiveQueryPersistedStatus>;
|
|
43
|
-
isPersistedReady: Signal<boolean>;
|
|
44
|
-
persistedError: Signal<unknown | undefined>;
|
|
45
|
-
isIdle: Signal<boolean>;
|
|
46
|
-
isError: Signal<boolean>;
|
|
47
|
-
isCleanedUp: Signal<boolean>;
|
|
48
31
|
}
|
|
49
|
-
export interface InjectLiveQueryResultWithSingleResultCollection<TResult extends object = any, TKey extends string | number = string | number, TUtils extends Record<string, any> = {}> {
|
|
32
|
+
export interface InjectLiveQueryResultWithSingleResultCollection<TResult extends object = any, TKey extends string | number = string | number, TUtils extends Record<string, any> = {}> extends InjectLiveQueryFlags {
|
|
50
33
|
state: Signal<Map<TKey, TResult>>;
|
|
51
34
|
data: Signal<TResult | undefined>;
|
|
52
35
|
collection: Signal<(Collection<TResult, TKey, TUtils> & SingleResult) | null>;
|
|
53
|
-
status: Signal<CollectionStatus | `disabled`>;
|
|
54
|
-
isLoading: Signal<boolean>;
|
|
55
|
-
isReady: Signal<boolean>;
|
|
56
|
-
persistedStatus: Signal<LiveQueryPersistedStatus>;
|
|
57
|
-
isPersistedReady: Signal<boolean>;
|
|
58
|
-
persistedError: Signal<unknown | undefined>;
|
|
59
|
-
isIdle: Signal<boolean>;
|
|
60
|
-
isError: Signal<boolean>;
|
|
61
|
-
isCleanedUp: Signal<boolean>;
|
|
62
36
|
}
|
|
63
37
|
export declare function injectLiveQuery<TContext extends Context, TParams extends any>(options: {
|
|
64
38
|
params: () => TParams;
|
|
39
|
+
/** Pass `pooledLiveQueries` to share one partition among equal-shape queries. */
|
|
40
|
+
pool?: PooledLiveQueries;
|
|
65
41
|
query: (args: {
|
|
66
42
|
params: TParams;
|
|
67
43
|
q: InitialQueryBuilder;
|
|
@@ -69,6 +45,8 @@ export declare function injectLiveQuery<TContext extends Context, TParams extend
|
|
|
69
45
|
}): InjectLiveQueryResult<TContext>;
|
|
70
46
|
export declare function injectLiveQuery<TContext extends Context, TParams extends any>(options: {
|
|
71
47
|
params: () => TParams;
|
|
48
|
+
/** Pass `pooledLiveQueries` to share one partition among equal-shape queries. */
|
|
49
|
+
pool?: PooledLiveQueries;
|
|
72
50
|
query: (args: {
|
|
73
51
|
params: TParams;
|
|
74
52
|
q: InitialQueryBuilder;
|
|
@@ -76,6 +54,6 @@ export declare function injectLiveQuery<TContext extends Context, TParams extend
|
|
|
76
54
|
}): InjectConditionalLiveQueryResult<TContext>;
|
|
77
55
|
export declare function injectLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>): InjectLiveQueryResult<TContext>;
|
|
78
56
|
export declare function injectLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext> | undefined | null): InjectConditionalLiveQueryResult<TContext>;
|
|
79
|
-
export declare function injectLiveQuery<TContext extends Context>(config:
|
|
57
|
+
export declare function injectLiveQuery<TContext extends Context>(config: LiveQueryHookConfig<TContext>): InjectLiveQueryResult<TContext>;
|
|
80
58
|
export declare function injectLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult): InjectLiveQueryResultWithCollection<TResult, TKey, TUtils>;
|
|
81
59
|
export declare function injectLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: Collection<TResult, TKey, TUtils> & SingleResult): InjectLiveQueryResultWithSingleResultCollection<TResult, TKey, TUtils>;
|
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assertInInjectionContext, inject, DestroyRef, computed, signal, effect } from "@angular/core";
|
|
2
|
-
import { isCollection, BaseQueryBuilder, resolveLiveQueryValue, isSingleResultCollection, createLiveQueryObserver, getPublicCollection } from "@tanstack/db";
|
|
2
|
+
import { isCollection, BaseQueryBuilder, resolveLiveQueryValue, configPool, getLiveQueryStatusFlags, isSingleResultCollection, createLiveQueryObserver, getPublicCollection } from "@tanstack/db";
|
|
3
3
|
export * from "@tanstack/db";
|
|
4
4
|
const DEFAULT_GC_TIME_MS = 1;
|
|
5
5
|
function injectLiveQuery(opts) {
|
|
@@ -19,14 +19,15 @@ function injectLiveQuery(opts) {
|
|
|
19
19
|
}
|
|
20
20
|
const isReactiveQueryOptions = opts && typeof opts === `object` && typeof opts.query === `function` && typeof opts.params === `function`;
|
|
21
21
|
if (isReactiveQueryOptions) {
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
const pool = configPool(opts);
|
|
23
|
+
const result = opts.query({
|
|
24
|
+
params: opts.params(),
|
|
25
|
+
q: new BaseQueryBuilder()
|
|
26
|
+
});
|
|
27
|
+
return resolveLiveQueryValue(result, {
|
|
28
|
+
gcTime: DEFAULT_GC_TIME_MS,
|
|
29
|
+
pool
|
|
30
|
+
});
|
|
30
31
|
}
|
|
31
32
|
if (opts && typeof opts === `object` && typeof opts.query === `function`) {
|
|
32
33
|
return resolveLiveQueryValue(opts, { gcTime: DEFAULT_GC_TIME_MS });
|
|
@@ -40,8 +41,10 @@ function injectLiveQuery(opts) {
|
|
|
40
41
|
const value = statusValue();
|
|
41
42
|
return value === `idle` && !collection() ? `disabled` : value;
|
|
42
43
|
});
|
|
43
|
-
const
|
|
44
|
-
const
|
|
44
|
+
const health = signal(void 0);
|
|
45
|
+
const flags = computed(
|
|
46
|
+
() => getLiveQueryStatusFlags(status(), health())
|
|
47
|
+
);
|
|
45
48
|
const data = computed(() => {
|
|
46
49
|
const currentCollection = collection();
|
|
47
50
|
if (!currentCollection) {
|
|
@@ -56,8 +59,7 @@ function injectLiveQuery(opts) {
|
|
|
56
59
|
internalData.set(newData);
|
|
57
60
|
const snapshot = observer.getSnapshot();
|
|
58
61
|
statusValue.set(snapshot.status);
|
|
59
|
-
|
|
60
|
-
persistedError.set(snapshot.persistedError);
|
|
62
|
+
health.set(snapshot.health);
|
|
61
63
|
};
|
|
62
64
|
let unsub = null;
|
|
63
65
|
const cleanup = () => {
|
|
@@ -68,8 +70,7 @@ function injectLiveQuery(opts) {
|
|
|
68
70
|
const currentCollection = collection();
|
|
69
71
|
if (!currentCollection) {
|
|
70
72
|
statusValue.set(`disabled`);
|
|
71
|
-
|
|
72
|
-
persistedError.set(void 0);
|
|
73
|
+
health.set(void 0);
|
|
73
74
|
state.set(/* @__PURE__ */ new Map());
|
|
74
75
|
internalData.set([]);
|
|
75
76
|
cleanup();
|
|
@@ -99,14 +100,14 @@ function injectLiveQuery(opts) {
|
|
|
99
100
|
() => getPublicCollection(collection())
|
|
100
101
|
),
|
|
101
102
|
status,
|
|
102
|
-
isLoading: computed(() =>
|
|
103
|
-
isReady: computed(() =>
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
isLoading: computed(() => flags().isLoading),
|
|
104
|
+
isReady: computed(() => flags().isReady || status() === `disabled`),
|
|
105
|
+
isIdle: computed(() => flags().isIdle),
|
|
106
|
+
isLoadingError: computed(() => flags().isLoadingError),
|
|
107
|
+
isCrashed: computed(() => flags().isCrashed),
|
|
108
|
+
isCleanedUp: computed(() => flags().isCleanedUp),
|
|
109
|
+
isSyncError: computed(() => flags().isSyncError),
|
|
110
|
+
health
|
|
110
111
|
};
|
|
111
112
|
}
|
|
112
113
|
export {
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../src/index.ts"],"sourcesContent":["import {\n DestroyRef,\n assertInInjectionContext,\n computed,\n effect,\n inject,\n signal,\n} from '@angular/core'\nimport {\n BaseQueryBuilder,\n createLiveQueryObserver,\n getPublicCollection,\n isCollection,\n isSingleResultCollection,\n resolveLiveQueryValue,\n} from '@tanstack/db'\nimport type {\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n LiveQueryObserver,\n LiveQueryPersistedStatus,\n NonSingleResult,\n QueryBuilder,\n SingleResult,\n} from '@tanstack/db'\nimport type { Signal } from '@angular/core'\n\nconst DEFAULT_GC_TIME_MS = 1\n\nexport * from '@tanstack/db'\n\n/**\n * The result of calling `injectLiveQuery`.\n * Contains reactive signals for the query state and data.\n */\nexport interface InjectLiveQueryResult<TContext extends Context> {\n /** A signal containing the complete state map of results keyed by their ID */\n state: Signal<Map<string | number, GetResult<TContext>>>\n /** A signal containing the results as an array, or single result for findOne queries */\n data: Signal<InferResultType<TContext>>\n /** A signal containing the underlying collection instance (null for disabled queries) */\n collection: Signal<Collection<\n GetResult<TContext>,\n string | number,\n {}\n > | null>\n /** A signal containing the current status of the collection */\n status: Signal<CollectionStatus | `disabled`>\n /** A signal indicating whether the collection is currently loading */\n isLoading: Signal<boolean>\n /** A signal indicating whether the collection is ready */\n isReady: Signal<boolean>\n persistedStatus: Signal<LiveQueryPersistedStatus>\n isPersistedReady: Signal<boolean>\n persistedError: Signal<unknown | undefined>\n /** A signal indicating whether the collection is idle */\n isIdle: Signal<boolean>\n /** A signal indicating whether the collection has an error */\n isError: Signal<boolean>\n /** A signal indicating whether the collection has been cleaned up */\n isCleanedUp: Signal<boolean>\n}\n\ntype InferConditionalResultType<TContext extends Context> =\n TContext extends SingleResult\n ? InferResultType<TContext> | []\n : InferResultType<TContext>\n\nexport type InjectConditionalLiveQueryResult<TContext extends Context> = Omit<\n InjectLiveQueryResult<TContext>,\n `data`\n> & {\n data: Signal<InferConditionalResultType<TContext>>\n}\n\nexport interface InjectLiveQueryResultWithCollection<\n TResult extends object = any,\n TKey extends string | number = string | number,\n TUtils extends Record<string, any> = {},\n> {\n state: Signal<Map<TKey, TResult>>\n data: Signal<Array<TResult>>\n collection: Signal<Collection<TResult, TKey, TUtils> | null>\n status: Signal<CollectionStatus | `disabled`>\n isLoading: Signal<boolean>\n isReady: Signal<boolean>\n persistedStatus: Signal<LiveQueryPersistedStatus>\n isPersistedReady: Signal<boolean>\n persistedError: Signal<unknown | undefined>\n isIdle: Signal<boolean>\n isError: Signal<boolean>\n isCleanedUp: Signal<boolean>\n}\n\nexport interface InjectLiveQueryResultWithSingleResultCollection<\n TResult extends object = any,\n TKey extends string | number = string | number,\n TUtils extends Record<string, any> = {},\n> {\n state: Signal<Map<TKey, TResult>>\n data: Signal<TResult | undefined>\n collection: Signal<(Collection<TResult, TKey, TUtils> & SingleResult) | null>\n status: Signal<CollectionStatus | `disabled`>\n isLoading: Signal<boolean>\n isReady: Signal<boolean>\n persistedStatus: Signal<LiveQueryPersistedStatus>\n isPersistedReady: Signal<boolean>\n persistedError: Signal<unknown | undefined>\n isIdle: Signal<boolean>\n isError: Signal<boolean>\n isCleanedUp: Signal<boolean>\n}\n\nexport function injectLiveQuery<\n TContext extends Context,\n TParams extends any,\n>(options: {\n params: () => TParams\n query: (args: {\n params: TParams\n q: InitialQueryBuilder\n }) => QueryBuilder<TContext>\n}): InjectLiveQueryResult<TContext>\nexport function injectLiveQuery<\n TContext extends Context,\n TParams extends any,\n>(options: {\n params: () => TParams\n query: (args: {\n params: TParams\n q: InitialQueryBuilder\n }) => QueryBuilder<TContext> | undefined | null\n}): InjectConditionalLiveQueryResult<TContext>\nexport function injectLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n): InjectLiveQueryResult<TContext>\nexport function injectLiveQuery<TContext extends Context>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> | undefined | null,\n): InjectConditionalLiveQueryResult<TContext>\nexport function injectLiveQuery<TContext extends Context>(\n config: LiveQueryCollectionConfig<TContext>,\n): InjectLiveQueryResult<TContext>\n// Pre-created collection without singleResult\nexport function injectLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult,\n): InjectLiveQueryResultWithCollection<TResult, TKey, TUtils>\n// Pre-created collection with singleResult\nexport function injectLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & SingleResult,\n): InjectLiveQueryResultWithSingleResultCollection<TResult, TKey, TUtils>\nexport function injectLiveQuery(opts: any) {\n assertInInjectionContext(injectLiveQuery)\n const destroyRef = inject(DestroyRef)\n\n const collection = computed(() => {\n // Check if it's an existing collection\n if (isCollection(opts)) {\n return opts\n }\n\n if (typeof opts === `function`) {\n // Check if query function returns null/undefined (disabled query)\n const queryBuilder = new BaseQueryBuilder() as InitialQueryBuilder\n const result = opts(queryBuilder)\n\n if (result === undefined || result === null) {\n // Disabled query - return null\n return null\n }\n\n return resolveLiveQueryValue(result, { gcTime: DEFAULT_GC_TIME_MS })\n }\n\n // Check if it's reactive query options\n const isReactiveQueryOptions =\n opts &&\n typeof opts === `object` &&\n typeof opts.query === `function` &&\n typeof opts.params === `function`\n\n if (isReactiveQueryOptions) {\n const { params, query } = opts\n const currentParams = params()\n\n // Check if query function returns null/undefined (disabled query)\n const queryBuilder = new BaseQueryBuilder() as InitialQueryBuilder\n const result = query({ params: currentParams, q: queryBuilder })\n\n if (result === undefined || result === null) {\n // Disabled query - return null\n return null\n }\n\n return resolveLiveQueryValue(result, { gcTime: DEFAULT_GC_TIME_MS })\n }\n\n // Handle LiveQueryCollectionConfig objects. Default startSync/gcTime to\n // match the query-fn and reactive-options paths, but let an explicit value\n // in the config win — otherwise a bare `{ query }` never syncs.\n if (opts && typeof opts === `object` && typeof opts.query === `function`) {\n return resolveLiveQueryValue(opts, { gcTime: DEFAULT_GC_TIME_MS })\n }\n\n throw new Error(`Invalid options provided to injectLiveQuery`)\n })\n\n const state = signal(new Map<string | number, any>())\n const internalData = signal<Array<any>>([])\n const statusValue = signal<CollectionStatus | `disabled`>(`idle`)\n const status = computed(() => {\n const value = statusValue()\n return value === `idle` && !collection() ? `disabled` : value\n })\n const persistedStatus = signal<LiveQueryPersistedStatus>(`unavailable`)\n const persistedError = signal<unknown>(undefined)\n\n // Returns single item for singleResult collections, array otherwise\n const data = computed(() => {\n const currentCollection = collection()\n if (!currentCollection) {\n return internalData()\n }\n return isSingleResultCollection(currentCollection)\n ? internalData()[0]\n : internalData()\n })\n\n const syncDataFromCollection = (\n currentCollection: Collection<any, any, any>,\n observer: LiveQueryObserver<any, any>,\n ) => {\n const newState = new Map(currentCollection.entries())\n const newData = Array.from(newState.values())\n\n state.set(newState)\n internalData.set(newData)\n const snapshot = observer.getSnapshot()\n statusValue.set(snapshot.status)\n persistedStatus.set(snapshot.persistedStatus)\n persistedError.set(snapshot.persistedError)\n }\n\n let unsub: (() => void) | null = null\n const cleanup = () => {\n unsub?.()\n unsub = null\n }\n\n effect((onCleanup) => {\n const currentCollection = collection()\n\n // Handle null collection (disabled query)\n if (!currentCollection) {\n statusValue.set(`disabled` as const)\n persistedStatus.set(`unavailable`)\n persistedError.set(undefined)\n state.set(new Map())\n internalData.set([])\n cleanup()\n return\n }\n\n cleanup()\n\n // The shared observer owns sync start, subscription, the ready-race, and\n // status transitions; Angular re-reads the whole collection on each notify\n // (wholesale) into its signals.\n // Angular re-reads the collection on notify; wholesale mode preserves its\n // pre-observer loading policy (no initial-state snapshot request).\n const observer = createLiveQueryObserver(currentCollection, {\n mode: `wholesale`,\n })\n\n const unsubscribe = observer.subscribe(() => {\n syncDataFromCollection(currentCollection, observer)\n })\n // Wholesale attach suppresses listener calls raised by synchronous sync\n // startup. Read once after subscribe returns to capture that final state.\n syncDataFromCollection(currentCollection, observer)\n unsub = () => {\n unsubscribe()\n observer.dispose()\n }\n\n onCleanup(cleanup)\n })\n\n destroyRef.onDestroy(cleanup)\n\n return {\n state,\n data,\n // Loosely typed so the impl return stays compatible with every overload\n // (the shared `isCollection` guard narrows the computed to `Collection | null`).\n collection: computed(() =>\n getPublicCollection(collection()),\n ) as Signal<any>,\n status,\n isLoading: computed(() => status() === `loading`),\n isReady: computed(() => status() === `ready` || status() === `disabled`),\n persistedStatus,\n isPersistedReady: computed(() => persistedStatus() === `ready`),\n persistedError,\n isIdle: computed(() => status() === `idle`),\n isError: computed(() => status() === `error`),\n isCleanedUp: computed(() => status() === `cleaned-up`),\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/index.ts"],"sourcesContent":["import {\n DestroyRef,\n assertInInjectionContext,\n computed,\n effect,\n inject,\n signal,\n} from '@angular/core'\nimport {\n BaseQueryBuilder,\n configPool,\n createLiveQueryObserver,\n getLiveQueryStatusFlags,\n getPublicCollection,\n isCollection,\n isSingleResultCollection,\n resolveLiveQueryValue,\n} from '@tanstack/db'\nimport type {\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n HealthNode,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryHookConfig,\n LiveQueryObserver,\n LiveQueryStatusFlags,\n NonSingleResult,\n PooledLiveQueries,\n QueryBuilder,\n SingleResult,\n} from '@tanstack/db'\nimport type { Signal } from '@angular/core'\n\nconst DEFAULT_GC_TIME_MS = 1\n\nexport * from '@tanstack/db'\n\n/**\n * The result of calling `injectLiveQuery`.\n * Contains reactive signals for the query state and data.\n */\n/** The status and flag signals every `injectLiveQuery` result has. */\nexport type InjectLiveQueryFlags = {\n /** A signal containing the current status of the collection */\n status: Signal<CollectionStatus | `disabled`>\n} & {\n [K in keyof LiveQueryStatusFlags]: Signal<LiveQueryStatusFlags[K]>\n}\n\nexport interface InjectLiveQueryResult<\n TContext extends Context,\n> extends InjectLiveQueryFlags {\n /** A signal containing the complete state map of results keyed by their ID */\n state: Signal<Map<string | number, GetResult<TContext>>>\n /** A signal containing the results as an array, or single result for findOne queries */\n data: Signal<InferResultType<TContext>>\n /** A signal containing the underlying collection instance (null for disabled queries) */\n collection: Signal<Collection<\n GetResult<TContext>,\n string | number,\n {}\n > | null>\n}\n\ntype InferConditionalResultType<TContext extends Context> =\n TContext extends SingleResult\n ? InferResultType<TContext> | []\n : InferResultType<TContext>\n\nexport type InjectConditionalLiveQueryResult<TContext extends Context> = Omit<\n InjectLiveQueryResult<TContext>,\n `data`\n> & {\n data: Signal<InferConditionalResultType<TContext>>\n}\n\nexport interface InjectLiveQueryResultWithCollection<\n TResult extends object = any,\n TKey extends string | number = string | number,\n TUtils extends Record<string, any> = {},\n> extends InjectLiveQueryFlags {\n state: Signal<Map<TKey, TResult>>\n data: Signal<Array<TResult>>\n collection: Signal<Collection<TResult, TKey, TUtils> | null>\n}\n\nexport interface InjectLiveQueryResultWithSingleResultCollection<\n TResult extends object = any,\n TKey extends string | number = string | number,\n TUtils extends Record<string, any> = {},\n> extends InjectLiveQueryFlags {\n state: Signal<Map<TKey, TResult>>\n data: Signal<TResult | undefined>\n collection: Signal<(Collection<TResult, TKey, TUtils> & SingleResult) | null>\n}\n\nexport function injectLiveQuery<\n TContext extends Context,\n TParams extends any,\n>(options: {\n params: () => TParams\n /** Pass `pooledLiveQueries` to share one partition among equal-shape queries. */\n pool?: PooledLiveQueries\n query: (args: {\n params: TParams\n q: InitialQueryBuilder\n }) => QueryBuilder<TContext>\n}): InjectLiveQueryResult<TContext>\nexport function injectLiveQuery<\n TContext extends Context,\n TParams extends any,\n>(options: {\n params: () => TParams\n /** Pass `pooledLiveQueries` to share one partition among equal-shape queries. */\n pool?: PooledLiveQueries\n query: (args: {\n params: TParams\n q: InitialQueryBuilder\n }) => QueryBuilder<TContext> | undefined | null\n}): InjectConditionalLiveQueryResult<TContext>\nexport function injectLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n): InjectLiveQueryResult<TContext>\nexport function injectLiveQuery<TContext extends Context>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> | undefined | null,\n): InjectConditionalLiveQueryResult<TContext>\nexport function injectLiveQuery<TContext extends Context>(\n config: LiveQueryHookConfig<TContext>,\n): InjectLiveQueryResult<TContext>\n// Pre-created collection without singleResult\nexport function injectLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult,\n): InjectLiveQueryResultWithCollection<TResult, TKey, TUtils>\n// Pre-created collection with singleResult\nexport function injectLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Collection<TResult, TKey, TUtils> & SingleResult,\n): InjectLiveQueryResultWithSingleResultCollection<TResult, TKey, TUtils>\nexport function injectLiveQuery(opts: any) {\n assertInInjectionContext(injectLiveQuery)\n const destroyRef = inject(DestroyRef)\n\n const collection = computed(() => {\n // Check if it's an existing collection\n if (isCollection(opts)) {\n return opts\n }\n\n if (typeof opts === `function`) {\n // Check if query function returns null/undefined (disabled query)\n const queryBuilder = new BaseQueryBuilder() as InitialQueryBuilder\n const result = opts(queryBuilder)\n\n if (result === undefined || result === null) {\n // Disabled query - return null\n return null\n }\n\n return resolveLiveQueryValue(result, { gcTime: DEFAULT_GC_TIME_MS })\n }\n\n // Check if it's reactive query options\n const isReactiveQueryOptions =\n opts &&\n typeof opts === `object` &&\n typeof opts.query === `function` &&\n typeof opts.params === `function`\n\n if (isReactiveQueryOptions) {\n // The pool is checked first, so a disabled query rejects it too.\n const pool = configPool(opts)\n // A query function returning null or undefined disables the query.\n const result = opts.query({\n params: opts.params(),\n q: new BaseQueryBuilder() as InitialQueryBuilder,\n })\n return resolveLiveQueryValue(result, {\n gcTime: DEFAULT_GC_TIME_MS,\n pool,\n })\n }\n\n // Handle LiveQueryCollectionConfig objects. Default startSync/gcTime to\n // match the query-fn and reactive-options paths, but let an explicit value\n // in the config win — otherwise a bare `{ query }` never syncs.\n if (opts && typeof opts === `object` && typeof opts.query === `function`) {\n return resolveLiveQueryValue(opts, { gcTime: DEFAULT_GC_TIME_MS })\n }\n\n throw new Error(`Invalid options provided to injectLiveQuery`)\n })\n\n const state = signal(new Map<string | number, any>())\n const internalData = signal<Array<any>>([])\n const statusValue = signal<CollectionStatus | `disabled`>(`idle`)\n const status = computed(() => {\n const value = statusValue()\n return value === `idle` && !collection() ? `disabled` : value\n })\n const health = signal<HealthNode | undefined>(undefined)\n // Every status flag comes from the shared rule; a disabled query is ready.\n const flags = computed(() =>\n getLiveQueryStatusFlags(status() as CollectionStatus, health()),\n )\n\n // Returns single item for singleResult collections, array otherwise\n const data = computed(() => {\n const currentCollection = collection()\n if (!currentCollection) {\n return internalData()\n }\n return isSingleResultCollection(currentCollection)\n ? internalData()[0]\n : internalData()\n })\n\n const syncDataFromCollection = (\n currentCollection: Collection<any, any, any>,\n observer: LiveQueryObserver<any, any>,\n ) => {\n const newState = new Map(currentCollection.entries())\n const newData = Array.from(newState.values())\n\n state.set(newState)\n internalData.set(newData)\n const snapshot = observer.getSnapshot()\n statusValue.set(snapshot.status)\n health.set(snapshot.health)\n }\n\n let unsub: (() => void) | null = null\n const cleanup = () => {\n unsub?.()\n unsub = null\n }\n\n effect((onCleanup) => {\n const currentCollection = collection()\n\n // Handle null collection (disabled query)\n if (!currentCollection) {\n statusValue.set(`disabled` as const)\n health.set(undefined)\n state.set(new Map())\n internalData.set([])\n cleanup()\n return\n }\n\n cleanup()\n\n // The shared observer owns sync start, subscription, the ready-race, and\n // status transitions; Angular re-reads the whole collection on each notify\n // (wholesale) into its signals.\n // Angular re-reads the collection on notify; wholesale mode preserves its\n // pre-observer loading policy (no initial-state snapshot request).\n const observer = createLiveQueryObserver(currentCollection, {\n mode: `wholesale`,\n })\n\n const unsubscribe = observer.subscribe(() => {\n syncDataFromCollection(currentCollection, observer)\n })\n // Wholesale attach suppresses listener calls raised by synchronous sync\n // startup. Read once after subscribe returns to capture that final state.\n syncDataFromCollection(currentCollection, observer)\n unsub = () => {\n unsubscribe()\n observer.dispose()\n }\n\n onCleanup(cleanup)\n })\n\n destroyRef.onDestroy(cleanup)\n\n return {\n state,\n data,\n // Loosely typed so the impl return stays compatible with every overload\n // (the shared `isCollection` guard narrows the computed to `Collection | null`).\n collection: computed(() =>\n getPublicCollection(collection()),\n ) as Signal<any>,\n status,\n isLoading: computed(() => flags().isLoading),\n isReady: computed(() => flags().isReady || status() === `disabled`),\n isIdle: computed(() => flags().isIdle),\n isLoadingError: computed(() => flags().isLoadingError),\n isCrashed: computed(() => flags().isCrashed),\n isCleanedUp: computed(() => flags().isCleanedUp),\n isSyncError: computed(() => flags().isSyncError),\n health,\n }\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/angular-db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-rc.1.1",
|
|
4
4
|
"description": "Angular integration for @tanstack/db",
|
|
5
5
|
"author": "Ethan McDaniel",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,19 +40,19 @@
|
|
|
40
40
|
"skills"
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@tanstack/db": "0.
|
|
43
|
+
"@tanstack/db": "1.0.0-rc.1.1"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@angular/core": ">=16.0.0",
|
|
47
47
|
"rxjs": ">=6.0.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@angular/common": "^20.3.
|
|
51
|
-
"@angular/compiler": "^20.3.
|
|
52
|
-
"@angular/core": "^20.3.
|
|
53
|
-
"@angular/platform-browser": "^20.3.
|
|
54
|
-
"@angular/platform-browser-dynamic": "^20.3.
|
|
55
|
-
"@vitest/coverage-istanbul": "^
|
|
50
|
+
"@angular/common": "^20.3.34",
|
|
51
|
+
"@angular/compiler": "^20.3.34",
|
|
52
|
+
"@angular/core": "^20.3.34",
|
|
53
|
+
"@angular/platform-browser": "^20.3.34",
|
|
54
|
+
"@angular/platform-browser-dynamic": "^20.3.34",
|
|
55
|
+
"@vitest/coverage-istanbul": "^4.1.12",
|
|
56
56
|
"rxjs": "^7.8.2",
|
|
57
57
|
"zone.js": "~0.15.0"
|
|
58
58
|
},
|
|
@@ -66,7 +66,11 @@ Returns an object with Angular `Signal<T>` properties — call with `()` in temp
|
|
|
66
66
|
const query = injectLiveQuery((q) => q.from({ todo: todoCollection }))
|
|
67
67
|
// query.data() → Array<T>
|
|
68
68
|
// query.status() → CollectionStatus | 'disabled'
|
|
69
|
-
// query.isLoading(), query.isReady()
|
|
69
|
+
// query.isLoading(), query.isReady()
|
|
70
|
+
// query.isLoadingError() → the first load failed: nothing to show
|
|
71
|
+
// query.isCrashed() → status 'crashed': local storage broke
|
|
72
|
+
// query.isSyncError() → data is shown, but a sync problem is in health
|
|
73
|
+
// query.health() → health tree: one node for the query and one per source
|
|
70
74
|
// query.isIdle(), query.isCleanedUp() (seldom used)
|
|
71
75
|
// query.state() → Map<TKey, T>
|
|
72
76
|
// query.collection() → Collection | null
|
|
@@ -188,7 +192,7 @@ Angular 16 structural directives:
|
|
|
188
192
|
|
|
189
193
|
## Includes (Hierarchical Data)
|
|
190
194
|
|
|
191
|
-
When a query uses
|
|
195
|
+
When a query uses `include()` for a subquery in `select`, each child field is a live `Collection`. Subscribe to it with `injectLiveQuery` in a child component:
|
|
192
196
|
|
|
193
197
|
```typescript
|
|
194
198
|
@Component({
|
|
@@ -209,10 +213,12 @@ export class ProjectListComponent {
|
|
|
209
213
|
q.from({ p: projectsCollection }).select(({ p }) => ({
|
|
210
214
|
id: p.id,
|
|
211
215
|
name: p.name,
|
|
212
|
-
issues:
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
+
issues: include(
|
|
217
|
+
q
|
|
218
|
+
.from({ i: issuesCollection })
|
|
219
|
+
.where(({ i }) => eq(i.projectId, p.id))
|
|
220
|
+
.select(({ i }) => ({ id: i.id, title: i.title })),
|
|
221
|
+
),
|
|
216
222
|
})),
|
|
217
223
|
)
|
|
218
224
|
}
|
package/src/index.ts
CHANGED
|
@@ -8,7 +8,9 @@ import {
|
|
|
8
8
|
} from '@angular/core'
|
|
9
9
|
import {
|
|
10
10
|
BaseQueryBuilder,
|
|
11
|
+
configPool,
|
|
11
12
|
createLiveQueryObserver,
|
|
13
|
+
getLiveQueryStatusFlags,
|
|
12
14
|
getPublicCollection,
|
|
13
15
|
isCollection,
|
|
14
16
|
isSingleResultCollection,
|
|
@@ -19,12 +21,14 @@ import type {
|
|
|
19
21
|
CollectionStatus,
|
|
20
22
|
Context,
|
|
21
23
|
GetResult,
|
|
24
|
+
HealthNode,
|
|
22
25
|
InferResultType,
|
|
23
26
|
InitialQueryBuilder,
|
|
24
|
-
|
|
27
|
+
LiveQueryHookConfig,
|
|
25
28
|
LiveQueryObserver,
|
|
26
|
-
|
|
29
|
+
LiveQueryStatusFlags,
|
|
27
30
|
NonSingleResult,
|
|
31
|
+
PooledLiveQueries,
|
|
28
32
|
QueryBuilder,
|
|
29
33
|
SingleResult,
|
|
30
34
|
} from '@tanstack/db'
|
|
@@ -38,7 +42,17 @@ export * from '@tanstack/db'
|
|
|
38
42
|
* The result of calling `injectLiveQuery`.
|
|
39
43
|
* Contains reactive signals for the query state and data.
|
|
40
44
|
*/
|
|
41
|
-
|
|
45
|
+
/** The status and flag signals every `injectLiveQuery` result has. */
|
|
46
|
+
export type InjectLiveQueryFlags = {
|
|
47
|
+
/** A signal containing the current status of the collection */
|
|
48
|
+
status: Signal<CollectionStatus | `disabled`>
|
|
49
|
+
} & {
|
|
50
|
+
[K in keyof LiveQueryStatusFlags]: Signal<LiveQueryStatusFlags[K]>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface InjectLiveQueryResult<
|
|
54
|
+
TContext extends Context,
|
|
55
|
+
> extends InjectLiveQueryFlags {
|
|
42
56
|
/** A signal containing the complete state map of results keyed by their ID */
|
|
43
57
|
state: Signal<Map<string | number, GetResult<TContext>>>
|
|
44
58
|
/** A signal containing the results as an array, or single result for findOne queries */
|
|
@@ -49,21 +63,6 @@ export interface InjectLiveQueryResult<TContext extends Context> {
|
|
|
49
63
|
string | number,
|
|
50
64
|
{}
|
|
51
65
|
> | null>
|
|
52
|
-
/** A signal containing the current status of the collection */
|
|
53
|
-
status: Signal<CollectionStatus | `disabled`>
|
|
54
|
-
/** A signal indicating whether the collection is currently loading */
|
|
55
|
-
isLoading: Signal<boolean>
|
|
56
|
-
/** A signal indicating whether the collection is ready */
|
|
57
|
-
isReady: Signal<boolean>
|
|
58
|
-
persistedStatus: Signal<LiveQueryPersistedStatus>
|
|
59
|
-
isPersistedReady: Signal<boolean>
|
|
60
|
-
persistedError: Signal<unknown | undefined>
|
|
61
|
-
/** A signal indicating whether the collection is idle */
|
|
62
|
-
isIdle: Signal<boolean>
|
|
63
|
-
/** A signal indicating whether the collection has an error */
|
|
64
|
-
isError: Signal<boolean>
|
|
65
|
-
/** A signal indicating whether the collection has been cleaned up */
|
|
66
|
-
isCleanedUp: Signal<boolean>
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
type InferConditionalResultType<TContext extends Context> =
|
|
@@ -82,38 +81,20 @@ export interface InjectLiveQueryResultWithCollection<
|
|
|
82
81
|
TResult extends object = any,
|
|
83
82
|
TKey extends string | number = string | number,
|
|
84
83
|
TUtils extends Record<string, any> = {},
|
|
85
|
-
> {
|
|
84
|
+
> extends InjectLiveQueryFlags {
|
|
86
85
|
state: Signal<Map<TKey, TResult>>
|
|
87
86
|
data: Signal<Array<TResult>>
|
|
88
87
|
collection: Signal<Collection<TResult, TKey, TUtils> | null>
|
|
89
|
-
status: Signal<CollectionStatus | `disabled`>
|
|
90
|
-
isLoading: Signal<boolean>
|
|
91
|
-
isReady: Signal<boolean>
|
|
92
|
-
persistedStatus: Signal<LiveQueryPersistedStatus>
|
|
93
|
-
isPersistedReady: Signal<boolean>
|
|
94
|
-
persistedError: Signal<unknown | undefined>
|
|
95
|
-
isIdle: Signal<boolean>
|
|
96
|
-
isError: Signal<boolean>
|
|
97
|
-
isCleanedUp: Signal<boolean>
|
|
98
88
|
}
|
|
99
89
|
|
|
100
90
|
export interface InjectLiveQueryResultWithSingleResultCollection<
|
|
101
91
|
TResult extends object = any,
|
|
102
92
|
TKey extends string | number = string | number,
|
|
103
93
|
TUtils extends Record<string, any> = {},
|
|
104
|
-
> {
|
|
94
|
+
> extends InjectLiveQueryFlags {
|
|
105
95
|
state: Signal<Map<TKey, TResult>>
|
|
106
96
|
data: Signal<TResult | undefined>
|
|
107
97
|
collection: Signal<(Collection<TResult, TKey, TUtils> & SingleResult) | null>
|
|
108
|
-
status: Signal<CollectionStatus | `disabled`>
|
|
109
|
-
isLoading: Signal<boolean>
|
|
110
|
-
isReady: Signal<boolean>
|
|
111
|
-
persistedStatus: Signal<LiveQueryPersistedStatus>
|
|
112
|
-
isPersistedReady: Signal<boolean>
|
|
113
|
-
persistedError: Signal<unknown | undefined>
|
|
114
|
-
isIdle: Signal<boolean>
|
|
115
|
-
isError: Signal<boolean>
|
|
116
|
-
isCleanedUp: Signal<boolean>
|
|
117
98
|
}
|
|
118
99
|
|
|
119
100
|
export function injectLiveQuery<
|
|
@@ -121,6 +102,8 @@ export function injectLiveQuery<
|
|
|
121
102
|
TParams extends any,
|
|
122
103
|
>(options: {
|
|
123
104
|
params: () => TParams
|
|
105
|
+
/** Pass `pooledLiveQueries` to share one partition among equal-shape queries. */
|
|
106
|
+
pool?: PooledLiveQueries
|
|
124
107
|
query: (args: {
|
|
125
108
|
params: TParams
|
|
126
109
|
q: InitialQueryBuilder
|
|
@@ -131,6 +114,8 @@ export function injectLiveQuery<
|
|
|
131
114
|
TParams extends any,
|
|
132
115
|
>(options: {
|
|
133
116
|
params: () => TParams
|
|
117
|
+
/** Pass `pooledLiveQueries` to share one partition among equal-shape queries. */
|
|
118
|
+
pool?: PooledLiveQueries
|
|
134
119
|
query: (args: {
|
|
135
120
|
params: TParams
|
|
136
121
|
q: InitialQueryBuilder
|
|
@@ -145,7 +130,7 @@ export function injectLiveQuery<TContext extends Context>(
|
|
|
145
130
|
) => QueryBuilder<TContext> | undefined | null,
|
|
146
131
|
): InjectConditionalLiveQueryResult<TContext>
|
|
147
132
|
export function injectLiveQuery<TContext extends Context>(
|
|
148
|
-
config:
|
|
133
|
+
config: LiveQueryHookConfig<TContext>,
|
|
149
134
|
): InjectLiveQueryResult<TContext>
|
|
150
135
|
// Pre-created collection without singleResult
|
|
151
136
|
export function injectLiveQuery<
|
|
@@ -194,19 +179,17 @@ export function injectLiveQuery(opts: any) {
|
|
|
194
179
|
typeof opts.params === `function`
|
|
195
180
|
|
|
196
181
|
if (isReactiveQueryOptions) {
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return resolveLiveQueryValue(result, { gcTime: DEFAULT_GC_TIME_MS })
|
|
182
|
+
// The pool is checked first, so a disabled query rejects it too.
|
|
183
|
+
const pool = configPool(opts)
|
|
184
|
+
// A query function returning null or undefined disables the query.
|
|
185
|
+
const result = opts.query({
|
|
186
|
+
params: opts.params(),
|
|
187
|
+
q: new BaseQueryBuilder() as InitialQueryBuilder,
|
|
188
|
+
})
|
|
189
|
+
return resolveLiveQueryValue(result, {
|
|
190
|
+
gcTime: DEFAULT_GC_TIME_MS,
|
|
191
|
+
pool,
|
|
192
|
+
})
|
|
210
193
|
}
|
|
211
194
|
|
|
212
195
|
// Handle LiveQueryCollectionConfig objects. Default startSync/gcTime to
|
|
@@ -226,8 +209,11 @@ export function injectLiveQuery(opts: any) {
|
|
|
226
209
|
const value = statusValue()
|
|
227
210
|
return value === `idle` && !collection() ? `disabled` : value
|
|
228
211
|
})
|
|
229
|
-
const
|
|
230
|
-
|
|
212
|
+
const health = signal<HealthNode | undefined>(undefined)
|
|
213
|
+
// Every status flag comes from the shared rule; a disabled query is ready.
|
|
214
|
+
const flags = computed(() =>
|
|
215
|
+
getLiveQueryStatusFlags(status() as CollectionStatus, health()),
|
|
216
|
+
)
|
|
231
217
|
|
|
232
218
|
// Returns single item for singleResult collections, array otherwise
|
|
233
219
|
const data = computed(() => {
|
|
@@ -251,8 +237,7 @@ export function injectLiveQuery(opts: any) {
|
|
|
251
237
|
internalData.set(newData)
|
|
252
238
|
const snapshot = observer.getSnapshot()
|
|
253
239
|
statusValue.set(snapshot.status)
|
|
254
|
-
|
|
255
|
-
persistedError.set(snapshot.persistedError)
|
|
240
|
+
health.set(snapshot.health)
|
|
256
241
|
}
|
|
257
242
|
|
|
258
243
|
let unsub: (() => void) | null = null
|
|
@@ -267,8 +252,7 @@ export function injectLiveQuery(opts: any) {
|
|
|
267
252
|
// Handle null collection (disabled query)
|
|
268
253
|
if (!currentCollection) {
|
|
269
254
|
statusValue.set(`disabled` as const)
|
|
270
|
-
|
|
271
|
-
persistedError.set(undefined)
|
|
255
|
+
health.set(undefined)
|
|
272
256
|
state.set(new Map())
|
|
273
257
|
internalData.set([])
|
|
274
258
|
cleanup()
|
|
@@ -311,13 +295,13 @@ export function injectLiveQuery(opts: any) {
|
|
|
311
295
|
getPublicCollection(collection()),
|
|
312
296
|
) as Signal<any>,
|
|
313
297
|
status,
|
|
314
|
-
isLoading: computed(() =>
|
|
315
|
-
isReady: computed(() =>
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
298
|
+
isLoading: computed(() => flags().isLoading),
|
|
299
|
+
isReady: computed(() => flags().isReady || status() === `disabled`),
|
|
300
|
+
isIdle: computed(() => flags().isIdle),
|
|
301
|
+
isLoadingError: computed(() => flags().isLoadingError),
|
|
302
|
+
isCrashed: computed(() => flags().isCrashed),
|
|
303
|
+
isCleanedUp: computed(() => flags().isCleanedUp),
|
|
304
|
+
isSyncError: computed(() => flags().isSyncError),
|
|
305
|
+
health,
|
|
322
306
|
}
|
|
323
307
|
}
|