@tanstack/db 0.4.7 → 0.4.9
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/cjs/collection/index.cjs.map +1 -1
- package/dist/cjs/collection/index.d.cts +2 -1
- package/dist/cjs/collection/lifecycle.cjs +2 -3
- package/dist/cjs/collection/lifecycle.cjs.map +1 -1
- package/dist/cjs/collection/state.cjs +22 -33
- package/dist/cjs/collection/state.cjs.map +1 -1
- package/dist/cjs/collection/state.d.cts +6 -2
- package/dist/cjs/collection/sync.cjs +4 -3
- package/dist/cjs/collection/sync.cjs.map +1 -1
- package/dist/cjs/errors.cjs +51 -17
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +38 -8
- package/dist/cjs/index.cjs +8 -4
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/indexes/auto-index.cjs +0 -3
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/query/builder/types.d.cts +1 -1
- package/dist/cjs/query/compiler/index.cjs +42 -19
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.d.cts +33 -8
- package/dist/cjs/query/compiler/joins.cjs +88 -66
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/joins.d.cts +5 -2
- package/dist/cjs/query/compiler/order-by.cjs +2 -0
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +1 -0
- package/dist/cjs/query/compiler/select.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.cjs +322 -46
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.d.cts +98 -7
- package/dist/cjs/query/live/collection-registry.cjs +16 -0
- package/dist/cjs/query/live/collection-registry.cjs.map +1 -0
- package/dist/cjs/query/live/collection-registry.d.cts +26 -0
- package/dist/cjs/query/live/collection-subscriber.cjs +57 -58
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/query/live/collection-subscriber.d.cts +4 -7
- package/dist/cjs/query/live-query-collection.cjs +11 -5
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.d.cts +10 -3
- package/dist/cjs/query/optimizer.cjs +44 -7
- package/dist/cjs/query/optimizer.cjs.map +1 -1
- package/dist/cjs/query/optimizer.d.cts +4 -4
- package/dist/cjs/scheduler.cjs +137 -0
- package/dist/cjs/scheduler.cjs.map +1 -0
- package/dist/cjs/scheduler.d.cts +56 -0
- package/dist/cjs/transactions.cjs +7 -1
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +3 -5
- package/dist/esm/collection/index.d.ts +2 -1
- package/dist/esm/collection/index.js.map +1 -1
- package/dist/esm/collection/lifecycle.js +2 -3
- package/dist/esm/collection/lifecycle.js.map +1 -1
- package/dist/esm/collection/state.d.ts +6 -2
- package/dist/esm/collection/state.js +22 -33
- package/dist/esm/collection/state.js.map +1 -1
- package/dist/esm/collection/sync.js +4 -3
- package/dist/esm/collection/sync.js.map +1 -1
- package/dist/esm/errors.d.ts +38 -8
- package/dist/esm/errors.js +52 -18
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.js +9 -5
- package/dist/esm/indexes/auto-index.js +0 -3
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/esm/query/builder/types.d.ts +1 -1
- package/dist/esm/query/compiler/index.d.ts +33 -8
- package/dist/esm/query/compiler/index.js +42 -19
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/joins.d.ts +5 -2
- package/dist/esm/query/compiler/joins.js +90 -68
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +1 -0
- package/dist/esm/query/compiler/order-by.js +2 -0
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/compiler/select.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.d.ts +98 -7
- package/dist/esm/query/live/collection-config-builder.js +322 -46
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-registry.d.ts +26 -0
- package/dist/esm/query/live/collection-registry.js +16 -0
- package/dist/esm/query/live/collection-registry.js.map +1 -0
- package/dist/esm/query/live/collection-subscriber.d.ts +4 -7
- package/dist/esm/query/live/collection-subscriber.js +57 -58
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/query/live-query-collection.d.ts +10 -3
- package/dist/esm/query/live-query-collection.js +11 -5
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/query/optimizer.d.ts +4 -4
- package/dist/esm/query/optimizer.js +44 -7
- package/dist/esm/query/optimizer.js.map +1 -1
- package/dist/esm/scheduler.d.ts +56 -0
- package/dist/esm/scheduler.js +137 -0
- package/dist/esm/scheduler.js.map +1 -0
- package/dist/esm/transactions.js +7 -1
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +3 -5
- package/package.json +2 -2
- package/src/collection/index.ts +1 -1
- package/src/collection/lifecycle.ts +3 -4
- package/src/collection/state.ts +52 -48
- package/src/collection/sync.ts +7 -6
- package/src/errors.ts +79 -13
- package/src/indexes/auto-index.ts +0 -8
- package/src/query/builder/types.ts +1 -1
- package/src/query/compiler/index.ts +115 -32
- package/src/query/compiler/joins.ts +180 -127
- package/src/query/compiler/order-by.ts +7 -0
- package/src/query/compiler/select.ts +2 -3
- package/src/query/live/collection-config-builder.ts +542 -71
- package/src/query/live/collection-registry.ts +47 -0
- package/src/query/live/collection-subscriber.ts +87 -105
- package/src/query/live-query-collection.ts +39 -14
- package/src/query/optimizer.ts +85 -15
- package/src/scheduler.ts +198 -0
- package/src/transactions.ts +12 -1
- package/src/types.ts +3 -5
|
@@ -1,37 +1,128 @@
|
|
|
1
|
+
import { SchedulerContextId } from '../../scheduler.js';
|
|
1
2
|
import { CollectionSubscription } from '../../collection/subscription.js';
|
|
2
3
|
import { OrderByOptimizationInfo } from '../compiler/order-by.js';
|
|
3
|
-
import { CollectionConfigSingleRowOption, SyncConfig } from '../../types.js';
|
|
4
|
+
import { CollectionConfigSingleRowOption, SyncConfig, UtilsRecord } from '../../types.js';
|
|
4
5
|
import { Context, GetResult } from '../builder/types.js';
|
|
5
6
|
import { BasicExpression, QueryIR } from '../ir.js';
|
|
6
7
|
import { LazyCollectionCallbacks } from '../compiler/joins.js';
|
|
7
8
|
import { FullSyncState, LiveQueryCollectionConfig } from './types.js';
|
|
9
|
+
export type LiveQueryCollectionUtils = UtilsRecord & {
|
|
10
|
+
getRunCount: () => number;
|
|
11
|
+
getBuilder: () => CollectionConfigBuilder<any, any>;
|
|
12
|
+
};
|
|
8
13
|
export declare class CollectionConfigBuilder<TContext extends Context, TResult extends object = GetResult<TContext>> {
|
|
9
14
|
private readonly config;
|
|
10
15
|
private readonly id;
|
|
11
16
|
readonly query: QueryIR;
|
|
12
17
|
private readonly collections;
|
|
18
|
+
private readonly collectionByAlias;
|
|
19
|
+
private compiledAliasToCollectionId;
|
|
13
20
|
private readonly resultKeys;
|
|
14
21
|
private readonly orderByIndices;
|
|
15
22
|
private readonly compare?;
|
|
16
23
|
private isGraphRunning;
|
|
24
|
+
private runCount;
|
|
25
|
+
currentSyncConfig: Parameters<SyncConfig<TResult>[`sync`]>[0] | undefined;
|
|
26
|
+
currentSyncState: FullSyncState | undefined;
|
|
27
|
+
private isInErrorState;
|
|
28
|
+
private liveQueryCollection?;
|
|
29
|
+
private readonly aliasDependencies;
|
|
30
|
+
private readonly builderDependencies;
|
|
31
|
+
private readonly pendingGraphRuns;
|
|
32
|
+
private unsubscribeFromSchedulerClears?;
|
|
17
33
|
private graphCache;
|
|
18
34
|
private inputsCache;
|
|
19
35
|
private pipelineCache;
|
|
20
|
-
|
|
36
|
+
sourceWhereClausesCache: Map<string, BasicExpression<boolean>> | undefined;
|
|
21
37
|
readonly subscriptions: Record<string, CollectionSubscription>;
|
|
22
|
-
|
|
23
|
-
readonly
|
|
38
|
+
lazySourcesCallbacks: Record<string, LazyCollectionCallbacks>;
|
|
39
|
+
readonly lazySources: Set<string>;
|
|
24
40
|
optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>;
|
|
25
41
|
constructor(config: LiveQueryCollectionConfig<TContext, TResult>);
|
|
26
|
-
getConfig(): CollectionConfigSingleRowOption<TResult
|
|
27
|
-
|
|
42
|
+
getConfig(): CollectionConfigSingleRowOption<TResult> & {
|
|
43
|
+
utils: LiveQueryCollectionUtils;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Resolves a collection alias to its collection ID.
|
|
47
|
+
*
|
|
48
|
+
* Uses a two-tier lookup strategy:
|
|
49
|
+
* 1. First checks compiled aliases (includes subquery inner aliases)
|
|
50
|
+
* 2. Falls back to declared aliases from the query's from/join clauses
|
|
51
|
+
*
|
|
52
|
+
* @param alias - The alias to resolve (e.g., "employee", "manager")
|
|
53
|
+
* @returns The collection ID that the alias references
|
|
54
|
+
* @throws {Error} If the alias is not found in either lookup
|
|
55
|
+
*/
|
|
56
|
+
getCollectionIdForAlias(alias: string): string;
|
|
57
|
+
isLazyAlias(alias: string): boolean;
|
|
58
|
+
maybeRunGraph(callback?: () => boolean): void;
|
|
59
|
+
/**
|
|
60
|
+
* Schedules a graph run with the transaction-scoped scheduler.
|
|
61
|
+
* Ensures each builder runs at most once per transaction, with automatic dependency tracking
|
|
62
|
+
* to run parent queries before child queries. Outside a transaction, runs immediately.
|
|
63
|
+
*
|
|
64
|
+
* Multiple calls during a transaction are coalesced into a single execution.
|
|
65
|
+
* Dependencies are auto-discovered from subscribed live queries, or can be overridden.
|
|
66
|
+
* Load callbacks are combined when entries merge.
|
|
67
|
+
*
|
|
68
|
+
* Uses the current sync session's config and syncState from instance properties.
|
|
69
|
+
*
|
|
70
|
+
* @param callback - Optional callback to load more data if needed (returns true when done)
|
|
71
|
+
* @param options - Optional scheduling configuration
|
|
72
|
+
* @param options.contextId - Transaction ID to group work; defaults to active transaction
|
|
73
|
+
* @param options.jobId - Unique identifier for this job; defaults to this builder instance
|
|
74
|
+
* @param options.alias - Source alias that triggered this schedule; adds alias-specific dependencies
|
|
75
|
+
* @param options.dependencies - Explicit dependency list; overrides auto-discovered dependencies
|
|
76
|
+
*/
|
|
77
|
+
scheduleGraphRun(callback?: () => boolean, options?: {
|
|
78
|
+
contextId?: SchedulerContextId;
|
|
79
|
+
jobId?: unknown;
|
|
80
|
+
alias?: string;
|
|
81
|
+
dependencies?: Array<CollectionConfigBuilder<any, any>>;
|
|
82
|
+
}): void;
|
|
83
|
+
/**
|
|
84
|
+
* Clears pending graph run state for a specific context.
|
|
85
|
+
* Called when the scheduler clears a context (e.g., transaction rollback/abort).
|
|
86
|
+
*/
|
|
87
|
+
clearPendingGraphRun(contextId: SchedulerContextId): void;
|
|
88
|
+
/**
|
|
89
|
+
* Executes a pending graph run. Called by the scheduler when dependencies are satisfied.
|
|
90
|
+
* Clears the pending state BEFORE execution so that any re-schedules during the run
|
|
91
|
+
* create fresh state and don't interfere with the current execution.
|
|
92
|
+
* Uses instance sync state - if sync has ended, gracefully returns without executing.
|
|
93
|
+
*
|
|
94
|
+
* @param contextId - Optional context ID to look up pending state
|
|
95
|
+
* @param pendingParam - For immediate execution (no context), pending state is passed directly
|
|
96
|
+
*/
|
|
97
|
+
private executeGraphRun;
|
|
28
98
|
private getSyncConfig;
|
|
99
|
+
incrementRunCount(): void;
|
|
100
|
+
getRunCount(): number;
|
|
29
101
|
private syncFn;
|
|
102
|
+
/**
|
|
103
|
+
* Compiles the query pipeline with all declared aliases.
|
|
104
|
+
*/
|
|
30
105
|
private compileBasePipeline;
|
|
31
106
|
private maybeCompileBasePipeline;
|
|
32
107
|
private extendPipelineWithChangeProcessing;
|
|
33
108
|
private applyChanges;
|
|
109
|
+
/**
|
|
110
|
+
* Handle status changes from source collections
|
|
111
|
+
*/
|
|
112
|
+
private handleSourceStatusChange;
|
|
113
|
+
/**
|
|
114
|
+
* Update the live query status based on source collection statuses
|
|
115
|
+
*/
|
|
116
|
+
private updateLiveQueryStatus;
|
|
117
|
+
/**
|
|
118
|
+
* Transition the live query to error state
|
|
119
|
+
*/
|
|
120
|
+
private transitionToError;
|
|
34
121
|
private allCollectionsReady;
|
|
35
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Creates per-alias subscriptions enabling self-join support.
|
|
124
|
+
* Each alias gets its own subscription with independent filters, even for the same collection.
|
|
125
|
+
* Example: `{ employee: col, manager: col }` creates two separate subscriptions.
|
|
126
|
+
*/
|
|
36
127
|
private subscribeToAllCollections;
|
|
37
128
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const collectionBuilderRegistry = /* @__PURE__ */ new WeakMap();
|
|
4
|
+
function getBuilderFromConfig(config) {
|
|
5
|
+
return config.utils?.getBuilder?.();
|
|
6
|
+
}
|
|
7
|
+
function registerCollectionBuilder(collection, builder) {
|
|
8
|
+
collectionBuilderRegistry.set(collection, builder);
|
|
9
|
+
}
|
|
10
|
+
function getCollectionBuilder(collection) {
|
|
11
|
+
return collectionBuilderRegistry.get(collection);
|
|
12
|
+
}
|
|
13
|
+
exports.getBuilderFromConfig = getBuilderFromConfig;
|
|
14
|
+
exports.getCollectionBuilder = getCollectionBuilder;
|
|
15
|
+
exports.registerCollectionBuilder = registerCollectionBuilder;
|
|
16
|
+
//# sourceMappingURL=collection-registry.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collection-registry.cjs","sources":["../../../../src/query/live/collection-registry.ts"],"sourcesContent":["import type { Collection } from \"../../collection/index.js\"\nimport type { CollectionConfigBuilder } from \"./collection-config-builder.js\"\n\nconst collectionBuilderRegistry = new WeakMap<\n Collection<any, any, any>,\n CollectionConfigBuilder<any, any>\n>()\n\n/**\n * Retrieves the builder attached to a config object via its utils.getBuilder() method.\n *\n * @param config - The collection config object\n * @returns The attached builder, or `undefined` if none exists\n */\nexport function getBuilderFromConfig(\n config: object\n): CollectionConfigBuilder<any, any> | undefined {\n return (config as any).utils?.getBuilder?.()\n}\n\n/**\n * Registers a builder for a collection in the global registry.\n * Used to detect when a live query depends on another live query,\n * enabling the scheduler to ensure parent queries run first.\n *\n * @param collection - The collection to register the builder for\n * @param builder - The builder that produces this collection\n */\nexport function registerCollectionBuilder(\n collection: Collection<any, any, any>,\n builder: CollectionConfigBuilder<any, any>\n): void {\n collectionBuilderRegistry.set(collection, builder)\n}\n\n/**\n * Retrieves the builder registered for a collection.\n * Used to discover dependencies when a live query subscribes to another live query.\n *\n * @param collection - The collection to look up\n * @returns The registered builder, or `undefined` if none exists\n */\nexport function getCollectionBuilder(\n collection: Collection<any, any, any>\n): CollectionConfigBuilder<any, any> | undefined {\n return collectionBuilderRegistry.get(collection)\n}\n"],"names":[],"mappings":";;AAGA,MAAM,gDAAgC,QAAA;AAW/B,SAAS,qBACd,QAC+C;AAC/C,SAAQ,OAAe,OAAO,aAAA;AAChC;AAUO,SAAS,0BACd,YACA,SACM;AACN,4BAA0B,IAAI,YAAY,OAAO;AACnD;AASO,SAAS,qBACd,YAC+C;AAC/C,SAAO,0BAA0B,IAAI,UAAU;AACjD;;;;"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Collection } from '../../collection/index.js';
|
|
2
|
+
import { CollectionConfigBuilder } from './collection-config-builder.js';
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves the builder attached to a config object via its utils.getBuilder() method.
|
|
5
|
+
*
|
|
6
|
+
* @param config - The collection config object
|
|
7
|
+
* @returns The attached builder, or `undefined` if none exists
|
|
8
|
+
*/
|
|
9
|
+
export declare function getBuilderFromConfig(config: object): CollectionConfigBuilder<any, any> | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Registers a builder for a collection in the global registry.
|
|
12
|
+
* Used to detect when a live query depends on another live query,
|
|
13
|
+
* enabling the scheduler to ensure parent queries run first.
|
|
14
|
+
*
|
|
15
|
+
* @param collection - The collection to register the builder for
|
|
16
|
+
* @param builder - The builder that produces this collection
|
|
17
|
+
*/
|
|
18
|
+
export declare function registerCollectionBuilder(collection: Collection<any, any, any>, builder: CollectionConfigBuilder<any, any>): void;
|
|
19
|
+
/**
|
|
20
|
+
* Retrieves the builder registered for a collection.
|
|
21
|
+
* Used to discover dependencies when a live query subscribes to another live query.
|
|
22
|
+
*
|
|
23
|
+
* @param collection - The collection to look up
|
|
24
|
+
* @returns The registered builder, or `undefined` if none exists
|
|
25
|
+
*/
|
|
26
|
+
export declare function getCollectionBuilder(collection: Collection<any, any, any>): CollectionConfigBuilder<any, any> | undefined;
|
|
@@ -2,46 +2,41 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const dbIvm = require("@tanstack/db-ivm");
|
|
4
4
|
const expressions = require("../compiler/expressions.cjs");
|
|
5
|
+
const errors = require("../../errors.cjs");
|
|
6
|
+
const loadMoreCallbackSymbol = Symbol.for(
|
|
7
|
+
`@tanstack/db.collection-config-builder`
|
|
8
|
+
);
|
|
5
9
|
class CollectionSubscriber {
|
|
6
|
-
constructor(collectionId, collection,
|
|
10
|
+
constructor(alias, collectionId, collection, collectionConfigBuilder) {
|
|
11
|
+
this.alias = alias;
|
|
7
12
|
this.collectionId = collectionId;
|
|
8
13
|
this.collection = collection;
|
|
9
|
-
this.config = config;
|
|
10
|
-
this.syncState = syncState;
|
|
11
14
|
this.collectionConfigBuilder = collectionConfigBuilder;
|
|
12
15
|
this.biggest = void 0;
|
|
13
|
-
this.collectionAlias = findCollectionAlias(
|
|
14
|
-
this.collectionId,
|
|
15
|
-
this.collectionConfigBuilder.query
|
|
16
|
-
);
|
|
17
16
|
}
|
|
18
17
|
subscribe() {
|
|
19
|
-
const whereClause = this.
|
|
18
|
+
const whereClause = this.getWhereClauseForAlias();
|
|
20
19
|
if (whereClause) {
|
|
21
|
-
const whereExpression = expressions.convertToBasicExpression(
|
|
22
|
-
whereClause,
|
|
23
|
-
this.collectionAlias
|
|
24
|
-
);
|
|
20
|
+
const whereExpression = expressions.convertToBasicExpression(whereClause, this.alias);
|
|
25
21
|
if (whereExpression) {
|
|
26
22
|
return this.subscribeToChanges(whereExpression);
|
|
27
|
-
} else {
|
|
28
|
-
throw new Error(
|
|
29
|
-
`Failed to convert WHERE clause to collection filter for collection '${this.collectionId}'. This indicates a bug in the query optimization logic.`
|
|
30
|
-
);
|
|
31
23
|
}
|
|
32
|
-
|
|
33
|
-
return this.subscribeToChanges();
|
|
24
|
+
throw new errors.WhereClauseConversionError(this.collectionId, this.alias);
|
|
34
25
|
}
|
|
26
|
+
return this.subscribeToChanges();
|
|
35
27
|
}
|
|
36
28
|
subscribeToChanges(whereExpression) {
|
|
37
29
|
let subscription;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
this.
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
const orderByInfo = this.getOrderByInfo();
|
|
31
|
+
if (orderByInfo) {
|
|
32
|
+
subscription = this.subscribeToOrderedChanges(
|
|
33
|
+
whereExpression,
|
|
34
|
+
orderByInfo
|
|
35
|
+
);
|
|
43
36
|
} else {
|
|
44
|
-
const includeInitialState = !this.collectionConfigBuilder.
|
|
37
|
+
const includeInitialState = !this.collectionConfigBuilder.isLazyAlias(
|
|
38
|
+
this.alias
|
|
39
|
+
);
|
|
45
40
|
subscription = this.subscribeToMatchingChanges(
|
|
46
41
|
whereExpression,
|
|
47
42
|
includeInitialState
|
|
@@ -50,22 +45,22 @@ class CollectionSubscriber {
|
|
|
50
45
|
const unsubscribe = () => {
|
|
51
46
|
subscription.unsubscribe();
|
|
52
47
|
};
|
|
53
|
-
this.
|
|
48
|
+
this.collectionConfigBuilder.currentSyncState.unsubscribeCallbacks.add(
|
|
49
|
+
unsubscribe
|
|
50
|
+
);
|
|
54
51
|
return subscription;
|
|
55
52
|
}
|
|
56
53
|
sendChangesToPipeline(changes, callback) {
|
|
57
|
-
const input = this.
|
|
54
|
+
const input = this.collectionConfigBuilder.currentSyncState.inputs[this.alias];
|
|
58
55
|
const sentChanges = sendChangesToInput(
|
|
59
56
|
input,
|
|
60
57
|
changes,
|
|
61
58
|
this.collection.config.getKey
|
|
62
59
|
);
|
|
63
60
|
const dataLoader = sentChanges > 0 ? callback : void 0;
|
|
64
|
-
this.collectionConfigBuilder.
|
|
65
|
-
this.
|
|
66
|
-
|
|
67
|
-
dataLoader
|
|
68
|
-
);
|
|
61
|
+
this.collectionConfigBuilder.scheduleGraphRun(dataLoader, {
|
|
62
|
+
alias: this.alias
|
|
63
|
+
});
|
|
69
64
|
}
|
|
70
65
|
subscribeToMatchingChanges(whereExpression, includeInitialState = false) {
|
|
71
66
|
const sendChanges = (changes) => {
|
|
@@ -77,12 +72,12 @@ class CollectionSubscriber {
|
|
|
77
72
|
});
|
|
78
73
|
return subscription;
|
|
79
74
|
}
|
|
80
|
-
subscribeToOrderedChanges(whereExpression) {
|
|
81
|
-
const { orderBy, offset, limit, comparator, dataNeeded, index } =
|
|
75
|
+
subscribeToOrderedChanges(whereExpression, orderByInfo) {
|
|
76
|
+
const { orderBy, offset, limit, comparator, dataNeeded, index } = orderByInfo;
|
|
82
77
|
const sendChangesInRange = (changes) => {
|
|
83
78
|
const splittedChanges = splitUpdates(changes);
|
|
84
79
|
let filteredChanges = splittedChanges;
|
|
85
|
-
if (dataNeeded() === 0) {
|
|
80
|
+
if (dataNeeded && dataNeeded() === 0) {
|
|
86
81
|
filteredChanges = filterChangesSmallerOrEqualToMax(
|
|
87
82
|
splittedChanges,
|
|
88
83
|
comparator,
|
|
@@ -97,7 +92,7 @@ class CollectionSubscriber {
|
|
|
97
92
|
subscription.setOrderByIndex(index);
|
|
98
93
|
const normalizedOrderBy = expressions.convertOrderByToBasicExpression(
|
|
99
94
|
orderBy,
|
|
100
|
-
this.
|
|
95
|
+
this.alias
|
|
101
96
|
);
|
|
102
97
|
subscription.requestLimitedSnapshot({
|
|
103
98
|
limit: offset + limit,
|
|
@@ -109,7 +104,7 @@ class CollectionSubscriber {
|
|
|
109
104
|
// after each iteration of the query pipeline
|
|
110
105
|
// to ensure that the orderBy operator has enough data to work with
|
|
111
106
|
loadMoreIfNeeded(subscription) {
|
|
112
|
-
const orderByInfo = this.
|
|
107
|
+
const orderByInfo = this.getOrderByInfo();
|
|
113
108
|
if (!orderByInfo) {
|
|
114
109
|
return true;
|
|
115
110
|
}
|
|
@@ -126,22 +121,32 @@ class CollectionSubscriber {
|
|
|
126
121
|
return true;
|
|
127
122
|
}
|
|
128
123
|
sendChangesToPipelineWithTracking(changes, subscription) {
|
|
129
|
-
const
|
|
130
|
-
|
|
124
|
+
const orderByInfo = this.getOrderByInfo();
|
|
125
|
+
if (!orderByInfo) {
|
|
126
|
+
this.sendChangesToPipeline(changes);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const trackedChanges = this.trackSentValues(changes, orderByInfo.comparator);
|
|
130
|
+
const subscriptionWithLoader = subscription;
|
|
131
|
+
subscriptionWithLoader[loadMoreCallbackSymbol] ??= this.loadMoreIfNeeded.bind(this, subscription);
|
|
131
132
|
this.sendChangesToPipeline(
|
|
132
133
|
trackedChanges,
|
|
133
|
-
|
|
134
|
+
subscriptionWithLoader[loadMoreCallbackSymbol]
|
|
134
135
|
);
|
|
135
136
|
}
|
|
136
137
|
// Loads the next `n` items from the collection
|
|
137
138
|
// starting from the biggest item it has sent
|
|
138
139
|
loadNextItems(n, subscription) {
|
|
139
|
-
const
|
|
140
|
+
const orderByInfo = this.getOrderByInfo();
|
|
141
|
+
if (!orderByInfo) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const { orderBy, valueExtractorForRawRow } = orderByInfo;
|
|
140
145
|
const biggestSentRow = this.biggest;
|
|
141
146
|
const biggestSentValue = biggestSentRow ? valueExtractorForRawRow(biggestSentRow) : biggestSentRow;
|
|
142
147
|
const normalizedOrderBy = expressions.convertOrderByToBasicExpression(
|
|
143
148
|
orderBy,
|
|
144
|
-
this.
|
|
149
|
+
this.alias
|
|
145
150
|
);
|
|
146
151
|
subscription.requestLimitedSnapshot({
|
|
147
152
|
orderBy: normalizedOrderBy,
|
|
@@ -149,10 +154,17 @@ class CollectionSubscriber {
|
|
|
149
154
|
minValue: biggestSentValue
|
|
150
155
|
});
|
|
151
156
|
}
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
if (
|
|
155
|
-
return
|
|
157
|
+
getWhereClauseForAlias() {
|
|
158
|
+
const sourceWhereClausesCache = this.collectionConfigBuilder.sourceWhereClausesCache;
|
|
159
|
+
if (!sourceWhereClausesCache) {
|
|
160
|
+
return void 0;
|
|
161
|
+
}
|
|
162
|
+
return sourceWhereClausesCache.get(this.alias);
|
|
163
|
+
}
|
|
164
|
+
getOrderByInfo() {
|
|
165
|
+
const info = this.collectionConfigBuilder.optimizableOrderByCollections[this.collectionId];
|
|
166
|
+
if (info && info.alias === this.alias) {
|
|
167
|
+
return info;
|
|
156
168
|
}
|
|
157
169
|
return void 0;
|
|
158
170
|
}
|
|
@@ -167,19 +179,6 @@ class CollectionSubscriber {
|
|
|
167
179
|
}
|
|
168
180
|
}
|
|
169
181
|
}
|
|
170
|
-
function findCollectionAlias(collectionId, query) {
|
|
171
|
-
if (query.from?.type === `collectionRef` && query.from.collection?.id === collectionId) {
|
|
172
|
-
return query.from.alias;
|
|
173
|
-
}
|
|
174
|
-
if (query.join) {
|
|
175
|
-
for (const joinClause of query.join) {
|
|
176
|
-
if (joinClause.from?.type === `collectionRef` && joinClause.from.collection?.id === collectionId) {
|
|
177
|
-
return joinClause.from.alias;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return void 0;
|
|
182
|
-
}
|
|
183
182
|
function sendChangesToInput(input, changes, getKey) {
|
|
184
183
|
const multiSetArray = [];
|
|
185
184
|
for (const change of changes) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collection-subscriber.cjs","sources":["../../../../src/query/live/collection-subscriber.ts"],"sourcesContent":["import { MultiSet } from \"@tanstack/db-ivm\"\nimport {\n convertOrderByToBasicExpression,\n convertToBasicExpression,\n} from \"../compiler/expressions.js\"\nimport type { FullSyncState } from \"./types.js\"\nimport type { MultiSetArray, RootStreamBuilder } from \"@tanstack/db-ivm\"\nimport type { Collection } from \"../../collection/index.js\"\nimport type { ChangeMessage, SyncConfig } from \"../../types.js\"\nimport type { Context, GetResult } from \"../builder/types.js\"\nimport type { BasicExpression } from \"../ir.js\"\nimport type { CollectionConfigBuilder } from \"./collection-config-builder.js\"\nimport type { CollectionSubscription } from \"../../collection/subscription.js\"\n\nexport class CollectionSubscriber<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n> {\n // Keep track of the biggest value we've sent so far (needed for orderBy optimization)\n private biggest: any = undefined\n\n private collectionAlias: string\n\n constructor(\n private collectionId: string,\n private collection: Collection,\n private config: Parameters<SyncConfig<TResult>[`sync`]>[0],\n private syncState: FullSyncState,\n private collectionConfigBuilder: CollectionConfigBuilder<TContext, TResult>\n ) {\n this.collectionAlias = findCollectionAlias(\n this.collectionId,\n this.collectionConfigBuilder.query\n )!\n }\n\n subscribe(): CollectionSubscription {\n const whereClause = this.getWhereClauseFromAlias(this.collectionAlias)\n\n if (whereClause) {\n // Convert WHERE clause to BasicExpression format for collection subscription\n const whereExpression = convertToBasicExpression(\n whereClause,\n this.collectionAlias\n )\n\n if (whereExpression) {\n // Use index optimization for this collection\n return this.subscribeToChanges(whereExpression)\n } else {\n // This should not happen - if we have a whereClause but can't create whereExpression,\n // it indicates a bug in our optimization logic\n throw new Error(\n `Failed to convert WHERE clause to collection filter for collection '${this.collectionId}'. ` +\n `This indicates a bug in the query optimization logic.`\n )\n }\n } else {\n // No WHERE clause for this collection, use regular subscription\n return this.subscribeToChanges()\n }\n }\n\n private subscribeToChanges(whereExpression?: BasicExpression<boolean>) {\n let subscription: CollectionSubscription\n if (\n Object.hasOwn(\n this.collectionConfigBuilder.optimizableOrderByCollections,\n this.collectionId\n )\n ) {\n subscription = this.subscribeToOrderedChanges(whereExpression)\n } else {\n // If the collection is lazy then we should not include the initial state\n const includeInitialState =\n !this.collectionConfigBuilder.lazyCollections.has(this.collectionId)\n\n subscription = this.subscribeToMatchingChanges(\n whereExpression,\n includeInitialState\n )\n }\n const unsubscribe = () => {\n subscription.unsubscribe()\n }\n this.syncState.unsubscribeCallbacks.add(unsubscribe)\n return subscription\n }\n\n private sendChangesToPipeline(\n changes: Iterable<ChangeMessage<any, string | number>>,\n callback?: () => boolean\n ) {\n const input = this.syncState.inputs[this.collectionId]!\n const sentChanges = sendChangesToInput(\n input,\n changes,\n this.collection.config.getKey\n )\n\n // Do not provide the callback that loads more data\n // if there's no more data to load\n // otherwise we end up in an infinite loop trying to load more data\n const dataLoader = sentChanges > 0 ? callback : undefined\n\n // We need to call `maybeRunGraph` even if there's no data to load\n // because we need to mark the collection as ready if it's not already\n // and that's only done in `maybeRunGraph`\n this.collectionConfigBuilder.maybeRunGraph(\n this.config,\n this.syncState,\n dataLoader\n )\n }\n\n private subscribeToMatchingChanges(\n whereExpression: BasicExpression<boolean> | undefined,\n includeInitialState: boolean = false\n ) {\n const sendChanges = (\n changes: Array<ChangeMessage<any, string | number>>\n ) => {\n this.sendChangesToPipeline(changes)\n }\n\n const subscription = this.collection.subscribeChanges(sendChanges, {\n includeInitialState,\n whereExpression,\n })\n\n return subscription\n }\n\n private subscribeToOrderedChanges(\n whereExpression: BasicExpression<boolean> | undefined\n ) {\n const { orderBy, offset, limit, comparator, dataNeeded, index } =\n this.collectionConfigBuilder.optimizableOrderByCollections[\n this.collectionId\n ]!\n\n const sendChangesInRange = (\n changes: Iterable<ChangeMessage<any, string | number>>\n ) => {\n // Split live updates into a delete of the old value and an insert of the new value\n // and filter out changes that are bigger than the biggest value we've sent so far\n // because they can't affect the topK (and if later we need more data, we will dynamically load more data)\n const splittedChanges = splitUpdates(changes)\n let filteredChanges = splittedChanges\n if (dataNeeded!() === 0) {\n // If the topK is full [..., maxSentValue] then we do not need to send changes > maxSentValue\n // because they can never make it into the topK.\n // However, if the topK isn't full yet, we need to also send changes > maxSentValue\n // because they will make it into the topK\n filteredChanges = filterChangesSmallerOrEqualToMax(\n splittedChanges,\n comparator,\n this.biggest\n )\n }\n\n this.sendChangesToPipelineWithTracking(filteredChanges, subscription)\n }\n\n // Subscribe to changes and only send changes that are smaller than the biggest value we've sent so far\n // values that are bigger don't need to be sent because they can't affect the topK\n const subscription = this.collection.subscribeChanges(sendChangesInRange, {\n whereExpression,\n })\n\n subscription.setOrderByIndex(index)\n\n // Normalize the orderBy clauses such that the references are relative to the collection\n const normalizedOrderBy = convertOrderByToBasicExpression(\n orderBy,\n this.collectionAlias\n )\n\n // Load the first `offset + limit` values from the index\n // i.e. the K items from the collection that fall into the requested range: [offset, offset + limit[\n subscription.requestLimitedSnapshot({\n limit: offset + limit,\n orderBy: normalizedOrderBy,\n })\n\n return subscription\n }\n\n // This function is called by maybeRunGraph\n // after each iteration of the query pipeline\n // to ensure that the orderBy operator has enough data to work with\n loadMoreIfNeeded(subscription: CollectionSubscription) {\n const orderByInfo =\n this.collectionConfigBuilder.optimizableOrderByCollections[\n this.collectionId\n ]\n\n if (!orderByInfo) {\n // This query has no orderBy operator\n // so there's no data to load\n return true\n }\n\n const { dataNeeded } = orderByInfo\n\n if (!dataNeeded) {\n // This should never happen because the topK operator should always set the size callback\n // which in turn should lead to the orderBy operator setting the dataNeeded callback\n throw new Error(\n `Missing dataNeeded callback for collection ${this.collectionId}`\n )\n }\n\n // `dataNeeded` probes the orderBy operator to see if it needs more data\n // if it needs more data, it returns the number of items it needs\n const n = dataNeeded()\n if (n > 0) {\n this.loadNextItems(n, subscription)\n }\n return true\n }\n\n private sendChangesToPipelineWithTracking(\n changes: Iterable<ChangeMessage<any, string | number>>,\n subscription: CollectionSubscription\n ) {\n const { comparator } =\n this.collectionConfigBuilder.optimizableOrderByCollections[\n this.collectionId\n ]!\n const trackedChanges = this.trackSentValues(changes, comparator)\n this.sendChangesToPipeline(\n trackedChanges,\n this.loadMoreIfNeeded.bind(this, subscription)\n )\n }\n\n // Loads the next `n` items from the collection\n // starting from the biggest item it has sent\n private loadNextItems(n: number, subscription: CollectionSubscription) {\n const { orderBy, valueExtractorForRawRow } =\n this.collectionConfigBuilder.optimizableOrderByCollections[\n this.collectionId\n ]!\n const biggestSentRow = this.biggest\n const biggestSentValue = biggestSentRow\n ? valueExtractorForRawRow(biggestSentRow)\n : biggestSentRow\n\n // Normalize the orderBy clauses such that the references are relative to the collection\n const normalizedOrderBy = convertOrderByToBasicExpression(\n orderBy,\n this.collectionAlias\n )\n\n // Take the `n` items after the biggest sent value\n subscription.requestLimitedSnapshot({\n orderBy: normalizedOrderBy,\n limit: n,\n minValue: biggestSentValue,\n })\n }\n\n private getWhereClauseFromAlias(\n collectionAlias: string | undefined\n ): BasicExpression<boolean> | undefined {\n const collectionWhereClausesCache =\n this.collectionConfigBuilder.collectionWhereClausesCache\n if (collectionAlias && collectionWhereClausesCache) {\n return collectionWhereClausesCache.get(collectionAlias)\n }\n return undefined\n }\n\n private *trackSentValues(\n changes: Iterable<ChangeMessage<any, string | number>>,\n comparator: (a: any, b: any) => number\n ) {\n for (const change of changes) {\n if (!this.biggest) {\n this.biggest = change.value\n } else if (comparator(this.biggest, change.value) < 0) {\n this.biggest = change.value\n }\n\n yield change\n }\n }\n}\n\n/**\n * Finds the alias for a collection ID in the query\n */\nfunction findCollectionAlias(\n collectionId: string,\n query: any\n): string | undefined {\n // Check FROM clause\n if (\n query.from?.type === `collectionRef` &&\n query.from.collection?.id === collectionId\n ) {\n return query.from.alias\n }\n\n // Check JOIN clauses\n if (query.join) {\n for (const joinClause of query.join) {\n if (\n joinClause.from?.type === `collectionRef` &&\n joinClause.from.collection?.id === collectionId\n ) {\n return joinClause.from.alias\n }\n }\n }\n\n return undefined\n}\n\n/**\n * Helper function to send changes to a D2 input stream\n */\nfunction sendChangesToInput(\n input: RootStreamBuilder<unknown>,\n changes: Iterable<ChangeMessage>,\n getKey: (item: ChangeMessage[`value`]) => any\n): number {\n const multiSetArray: MultiSetArray<unknown> = []\n for (const change of changes) {\n const key = getKey(change.value)\n if (change.type === `insert`) {\n multiSetArray.push([[key, change.value], 1])\n } else if (change.type === `update`) {\n multiSetArray.push([[key, change.previousValue], -1])\n multiSetArray.push([[key, change.value], 1])\n } else {\n // change.type === `delete`\n multiSetArray.push([[key, change.value], -1])\n }\n }\n\n if (multiSetArray.length !== 0) {\n input.sendData(new MultiSet(multiSetArray))\n }\n\n return multiSetArray.length\n}\n\n/** Splits updates into a delete of the old value and an insert of the new value */\nfunction* splitUpdates<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>\n): Generator<ChangeMessage<T, TKey>> {\n for (const change of changes) {\n if (change.type === `update`) {\n yield { type: `delete`, key: change.key, value: change.previousValue! }\n yield { type: `insert`, key: change.key, value: change.value }\n } else {\n yield change\n }\n }\n}\n\nfunction* filterChanges<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>,\n f: (change: ChangeMessage<T, TKey>) => boolean\n): Generator<ChangeMessage<T, TKey>> {\n for (const change of changes) {\n if (f(change)) {\n yield change\n }\n }\n}\n\n/**\n * Filters changes to only include those that are smaller or equal to the provided max value\n * @param changes - Iterable of changes to filter\n * @param comparator - Comparator function to use for filtering\n * @param maxValue - Range to filter changes within (range boundaries are exclusive)\n * @returns Iterable of changes that fall within the range\n */\nfunction* filterChangesSmallerOrEqualToMax<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>,\n comparator: (a: any, b: any) => number,\n maxValue: any\n): Generator<ChangeMessage<T, TKey>> {\n yield* filterChanges(changes, (change) => {\n return !maxValue || comparator(change.value, maxValue) <= 0\n })\n}\n"],"names":["convertToBasicExpression","convertOrderByToBasicExpression","MultiSet"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"collection-subscriber.cjs","sources":["../../../../src/query/live/collection-subscriber.ts"],"sourcesContent":["import { MultiSet } from \"@tanstack/db-ivm\"\nimport {\n convertOrderByToBasicExpression,\n convertToBasicExpression,\n} from \"../compiler/expressions.js\"\nimport { WhereClauseConversionError } from \"../../errors.js\"\nimport type { MultiSetArray, RootStreamBuilder } from \"@tanstack/db-ivm\"\nimport type { Collection } from \"../../collection/index.js\"\nimport type { ChangeMessage } from \"../../types.js\"\nimport type { Context, GetResult } from \"../builder/types.js\"\nimport type { BasicExpression } from \"../ir.js\"\nimport type { OrderByOptimizationInfo } from \"../compiler/order-by.js\"\nimport type { CollectionConfigBuilder } from \"./collection-config-builder.js\"\nimport type { CollectionSubscription } from \"../../collection/subscription.js\"\n\nconst loadMoreCallbackSymbol = Symbol.for(\n `@tanstack/db.collection-config-builder`\n)\n\nexport class CollectionSubscriber<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n> {\n // Keep track of the biggest value we've sent so far (needed for orderBy optimization)\n private biggest: any = undefined\n\n constructor(\n private alias: string,\n private collectionId: string,\n private collection: Collection,\n private collectionConfigBuilder: CollectionConfigBuilder<TContext, TResult>\n ) {}\n\n subscribe(): CollectionSubscription {\n const whereClause = this.getWhereClauseForAlias()\n\n if (whereClause) {\n const whereExpression = convertToBasicExpression(whereClause, this.alias)\n\n if (whereExpression) {\n return this.subscribeToChanges(whereExpression)\n }\n\n throw new WhereClauseConversionError(this.collectionId, this.alias)\n }\n\n return this.subscribeToChanges()\n }\n\n private subscribeToChanges(whereExpression?: BasicExpression<boolean>) {\n let subscription: CollectionSubscription\n const orderByInfo = this.getOrderByInfo()\n if (orderByInfo) {\n subscription = this.subscribeToOrderedChanges(\n whereExpression,\n orderByInfo\n )\n } else {\n // If the source alias is lazy then we should not include the initial state\n const includeInitialState = !this.collectionConfigBuilder.isLazyAlias(\n this.alias\n )\n\n subscription = this.subscribeToMatchingChanges(\n whereExpression,\n includeInitialState\n )\n }\n const unsubscribe = () => {\n subscription.unsubscribe()\n }\n // currentSyncState is always defined when subscribe() is called\n // (called during sync session setup)\n this.collectionConfigBuilder.currentSyncState!.unsubscribeCallbacks.add(\n unsubscribe\n )\n return subscription\n }\n\n private sendChangesToPipeline(\n changes: Iterable<ChangeMessage<any, string | number>>,\n callback?: () => boolean\n ) {\n // currentSyncState and input are always defined when this method is called\n // (only called from active subscriptions during a sync session)\n const input =\n this.collectionConfigBuilder.currentSyncState!.inputs[this.alias]!\n const sentChanges = sendChangesToInput(\n input,\n changes,\n this.collection.config.getKey\n )\n\n // Do not provide the callback that loads more data\n // if there's no more data to load\n // otherwise we end up in an infinite loop trying to load more data\n const dataLoader = sentChanges > 0 ? callback : undefined\n\n // We need to schedule a graph run even if there's no data to load\n // because we need to mark the collection as ready if it's not already\n // and that's only done in `scheduleGraphRun`\n this.collectionConfigBuilder.scheduleGraphRun(dataLoader, {\n alias: this.alias,\n })\n }\n\n private subscribeToMatchingChanges(\n whereExpression: BasicExpression<boolean> | undefined,\n includeInitialState: boolean = false\n ) {\n const sendChanges = (\n changes: Array<ChangeMessage<any, string | number>>\n ) => {\n this.sendChangesToPipeline(changes)\n }\n\n const subscription = this.collection.subscribeChanges(sendChanges, {\n includeInitialState,\n whereExpression,\n })\n\n return subscription\n }\n\n private subscribeToOrderedChanges(\n whereExpression: BasicExpression<boolean> | undefined,\n orderByInfo: OrderByOptimizationInfo\n ) {\n const { orderBy, offset, limit, comparator, dataNeeded, index } =\n orderByInfo\n\n const sendChangesInRange = (\n changes: Iterable<ChangeMessage<any, string | number>>\n ) => {\n // Split live updates into a delete of the old value and an insert of the new value\n // and filter out changes that are bigger than the biggest value we've sent so far\n // because they can't affect the topK (and if later we need more data, we will dynamically load more data)\n const splittedChanges = splitUpdates(changes)\n let filteredChanges = splittedChanges\n if (dataNeeded && dataNeeded() === 0) {\n // If the topK is full [..., maxSentValue] then we do not need to send changes > maxSentValue\n // because they can never make it into the topK.\n // However, if the topK isn't full yet, we need to also send changes > maxSentValue\n // because they will make it into the topK\n filteredChanges = filterChangesSmallerOrEqualToMax(\n splittedChanges,\n comparator,\n this.biggest\n )\n }\n\n this.sendChangesToPipelineWithTracking(filteredChanges, subscription)\n }\n\n // Subscribe to changes and only send changes that are smaller than the biggest value we've sent so far\n // values that are bigger don't need to be sent because they can't affect the topK\n const subscription = this.collection.subscribeChanges(sendChangesInRange, {\n whereExpression,\n })\n\n subscription.setOrderByIndex(index)\n\n // Normalize the orderBy clauses such that the references are relative to the collection\n const normalizedOrderBy = convertOrderByToBasicExpression(\n orderBy,\n this.alias\n )\n\n // Load the first `offset + limit` values from the index\n // i.e. the K items from the collection that fall into the requested range: [offset, offset + limit[\n subscription.requestLimitedSnapshot({\n limit: offset + limit,\n orderBy: normalizedOrderBy,\n })\n\n return subscription\n }\n\n // This function is called by maybeRunGraph\n // after each iteration of the query pipeline\n // to ensure that the orderBy operator has enough data to work with\n loadMoreIfNeeded(subscription: CollectionSubscription) {\n const orderByInfo = this.getOrderByInfo()\n\n if (!orderByInfo) {\n // This query has no orderBy operator\n // so there's no data to load\n return true\n }\n\n const { dataNeeded } = orderByInfo\n\n if (!dataNeeded) {\n // This should never happen because the topK operator should always set the size callback\n // which in turn should lead to the orderBy operator setting the dataNeeded callback\n throw new Error(\n `Missing dataNeeded callback for collection ${this.collectionId}`\n )\n }\n\n // `dataNeeded` probes the orderBy operator to see if it needs more data\n // if it needs more data, it returns the number of items it needs\n const n = dataNeeded()\n if (n > 0) {\n this.loadNextItems(n, subscription)\n }\n return true\n }\n\n private sendChangesToPipelineWithTracking(\n changes: Iterable<ChangeMessage<any, string | number>>,\n subscription: CollectionSubscription\n ) {\n const orderByInfo = this.getOrderByInfo()\n if (!orderByInfo) {\n this.sendChangesToPipeline(changes)\n return\n }\n\n const trackedChanges = this.trackSentValues(changes, orderByInfo.comparator)\n\n // Cache the loadMoreIfNeeded callback on the subscription using a symbol property.\n // This ensures we pass the same function instance to the scheduler each time,\n // allowing it to deduplicate callbacks when multiple changes arrive during a transaction.\n type SubscriptionWithLoader = CollectionSubscription & {\n [loadMoreCallbackSymbol]?: () => boolean\n }\n\n const subscriptionWithLoader = subscription as SubscriptionWithLoader\n\n subscriptionWithLoader[loadMoreCallbackSymbol] ??=\n this.loadMoreIfNeeded.bind(this, subscription)\n\n this.sendChangesToPipeline(\n trackedChanges,\n subscriptionWithLoader[loadMoreCallbackSymbol]\n )\n }\n\n // Loads the next `n` items from the collection\n // starting from the biggest item it has sent\n private loadNextItems(n: number, subscription: CollectionSubscription) {\n const orderByInfo = this.getOrderByInfo()\n if (!orderByInfo) {\n return\n }\n const { orderBy, valueExtractorForRawRow } = orderByInfo\n const biggestSentRow = this.biggest\n const biggestSentValue = biggestSentRow\n ? valueExtractorForRawRow(biggestSentRow)\n : biggestSentRow\n\n // Normalize the orderBy clauses such that the references are relative to the collection\n const normalizedOrderBy = convertOrderByToBasicExpression(\n orderBy,\n this.alias\n )\n\n // Take the `n` items after the biggest sent value\n subscription.requestLimitedSnapshot({\n orderBy: normalizedOrderBy,\n limit: n,\n minValue: biggestSentValue,\n })\n }\n\n private getWhereClauseForAlias(): BasicExpression<boolean> | undefined {\n const sourceWhereClausesCache =\n this.collectionConfigBuilder.sourceWhereClausesCache\n if (!sourceWhereClausesCache) {\n return undefined\n }\n return sourceWhereClausesCache.get(this.alias)\n }\n\n private getOrderByInfo(): OrderByOptimizationInfo | undefined {\n const info =\n this.collectionConfigBuilder.optimizableOrderByCollections[\n this.collectionId\n ]\n if (info && info.alias === this.alias) {\n return info\n }\n return undefined\n }\n\n private *trackSentValues(\n changes: Iterable<ChangeMessage<any, string | number>>,\n comparator: (a: any, b: any) => number\n ) {\n for (const change of changes) {\n if (!this.biggest) {\n this.biggest = change.value\n } else if (comparator(this.biggest, change.value) < 0) {\n this.biggest = change.value\n }\n\n yield change\n }\n }\n}\n\n/**\n * Helper function to send changes to a D2 input stream\n */\nfunction sendChangesToInput(\n input: RootStreamBuilder<unknown>,\n changes: Iterable<ChangeMessage>,\n getKey: (item: ChangeMessage[`value`]) => any\n): number {\n const multiSetArray: MultiSetArray<unknown> = []\n for (const change of changes) {\n const key = getKey(change.value)\n if (change.type === `insert`) {\n multiSetArray.push([[key, change.value], 1])\n } else if (change.type === `update`) {\n multiSetArray.push([[key, change.previousValue], -1])\n multiSetArray.push([[key, change.value], 1])\n } else {\n // change.type === `delete`\n multiSetArray.push([[key, change.value], -1])\n }\n }\n\n if (multiSetArray.length !== 0) {\n input.sendData(new MultiSet(multiSetArray))\n }\n\n return multiSetArray.length\n}\n\n/** Splits updates into a delete of the old value and an insert of the new value */\nfunction* splitUpdates<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>\n): Generator<ChangeMessage<T, TKey>> {\n for (const change of changes) {\n if (change.type === `update`) {\n yield { type: `delete`, key: change.key, value: change.previousValue! }\n yield { type: `insert`, key: change.key, value: change.value }\n } else {\n yield change\n }\n }\n}\n\nfunction* filterChanges<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>,\n f: (change: ChangeMessage<T, TKey>) => boolean\n): Generator<ChangeMessage<T, TKey>> {\n for (const change of changes) {\n if (f(change)) {\n yield change\n }\n }\n}\n\n/**\n * Filters changes to only include those that are smaller or equal to the provided max value\n * @param changes - Iterable of changes to filter\n * @param comparator - Comparator function to use for filtering\n * @param maxValue - Range to filter changes within (range boundaries are exclusive)\n * @returns Iterable of changes that fall within the range\n */\nfunction* filterChangesSmallerOrEqualToMax<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n changes: Iterable<ChangeMessage<T, TKey>>,\n comparator: (a: any, b: any) => number,\n maxValue: any\n): Generator<ChangeMessage<T, TKey>> {\n yield* filterChanges(changes, (change) => {\n return !maxValue || comparator(change.value, maxValue) <= 0\n })\n}\n"],"names":["convertToBasicExpression","WhereClauseConversionError","convertOrderByToBasicExpression","MultiSet"],"mappings":"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"}
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
import { FullSyncState } from './types.js';
|
|
2
1
|
import { Collection } from '../../collection/index.js';
|
|
3
|
-
import { SyncConfig } from '../../types.js';
|
|
4
2
|
import { Context, GetResult } from '../builder/types.js';
|
|
5
3
|
import { CollectionConfigBuilder } from './collection-config-builder.js';
|
|
6
4
|
import { CollectionSubscription } from '../../collection/subscription.js';
|
|
7
5
|
export declare class CollectionSubscriber<TContext extends Context, TResult extends object = GetResult<TContext>> {
|
|
6
|
+
private alias;
|
|
8
7
|
private collectionId;
|
|
9
8
|
private collection;
|
|
10
|
-
private config;
|
|
11
|
-
private syncState;
|
|
12
9
|
private collectionConfigBuilder;
|
|
13
10
|
private biggest;
|
|
14
|
-
|
|
15
|
-
constructor(collectionId: string, collection: Collection, config: Parameters<SyncConfig<TResult>[`sync`]>[0], syncState: FullSyncState, collectionConfigBuilder: CollectionConfigBuilder<TContext, TResult>);
|
|
11
|
+
constructor(alias: string, collectionId: string, collection: Collection, collectionConfigBuilder: CollectionConfigBuilder<TContext, TResult>);
|
|
16
12
|
subscribe(): CollectionSubscription;
|
|
17
13
|
private subscribeToChanges;
|
|
18
14
|
private sendChangesToPipeline;
|
|
@@ -21,6 +17,7 @@ export declare class CollectionSubscriber<TContext extends Context, TResult exte
|
|
|
21
17
|
loadMoreIfNeeded(subscription: CollectionSubscription): boolean;
|
|
22
18
|
private sendChangesToPipelineWithTracking;
|
|
23
19
|
private loadNextItems;
|
|
24
|
-
private
|
|
20
|
+
private getWhereClauseForAlias;
|
|
21
|
+
private getOrderByInfo;
|
|
25
22
|
private trackSentValues;
|
|
26
23
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const index = require("../collection/index.cjs");
|
|
4
4
|
const collectionConfigBuilder = require("./live/collection-config-builder.cjs");
|
|
5
|
+
const collectionRegistry = require("./live/collection-registry.cjs");
|
|
5
6
|
function liveQueryCollectionOptions(config) {
|
|
6
7
|
const collectionConfigBuilder$1 = new collectionConfigBuilder.CollectionConfigBuilder(config);
|
|
7
8
|
return collectionConfigBuilder$1.getConfig();
|
|
@@ -16,14 +17,19 @@ function createLiveQueryCollection(configOrQuery) {
|
|
|
16
17
|
} else {
|
|
17
18
|
const config = configOrQuery;
|
|
18
19
|
const options = liveQueryCollectionOptions(config);
|
|
19
|
-
|
|
20
|
-
...options,
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
if (config.utils) {
|
|
21
|
+
options.utils = { ...options.utils, ...config.utils };
|
|
22
|
+
}
|
|
23
|
+
return bridgeToCreateCollection(options);
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
function bridgeToCreateCollection(options) {
|
|
26
|
-
|
|
27
|
+
const collection = index.createCollection(options);
|
|
28
|
+
const builder = collectionRegistry.getBuilderFromConfig(options);
|
|
29
|
+
if (builder) {
|
|
30
|
+
collectionRegistry.registerCollectionBuilder(collection, builder);
|
|
31
|
+
}
|
|
32
|
+
return collection;
|
|
27
33
|
}
|
|
28
34
|
exports.createLiveQueryCollection = createLiveQueryCollection;
|
|
29
35
|
exports.liveQueryCollectionOptions = liveQueryCollectionOptions;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-query-collection.cjs","sources":["../../../src/query/live-query-collection.ts"],"sourcesContent":["import { createCollection } from \"../collection/index.js\"\nimport { CollectionConfigBuilder } from \"./live/collection-config-builder.js\"\nimport type { LiveQueryCollectionConfig } from \"./live/types.js\"\nimport type { InitialQueryBuilder, QueryBuilder } from \"./builder/index.js\"\nimport type { Collection } from \"../collection/index.js\"\nimport type {\n CollectionConfig,\n CollectionConfigSingleRowOption,\n NonSingleResult,\n SingleResult,\n UtilsRecord,\n} from \"../types.js\"\nimport type { Context, GetResult } from \"./builder/types.js\"\n\ntype CollectionConfigForContext<\n TContext extends Context,\n TResult extends object,\n> = TContext extends SingleResult\n ? CollectionConfigSingleRowOption<TResult> & SingleResult\n : CollectionConfigSingleRowOption<TResult> & NonSingleResult\n\ntype CollectionForContext<\n TContext extends Context,\n TResult extends object,\n> = TContext extends SingleResult\n ? Collection<TResult> & SingleResult\n : Collection<TResult> & NonSingleResult\n\n/**\n * Creates live query collection options for use with createCollection\n *\n * @example\n * ```typescript\n * const options = liveQueryCollectionOptions({\n * // id is optional - will auto-generate if not provided\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => eq(post.published, true))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * content: post.content,\n * })),\n * // getKey is optional - will use stream key if not provided\n * })\n *\n * const collection = createCollection(options)\n * ```\n *\n * @param config - Configuration options for the live query collection\n * @returns Collection options that can be passed to createCollection\n */\nexport function liveQueryCollectionOptions<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n config: LiveQueryCollectionConfig<TContext, TResult>\n): CollectionConfigForContext<TContext, TResult> {\n const collectionConfigBuilder = new CollectionConfigBuilder<\n TContext,\n TResult\n >(config)\n return collectionConfigBuilder.getConfig() as CollectionConfigForContext<\n TContext,\n TResult\n
|
|
1
|
+
{"version":3,"file":"live-query-collection.cjs","sources":["../../../src/query/live-query-collection.ts"],"sourcesContent":["import { createCollection } from \"../collection/index.js\"\nimport { CollectionConfigBuilder } from \"./live/collection-config-builder.js\"\nimport {\n getBuilderFromConfig,\n registerCollectionBuilder,\n} from \"./live/collection-registry.js\"\nimport type { LiveQueryCollectionUtils } from \"./live/collection-config-builder.js\"\nimport type { LiveQueryCollectionConfig } from \"./live/types.js\"\nimport type { InitialQueryBuilder, QueryBuilder } from \"./builder/index.js\"\nimport type { Collection } from \"../collection/index.js\"\nimport type {\n CollectionConfig,\n CollectionConfigSingleRowOption,\n NonSingleResult,\n SingleResult,\n UtilsRecord,\n} from \"../types.js\"\nimport type { Context, GetResult } from \"./builder/types.js\"\n\ntype CollectionConfigForContext<\n TContext extends Context,\n TResult extends object,\n> = TContext extends SingleResult\n ? CollectionConfigSingleRowOption<TResult> & SingleResult\n : CollectionConfigSingleRowOption<TResult> & NonSingleResult\n\ntype CollectionForContext<\n TContext extends Context,\n TResult extends object,\n> = TContext extends SingleResult\n ? Collection<TResult> & SingleResult\n : Collection<TResult> & NonSingleResult\n\n/**\n * Creates live query collection options for use with createCollection\n *\n * @example\n * ```typescript\n * const options = liveQueryCollectionOptions({\n * // id is optional - will auto-generate if not provided\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => eq(post.published, true))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * content: post.content,\n * })),\n * // getKey is optional - will use stream key if not provided\n * })\n *\n * const collection = createCollection(options)\n * ```\n *\n * @param config - Configuration options for the live query collection\n * @returns Collection options that can be passed to createCollection\n */\nexport function liveQueryCollectionOptions<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n config: LiveQueryCollectionConfig<TContext, TResult>\n): CollectionConfigForContext<TContext, TResult> & {\n utils: LiveQueryCollectionUtils\n} {\n const collectionConfigBuilder = new CollectionConfigBuilder<\n TContext,\n TResult\n >(config)\n return collectionConfigBuilder.getConfig() as CollectionConfigForContext<\n TContext,\n TResult\n > & { utils: LiveQueryCollectionUtils }\n}\n\n/**\n * Creates a live query collection directly\n *\n * @example\n * ```typescript\n * // Minimal usage - just pass a query function\n * const activeUsers = createLiveQueryCollection(\n * (q) => q\n * .from({ user: usersCollection })\n * .where(({ user }) => eq(user.active, true))\n * .select(({ user }) => ({ id: user.id, name: user.name }))\n * )\n *\n * // Full configuration with custom options\n * const searchResults = createLiveQueryCollection({\n * id: \"search-results\", // Custom ID (auto-generated if omitted)\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => like(post.title, `%${searchTerm}%`))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * excerpt: post.excerpt,\n * })),\n * getKey: (item) => item.id, // Custom key function (uses stream key if omitted)\n * utils: {\n * updateSearchTerm: (newTerm: string) => {\n * // Custom utility functions\n * }\n * }\n * })\n * ```\n */\n\n// Overload 1: Accept just the query function\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n query: (q: InitialQueryBuilder) => QueryBuilder<TContext>\n): CollectionForContext<TContext, TResult> & {\n utils: LiveQueryCollectionUtils\n}\n\n// Overload 2: Accept full config object with optional utilities\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n config: LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils }\n): CollectionForContext<TContext, TResult> & {\n utils: LiveQueryCollectionUtils & TUtils\n}\n\n// Implementation\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n configOrQuery:\n | (LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils })\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n): CollectionForContext<TContext, TResult> & {\n utils: LiveQueryCollectionUtils & TUtils\n} {\n // Determine if the argument is a function (query) or a config object\n if (typeof configOrQuery === `function`) {\n // Simple query function case\n const config: LiveQueryCollectionConfig<TContext, TResult> = {\n query: configOrQuery as (\n q: InitialQueryBuilder\n ) => QueryBuilder<TContext>,\n }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n return bridgeToCreateCollection(options) as CollectionForContext<\n TContext,\n TResult\n > & { utils: LiveQueryCollectionUtils & TUtils }\n } else {\n // Config object case\n const config = configOrQuery as LiveQueryCollectionConfig<\n TContext,\n TResult\n > & { utils?: TUtils }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n\n // Merge custom utils if provided, preserving the getBuilder() method for dependency tracking\n if (config.utils) {\n options.utils = { ...options.utils, ...config.utils }\n }\n\n return bridgeToCreateCollection(options) as CollectionForContext<\n TContext,\n TResult\n > & { utils: LiveQueryCollectionUtils & TUtils }\n }\n}\n\n/**\n * Bridge function that handles the type compatibility between query2's TResult\n * and core collection's output type without exposing ugly type assertions to users\n */\nfunction bridgeToCreateCollection<\n TResult extends object,\n TUtils extends UtilsRecord = {},\n>(\n options: CollectionConfig<TResult> & { utils: TUtils }\n): Collection<TResult, string | number, TUtils> {\n const collection = createCollection(options as any) as unknown as Collection<\n TResult,\n string | number,\n LiveQueryCollectionUtils\n >\n\n const builder = getBuilderFromConfig(options)\n if (builder) {\n registerCollectionBuilder(collection, builder)\n }\n\n return collection as unknown as Collection<TResult, string | number, TUtils>\n}\n"],"names":["collectionConfigBuilder","CollectionConfigBuilder","createCollection","getBuilderFromConfig","registerCollectionBuilder"],"mappings":";;;;;AAyDO,SAAS,2BAId,QAGA;AACA,QAAMA,4BAA0B,IAAIC,wBAAAA,wBAGlC,MAAM;AACR,SAAOD,0BAAwB,UAAA;AAIjC;AA0DO,SAAS,0BAKd,eAKA;AAEA,MAAI,OAAO,kBAAkB,YAAY;AAEvC,UAAM,SAAuD;AAAA,MAC3D,OAAO;AAAA,IAAA;AAIT,UAAM,UAAU,2BAA8C,MAAM;AACpE,WAAO,yBAAyB,OAAO;AAAA,EAIzC,OAAO;AAEL,UAAM,SAAS;AAIf,UAAM,UAAU,2BAA8C,MAAM;AAGpE,QAAI,OAAO,OAAO;AAChB,cAAQ,QAAQ,EAAE,GAAG,QAAQ,OAAO,GAAG,OAAO,MAAA;AAAA,IAChD;AAEA,WAAO,yBAAyB,OAAO;AAAA,EAIzC;AACF;AAMA,SAAS,yBAIP,SAC8C;AAC9C,QAAM,aAAaE,MAAAA,iBAAiB,OAAc;AAMlD,QAAM,UAAUC,mBAAAA,qBAAqB,OAAO;AAC5C,MAAI,SAAS;AACXC,uBAAAA,0BAA0B,YAAY,OAAO;AAAA,EAC/C;AAEA,SAAO;AACT;;;"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LiveQueryCollectionUtils } from './live/collection-config-builder.js';
|
|
1
2
|
import { LiveQueryCollectionConfig } from './live/types.js';
|
|
2
3
|
import { InitialQueryBuilder, QueryBuilder } from './builder/index.js';
|
|
3
4
|
import { Collection } from '../collection/index.js';
|
|
@@ -29,7 +30,9 @@ type CollectionForContext<TContext extends Context, TResult extends object> = TC
|
|
|
29
30
|
* @param config - Configuration options for the live query collection
|
|
30
31
|
* @returns Collection options that can be passed to createCollection
|
|
31
32
|
*/
|
|
32
|
-
export declare function liveQueryCollectionOptions<TContext extends Context, TResult extends object = GetResult<TContext>>(config: LiveQueryCollectionConfig<TContext, TResult>): CollectionConfigForContext<TContext, TResult
|
|
33
|
+
export declare function liveQueryCollectionOptions<TContext extends Context, TResult extends object = GetResult<TContext>>(config: LiveQueryCollectionConfig<TContext, TResult>): CollectionConfigForContext<TContext, TResult> & {
|
|
34
|
+
utils: LiveQueryCollectionUtils;
|
|
35
|
+
};
|
|
33
36
|
/**
|
|
34
37
|
* Creates a live query collection directly
|
|
35
38
|
*
|
|
@@ -63,8 +66,12 @@ export declare function liveQueryCollectionOptions<TContext extends Context, TRe
|
|
|
63
66
|
* })
|
|
64
67
|
* ```
|
|
65
68
|
*/
|
|
66
|
-
export declare function createLiveQueryCollection<TContext extends Context, TResult extends object = GetResult<TContext>>(query: (q: InitialQueryBuilder) => QueryBuilder<TContext>): CollectionForContext<TContext, TResult
|
|
69
|
+
export declare function createLiveQueryCollection<TContext extends Context, TResult extends object = GetResult<TContext>>(query: (q: InitialQueryBuilder) => QueryBuilder<TContext>): CollectionForContext<TContext, TResult> & {
|
|
70
|
+
utils: LiveQueryCollectionUtils;
|
|
71
|
+
};
|
|
67
72
|
export declare function createLiveQueryCollection<TContext extends Context, TResult extends object = GetResult<TContext>, TUtils extends UtilsRecord = {}>(config: LiveQueryCollectionConfig<TContext, TResult> & {
|
|
68
73
|
utils?: TUtils;
|
|
69
|
-
}): CollectionForContext<TContext, TResult
|
|
74
|
+
}): CollectionForContext<TContext, TResult> & {
|
|
75
|
+
utils: LiveQueryCollectionUtils & TUtils;
|
|
76
|
+
};
|
|
70
77
|
export {};
|