@tanstack/db 0.5.33 → 0.6.0
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/change-events.cjs.map +1 -1
- package/dist/cjs/collection/change-events.d.cts +3 -2
- package/dist/cjs/collection/changes.cjs +13 -4
- package/dist/cjs/collection/changes.cjs.map +1 -1
- package/dist/cjs/collection/changes.d.cts +10 -1
- package/dist/cjs/collection/cleanup-queue.cjs +89 -0
- package/dist/cjs/collection/cleanup-queue.cjs.map +1 -0
- package/dist/cjs/collection/cleanup-queue.d.cts +30 -0
- package/dist/cjs/collection/events.cjs +14 -0
- package/dist/cjs/collection/events.cjs.map +1 -1
- package/dist/cjs/collection/events.d.cts +39 -1
- package/dist/cjs/collection/index.cjs +66 -28
- package/dist/cjs/collection/index.cjs.map +1 -1
- package/dist/cjs/collection/index.d.cts +49 -36
- package/dist/cjs/collection/indexes.cjs +211 -62
- package/dist/cjs/collection/indexes.cjs.map +1 -1
- package/dist/cjs/collection/indexes.d.cts +27 -17
- package/dist/cjs/collection/lifecycle.cjs +5 -22
- package/dist/cjs/collection/lifecycle.cjs.map +1 -1
- package/dist/cjs/collection/lifecycle.d.cts +0 -1
- package/dist/cjs/collection/mutations.cjs +18 -0
- package/dist/cjs/collection/mutations.cjs.map +1 -1
- package/dist/cjs/collection/mutations.d.cts +1 -0
- package/dist/cjs/collection/state.cjs +381 -53
- package/dist/cjs/collection/state.cjs.map +1 -1
- package/dist/cjs/collection/state.d.cts +65 -1
- package/dist/cjs/collection/subscription.cjs +6 -0
- package/dist/cjs/collection/subscription.cjs.map +1 -1
- package/dist/cjs/collection/subscription.d.cts +4 -0
- package/dist/cjs/collection/sync.cjs +108 -1
- package/dist/cjs/collection/sync.cjs.map +1 -1
- package/dist/cjs/collection/sync.d.cts +2 -0
- package/dist/cjs/collection/transaction-metadata.cjs +5 -0
- package/dist/cjs/collection/transaction-metadata.cjs.map +1 -0
- package/dist/cjs/collection/transaction-metadata.d.cts +1 -0
- package/dist/cjs/errors.cjs +8 -0
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +3 -0
- package/dist/cjs/index.cjs +22 -4
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +11 -3
- package/dist/cjs/indexes/auto-index.cjs +13 -6
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/indexes/base-index.cjs +0 -3
- package/dist/cjs/indexes/base-index.cjs.map +1 -1
- package/dist/cjs/indexes/base-index.d.cts +2 -6
- package/dist/cjs/indexes/basic-index.cjs +361 -0
- package/dist/cjs/indexes/basic-index.cjs.map +1 -0
- package/dist/cjs/indexes/basic-index.d.cts +102 -0
- package/dist/cjs/indexes/btree-index.cjs.map +1 -1
- package/dist/cjs/indexes/btree-index.d.cts +1 -1
- package/dist/cjs/indexes/index-options.d.cts +8 -9
- package/dist/cjs/indexes/index-registry.cjs +89 -0
- package/dist/cjs/indexes/index-registry.cjs.map +1 -0
- package/dist/cjs/indexes/index-registry.d.cts +61 -0
- package/dist/cjs/local-only.cjs +5 -0
- package/dist/cjs/local-only.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.cjs +27 -11
- package/dist/cjs/query/builder/functions.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.d.cts +25 -3
- package/dist/cjs/query/builder/index.cjs +200 -39
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/index.d.cts +4 -3
- package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
- package/dist/cjs/query/builder/ref-proxy.d.cts +14 -3
- package/dist/cjs/query/builder/types.d.cts +84 -19
- package/dist/cjs/query/compiler/evaluators.cjs +51 -0
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.cjs +100 -28
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.d.cts +4 -2
- package/dist/cjs/query/compiler/index.cjs +283 -11
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.d.cts +30 -2
- package/dist/cjs/query/compiler/order-by.cjs +29 -10
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +1 -1
- package/dist/cjs/query/compiler/select.cjs +8 -0
- package/dist/cjs/query/compiler/select.cjs.map +1 -1
- package/dist/cjs/query/index.d.cts +2 -1
- package/dist/cjs/query/ir.cjs +18 -1
- package/dist/cjs/query/ir.cjs.map +1 -1
- package/dist/cjs/query/ir.d.cts +21 -1
- package/dist/cjs/query/live/collection-config-builder.cjs +501 -5
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.d.cts +7 -0
- package/dist/cjs/query/live/types.d.cts +3 -3
- package/dist/cjs/query/live/utils.cjs +43 -3
- package/dist/cjs/query/live/utils.cjs.map +1 -1
- package/dist/cjs/query/live/utils.d.cts +1 -0
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.d.cts +9 -6
- package/dist/cjs/query/query-once.cjs.map +1 -1
- package/dist/cjs/query/query-once.d.cts +7 -5
- package/dist/cjs/query/subset-dedupe.cjs +9 -3
- package/dist/cjs/query/subset-dedupe.cjs.map +1 -1
- package/dist/cjs/types.d.cts +42 -8
- package/dist/cjs/utils/array-utils.cjs +27 -0
- package/dist/cjs/utils/array-utils.cjs.map +1 -0
- package/dist/cjs/utils/array-utils.d.cts +16 -0
- package/dist/cjs/utils/comparison.cjs +11 -0
- package/dist/cjs/utils/comparison.cjs.map +1 -1
- package/dist/cjs/utils/index-optimization.cjs +4 -0
- package/dist/cjs/utils/index-optimization.cjs.map +1 -1
- package/dist/cjs/utils.cjs +7 -9
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +6 -1
- package/dist/cjs/virtual-props.cjs +33 -0
- package/dist/cjs/virtual-props.cjs.map +1 -0
- package/dist/cjs/virtual-props.d.cts +196 -0
- package/dist/esm/collection/change-events.d.ts +3 -2
- package/dist/esm/collection/change-events.js.map +1 -1
- package/dist/esm/collection/changes.d.ts +10 -1
- package/dist/esm/collection/changes.js +13 -4
- package/dist/esm/collection/changes.js.map +1 -1
- package/dist/esm/collection/cleanup-queue.d.ts +30 -0
- package/dist/esm/collection/cleanup-queue.js +89 -0
- package/dist/esm/collection/cleanup-queue.js.map +1 -0
- package/dist/esm/collection/events.d.ts +39 -1
- package/dist/esm/collection/events.js +14 -0
- package/dist/esm/collection/events.js.map +1 -1
- package/dist/esm/collection/index.d.ts +49 -36
- package/dist/esm/collection/index.js +67 -29
- package/dist/esm/collection/index.js.map +1 -1
- package/dist/esm/collection/indexes.d.ts +27 -17
- package/dist/esm/collection/indexes.js +211 -62
- package/dist/esm/collection/indexes.js.map +1 -1
- package/dist/esm/collection/lifecycle.d.ts +0 -1
- package/dist/esm/collection/lifecycle.js +5 -22
- package/dist/esm/collection/lifecycle.js.map +1 -1
- package/dist/esm/collection/mutations.d.ts +1 -0
- package/dist/esm/collection/mutations.js +18 -0
- package/dist/esm/collection/mutations.js.map +1 -1
- package/dist/esm/collection/state.d.ts +65 -1
- package/dist/esm/collection/state.js +381 -53
- package/dist/esm/collection/state.js.map +1 -1
- package/dist/esm/collection/subscription.d.ts +4 -0
- package/dist/esm/collection/subscription.js +6 -0
- package/dist/esm/collection/subscription.js.map +1 -1
- package/dist/esm/collection/sync.d.ts +2 -0
- package/dist/esm/collection/sync.js +108 -1
- package/dist/esm/collection/sync.js.map +1 -1
- package/dist/esm/collection/transaction-metadata.d.ts +1 -0
- package/dist/esm/collection/transaction-metadata.js +5 -0
- package/dist/esm/collection/transaction-metadata.js.map +1 -0
- package/dist/esm/errors.d.ts +3 -0
- package/dist/esm/errors.js +8 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +11 -3
- package/dist/esm/index.js +25 -7
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/indexes/auto-index.js +13 -6
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/esm/indexes/base-index.d.ts +2 -6
- package/dist/esm/indexes/base-index.js +1 -4
- package/dist/esm/indexes/base-index.js.map +1 -1
- package/dist/esm/indexes/basic-index.d.ts +102 -0
- package/dist/esm/indexes/basic-index.js +361 -0
- package/dist/esm/indexes/basic-index.js.map +1 -0
- package/dist/esm/indexes/btree-index.d.ts +1 -1
- package/dist/esm/indexes/btree-index.js.map +1 -1
- package/dist/esm/indexes/index-options.d.ts +8 -9
- package/dist/esm/indexes/index-registry.d.ts +61 -0
- package/dist/esm/indexes/index-registry.js +89 -0
- package/dist/esm/indexes/index-registry.js.map +1 -0
- package/dist/esm/local-only.js +5 -0
- package/dist/esm/local-only.js.map +1 -1
- package/dist/esm/query/builder/functions.d.ts +25 -3
- package/dist/esm/query/builder/functions.js +27 -11
- package/dist/esm/query/builder/functions.js.map +1 -1
- package/dist/esm/query/builder/index.d.ts +4 -3
- package/dist/esm/query/builder/index.js +201 -40
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/ref-proxy.d.ts +14 -3
- package/dist/esm/query/builder/ref-proxy.js.map +1 -1
- package/dist/esm/query/builder/types.d.ts +84 -19
- package/dist/esm/query/compiler/evaluators.js +51 -0
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/group-by.d.ts +4 -2
- package/dist/esm/query/compiler/group-by.js +101 -29
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/compiler/index.d.ts +30 -2
- package/dist/esm/query/compiler/index.js +285 -13
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +1 -1
- package/dist/esm/query/compiler/order-by.js +30 -11
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/compiler/select.js +8 -0
- package/dist/esm/query/compiler/select.js.map +1 -1
- package/dist/esm/query/index.d.ts +2 -1
- package/dist/esm/query/ir.d.ts +21 -1
- package/dist/esm/query/ir.js +18 -1
- package/dist/esm/query/ir.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.d.ts +7 -0
- package/dist/esm/query/live/collection-config-builder.js +503 -7
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/types.d.ts +3 -3
- package/dist/esm/query/live/utils.d.ts +1 -0
- package/dist/esm/query/live/utils.js +43 -3
- package/dist/esm/query/live/utils.js.map +1 -1
- package/dist/esm/query/live-query-collection.d.ts +9 -6
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/query/query-once.d.ts +7 -5
- package/dist/esm/query/query-once.js.map +1 -1
- package/dist/esm/query/subset-dedupe.js +9 -3
- package/dist/esm/query/subset-dedupe.js.map +1 -1
- package/dist/esm/types.d.ts +42 -8
- package/dist/esm/utils/array-utils.d.ts +16 -0
- package/dist/esm/utils/array-utils.js +27 -0
- package/dist/esm/utils/array-utils.js.map +1 -0
- package/dist/esm/utils/comparison.js +11 -0
- package/dist/esm/utils/comparison.js.map +1 -1
- package/dist/esm/utils/index-optimization.js +4 -0
- package/dist/esm/utils/index-optimization.js.map +1 -1
- package/dist/esm/utils.d.ts +6 -1
- package/dist/esm/utils.js +7 -9
- package/dist/esm/utils.js.map +1 -1
- package/dist/esm/virtual-props.d.ts +196 -0
- package/dist/esm/virtual-props.js +33 -0
- package/dist/esm/virtual-props.js.map +1 -0
- package/package.json +2 -2
- package/skills/db-core/collection-setup/references/electric-adapter.md +1 -1
- package/src/collection/change-events.ts +13 -9
- package/src/collection/changes.ts +30 -7
- package/src/collection/cleanup-queue.ts +105 -0
- package/src/collection/events.ts +65 -0
- package/src/collection/index.ts +110 -45
- package/src/collection/indexes.ts +283 -76
- package/src/collection/lifecycle.ts +5 -26
- package/src/collection/mutations.ts +21 -0
- package/src/collection/state.ts +545 -71
- package/src/collection/subscription.ts +7 -0
- package/src/collection/sync.ts +137 -0
- package/src/collection/transaction-metadata.ts +1 -0
- package/src/errors.ts +9 -0
- package/src/index.ts +46 -3
- package/src/indexes/auto-index.ts +18 -8
- package/src/indexes/base-index.ts +2 -10
- package/src/indexes/basic-index.ts +507 -0
- package/src/indexes/btree-index.ts +1 -1
- package/src/indexes/index-options.ts +17 -37
- package/src/indexes/index-registry.ts +174 -0
- package/src/local-only.ts +7 -0
- package/src/query/builder/functions.ts +84 -7
- package/src/query/builder/index.ts +329 -9
- package/src/query/builder/ref-proxy.ts +22 -4
- package/src/query/builder/types.ts +257 -62
- package/src/query/compiler/evaluators.ts +57 -0
- package/src/query/compiler/group-by.ts +156 -35
- package/src/query/compiler/index.ts +445 -15
- package/src/query/compiler/order-by.ts +51 -12
- package/src/query/compiler/select.ts +9 -0
- package/src/query/index.ts +7 -0
- package/src/query/ir.ts +23 -2
- package/src/query/live/collection-config-builder.ts +809 -9
- package/src/query/live/types.ts +10 -4
- package/src/query/live/utils.ts +64 -3
- package/src/query/live-query-collection.ts +43 -18
- package/src/query/query-once.ts +31 -12
- package/src/query/subset-dedupe.ts +11 -7
- package/src/types.ts +49 -9
- package/src/utils/array-utils.ts +49 -0
- package/src/utils/comparison.ts +14 -0
- package/src/utils/index-optimization.ts +4 -0
- package/src/utils.ts +12 -9
- package/src/virtual-props.ts +282 -0
- package/dist/cjs/indexes/lazy-index.cjs +0 -190
- package/dist/cjs/indexes/lazy-index.cjs.map +0 -1
- package/dist/cjs/indexes/lazy-index.d.cts +0 -96
- package/dist/esm/indexes/lazy-index.d.ts +0 -96
- package/dist/esm/indexes/lazy-index.js +0 -190
- package/dist/esm/indexes/lazy-index.js.map +0 -1
- package/src/indexes/lazy-index.ts +0 -251
|
@@ -17,7 +17,6 @@ export declare class CollectionLifecycleManager<TOutput extends object = Record<
|
|
|
17
17
|
hasBeenReady: boolean;
|
|
18
18
|
hasReceivedFirstCommit: boolean;
|
|
19
19
|
onFirstReadyCallbacks: Array<() => void>;
|
|
20
|
-
gcTimeoutId: ReturnType<typeof setTimeout> | null;
|
|
21
20
|
private idleCallbackId;
|
|
22
21
|
/**
|
|
23
22
|
* Creates a new CollectionLifecycleManager instance
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { InvalidCollectionStatusTransitionError, CollectionStateError, CollectionInErrorStateError } from "../errors.js";
|
|
2
2
|
import { safeCancelIdleCallback, safeRequestIdleCallback } from "../utils/browser-polyfills.js";
|
|
3
|
+
import { CleanupQueue } from "./cleanup-queue.js";
|
|
3
4
|
class CollectionLifecycleManager {
|
|
4
5
|
/**
|
|
5
6
|
* Creates a new CollectionLifecycleManager instance
|
|
@@ -9,7 +10,6 @@ class CollectionLifecycleManager {
|
|
|
9
10
|
this.hasBeenReady = false;
|
|
10
11
|
this.hasReceivedFirstCommit = false;
|
|
11
12
|
this.onFirstReadyCallbacks = [];
|
|
12
|
-
this.gcTimeoutId = null;
|
|
13
13
|
this.idleCallbackId = null;
|
|
14
14
|
this.config = config;
|
|
15
15
|
this.id = id;
|
|
@@ -52,14 +52,6 @@ class CollectionLifecycleManager {
|
|
|
52
52
|
this.validateStatusTransition(this.status, newStatus);
|
|
53
53
|
const previousStatus = this.status;
|
|
54
54
|
this.status = newStatus;
|
|
55
|
-
if (newStatus === `ready` && !this.indexes.isIndexesResolved) {
|
|
56
|
-
this.indexes.resolveAllIndexes().catch((error) => {
|
|
57
|
-
console.warn(
|
|
58
|
-
`${this.config.id ? `[${this.config.id}] ` : ``}Failed to resolve indexes:`,
|
|
59
|
-
error
|
|
60
|
-
);
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
55
|
this.events.emitStatusChange(newStatus, previousStatus);
|
|
64
56
|
}
|
|
65
57
|
/**
|
|
@@ -104,28 +96,22 @@ class CollectionLifecycleManager {
|
|
|
104
96
|
* Called when the collection becomes inactive (no subscribers)
|
|
105
97
|
*/
|
|
106
98
|
startGCTimer() {
|
|
107
|
-
if (this.gcTimeoutId) {
|
|
108
|
-
clearTimeout(this.gcTimeoutId);
|
|
109
|
-
}
|
|
110
99
|
const gcTime = this.config.gcTime ?? 3e5;
|
|
111
100
|
if (gcTime <= 0 || !Number.isFinite(gcTime)) {
|
|
112
101
|
return;
|
|
113
102
|
}
|
|
114
|
-
this
|
|
103
|
+
CleanupQueue.getInstance().schedule(this, gcTime, () => {
|
|
115
104
|
if (this.changes.activeSubscribersCount === 0) {
|
|
116
105
|
this.scheduleIdleCleanup();
|
|
117
106
|
}
|
|
118
|
-
}
|
|
107
|
+
});
|
|
119
108
|
}
|
|
120
109
|
/**
|
|
121
110
|
* Cancel the garbage collection timer
|
|
122
111
|
* Called when the collection becomes active again
|
|
123
112
|
*/
|
|
124
113
|
cancelGCTimer() {
|
|
125
|
-
|
|
126
|
-
clearTimeout(this.gcTimeoutId);
|
|
127
|
-
this.gcTimeoutId = null;
|
|
128
|
-
}
|
|
114
|
+
CleanupQueue.getInstance().cancel(this);
|
|
129
115
|
if (this.idleCallbackId !== null) {
|
|
130
116
|
safeCancelIdleCallback(this.idleCallbackId);
|
|
131
117
|
this.idleCallbackId = null;
|
|
@@ -164,10 +150,7 @@ class CollectionLifecycleManager {
|
|
|
164
150
|
this.state.cleanup();
|
|
165
151
|
this.changes.cleanup();
|
|
166
152
|
this.indexes.cleanup();
|
|
167
|
-
|
|
168
|
-
clearTimeout(this.gcTimeoutId);
|
|
169
|
-
this.gcTimeoutId = null;
|
|
170
|
-
}
|
|
153
|
+
CleanupQueue.getInstance().cancel(this);
|
|
171
154
|
this.hasBeenReady = false;
|
|
172
155
|
const callbacks = [...this.onFirstReadyCallbacks];
|
|
173
156
|
this.onFirstReadyCallbacks = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle.js","sources":["../../../src/collection/lifecycle.ts"],"sourcesContent":["import {\n CollectionInErrorStateError,\n CollectionStateError,\n InvalidCollectionStatusTransitionError,\n} from '../errors'\nimport {\n safeCancelIdleCallback,\n safeRequestIdleCallback,\n} from '../utils/browser-polyfills'\nimport type { IdleCallbackDeadline } from '../utils/browser-polyfills'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { CollectionConfig, CollectionStatus } from '../types'\nimport type { CollectionEventsManager } from './events'\nimport type { CollectionIndexesManager } from './indexes'\nimport type { CollectionChangesManager } from './changes'\nimport type { CollectionSyncManager } from './sync'\nimport type { CollectionStateManager } from './state'\n\nexport class CollectionLifecycleManager<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n private config: CollectionConfig<TOutput, TKey, TSchema>\n private id: string\n private indexes!: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n private events!: CollectionEventsManager\n private changes!: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n private sync!: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n private state!: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n\n public status: CollectionStatus = `idle`\n public hasBeenReady = false\n public hasReceivedFirstCommit = false\n public onFirstReadyCallbacks: Array<() => void> = []\n public gcTimeoutId: ReturnType<typeof setTimeout> | null = null\n private idleCallbackId: number | null = null\n\n /**\n * Creates a new CollectionLifecycleManager instance\n */\n constructor(config: CollectionConfig<TOutput, TKey, TSchema>, id: string) {\n this.config = config\n this.id = id\n }\n\n setDeps(deps: {\n indexes: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n events: CollectionEventsManager\n changes: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n sync: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n state: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n }) {\n this.indexes = deps.indexes\n this.events = deps.events\n this.changes = deps.changes\n this.sync = deps.sync\n this.state = deps.state\n }\n\n /**\n * Validates state transitions to prevent invalid status changes\n */\n public validateStatusTransition(\n from: CollectionStatus,\n to: CollectionStatus,\n ): void {\n if (from === to) {\n // Allow same state transitions\n return\n }\n const validTransitions: Record<\n CollectionStatus,\n Array<CollectionStatus>\n > = {\n idle: [`loading`, `error`, `cleaned-up`],\n loading: [`ready`, `error`, `cleaned-up`],\n ready: [`cleaned-up`, `error`],\n error: [`cleaned-up`, `idle`],\n 'cleaned-up': [`loading`, `error`],\n }\n\n if (!validTransitions[from].includes(to)) {\n throw new InvalidCollectionStatusTransitionError(from, to, this.id)\n }\n }\n\n /**\n * Safely update the collection status with validation\n * @private\n */\n public setStatus(\n newStatus: CollectionStatus,\n allowReady: boolean = false,\n ): void {\n if (newStatus === `ready` && !allowReady) {\n // setStatus('ready') is an internal method that should not be called directly\n // Instead, use markReady to transition to ready triggering the necessary events\n // and side effects.\n throw new CollectionStateError(\n `You can't directly call \"setStatus('ready'). You must use markReady instead.`,\n )\n }\n this.validateStatusTransition(this.status, newStatus)\n const previousStatus = this.status\n this.status = newStatus\n\n // Resolve indexes when collection becomes ready\n if (newStatus === `ready` && !this.indexes.isIndexesResolved) {\n // Resolve indexes asynchronously without blocking\n this.indexes.resolveAllIndexes().catch((error) => {\n console.warn(\n `${this.config.id ? `[${this.config.id}] ` : ``}Failed to resolve indexes:`,\n error,\n )\n })\n }\n\n // Emit event\n this.events.emitStatusChange(newStatus, previousStatus)\n }\n\n /**\n * Validates that the collection is in a usable state for data operations\n * @private\n */\n public validateCollectionUsable(operation: string): void {\n switch (this.status) {\n case `error`:\n throw new CollectionInErrorStateError(operation, this.id)\n case `cleaned-up`:\n // Automatically restart the collection when operations are called on cleaned-up collections\n this.sync.startSync()\n break\n }\n }\n\n /**\n * Mark the collection as ready for use\n * This is called by sync implementations to explicitly signal that the collection is ready,\n * providing a more intuitive alternative to using commits for readiness signaling\n * @private - Should only be called by sync implementations\n */\n public markReady(): void {\n this.validateStatusTransition(this.status, `ready`)\n // Can transition to ready from loading state\n if (this.status === `loading`) {\n this.setStatus(`ready`, true)\n\n // Call any registered first ready callbacks (only on first time becoming ready)\n if (!this.hasBeenReady) {\n this.hasBeenReady = true\n\n // Also mark as having received first commit for backwards compatibility\n if (!this.hasReceivedFirstCommit) {\n this.hasReceivedFirstCommit = true\n }\n\n const callbacks = [...this.onFirstReadyCallbacks]\n this.onFirstReadyCallbacks = []\n callbacks.forEach((callback) => callback())\n }\n // Notify dependents when markReady is called, after status is set\n // This ensures live queries get notified when their dependencies become ready\n if (this.changes.changeSubscriptions.size > 0) {\n this.changes.emitEmptyReadyEvent()\n }\n }\n }\n\n /**\n * Start the garbage collection timer\n * Called when the collection becomes inactive (no subscribers)\n */\n public startGCTimer(): void {\n if (this.gcTimeoutId) {\n clearTimeout(this.gcTimeoutId)\n }\n\n const gcTime = this.config.gcTime ?? 300000 // 5 minutes default\n\n // If gcTime is 0, negative, or non-finite (Infinity, -Infinity, NaN), GC is disabled.\n // Note: setTimeout with Infinity coerces to 0 via ToInt32, causing immediate GC,\n // so we must explicitly check for non-finite values here.\n if (gcTime <= 0 || !Number.isFinite(gcTime)) {\n return\n }\n\n this.gcTimeoutId = setTimeout(() => {\n if (this.changes.activeSubscribersCount === 0) {\n // Schedule cleanup during idle time to avoid blocking the UI thread\n this.scheduleIdleCleanup()\n }\n }, gcTime)\n }\n\n /**\n * Cancel the garbage collection timer\n * Called when the collection becomes active again\n */\n public cancelGCTimer(): void {\n if (this.gcTimeoutId) {\n clearTimeout(this.gcTimeoutId)\n this.gcTimeoutId = null\n }\n // Also cancel any pending idle cleanup\n if (this.idleCallbackId !== null) {\n safeCancelIdleCallback(this.idleCallbackId)\n this.idleCallbackId = null\n }\n }\n\n /**\n * Schedule cleanup to run during browser idle time\n * This prevents blocking the UI thread during cleanup operations\n */\n private scheduleIdleCleanup(): void {\n // Cancel any existing idle callback\n if (this.idleCallbackId !== null) {\n safeCancelIdleCallback(this.idleCallbackId)\n }\n\n // Schedule cleanup with a timeout of 1 second\n // This ensures cleanup happens even if the browser is busy\n this.idleCallbackId = safeRequestIdleCallback(\n (deadline) => {\n // Perform cleanup if we still have no subscribers\n if (this.changes.activeSubscribersCount === 0) {\n const cleanupCompleted = this.performCleanup(deadline)\n // Only clear the callback ID if cleanup actually completed\n if (cleanupCompleted) {\n this.idleCallbackId = null\n }\n } else {\n // No need to cleanup, clear the callback ID\n this.idleCallbackId = null\n }\n },\n { timeout: 1000 },\n )\n }\n\n /**\n * Perform cleanup operations, optionally in chunks during idle time\n * @returns true if cleanup was completed, false if it was rescheduled\n */\n private performCleanup(deadline?: IdleCallbackDeadline): boolean {\n // If we have a deadline, we can potentially split cleanup into chunks\n // For now, we'll do all cleanup at once but check if we have time\n const hasTime =\n !deadline || deadline.timeRemaining() > 0 || deadline.didTimeout\n\n if (hasTime) {\n // Perform all cleanup operations except events\n this.sync.cleanup()\n this.state.cleanup()\n this.changes.cleanup()\n this.indexes.cleanup()\n\n if (this.gcTimeoutId) {\n clearTimeout(this.gcTimeoutId)\n this.gcTimeoutId = null\n }\n\n this.hasBeenReady = false\n\n // Call any pending onFirstReady callbacks before clearing them.\n // This ensures preload() promises resolve during cleanup instead of hanging.\n const callbacks = [...this.onFirstReadyCallbacks]\n this.onFirstReadyCallbacks = []\n callbacks.forEach((callback) => {\n try {\n callback()\n } catch (error) {\n console.error(\n `${this.config.id ? `[${this.config.id}] ` : ``}Error in onFirstReady callback during cleanup:`,\n error,\n )\n }\n })\n\n // Set status to cleaned-up after everything is cleaned up\n // This fires the status:change event to notify listeners\n this.setStatus(`cleaned-up`)\n\n // Finally, cleanup event handlers after the event has been fired\n this.events.cleanup()\n\n return true\n } else {\n // If we don't have time, reschedule for the next idle period\n this.scheduleIdleCleanup()\n return false\n }\n }\n\n /**\n * Register a callback to be executed when the collection first becomes ready\n * Useful for preloading collections\n * @param callback Function to call when the collection first becomes ready\n */\n public onFirstReady(callback: () => void): void {\n // If already ready, call immediately\n if (this.hasBeenReady) {\n callback()\n return\n }\n\n this.onFirstReadyCallbacks.push(callback)\n }\n\n public cleanup(): void {\n // Cancel any pending idle cleanup\n if (this.idleCallbackId !== null) {\n safeCancelIdleCallback(this.idleCallbackId)\n this.idleCallbackId = null\n }\n\n // Perform cleanup immediately (used when explicitly called)\n this.performCleanup()\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecycle.js","sources":["../../../src/collection/lifecycle.ts"],"sourcesContent":["import {\n CollectionInErrorStateError,\n CollectionStateError,\n InvalidCollectionStatusTransitionError,\n} from '../errors'\nimport {\n safeCancelIdleCallback,\n safeRequestIdleCallback,\n} from '../utils/browser-polyfills'\nimport { CleanupQueue } from './cleanup-queue'\nimport type { IdleCallbackDeadline } from '../utils/browser-polyfills'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { CollectionConfig, CollectionStatus } from '../types'\nimport type { CollectionEventsManager } from './events'\nimport type { CollectionIndexesManager } from './indexes'\nimport type { CollectionChangesManager } from './changes'\nimport type { CollectionSyncManager } from './sync'\nimport type { CollectionStateManager } from './state'\n\nexport class CollectionLifecycleManager<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n private config: CollectionConfig<TOutput, TKey, TSchema>\n private id: string\n private indexes!: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n private events!: CollectionEventsManager\n private changes!: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n private sync!: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n private state!: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n\n public status: CollectionStatus = `idle`\n public hasBeenReady = false\n public hasReceivedFirstCommit = false\n public onFirstReadyCallbacks: Array<() => void> = []\n private idleCallbackId: number | null = null\n\n /**\n * Creates a new CollectionLifecycleManager instance\n */\n constructor(config: CollectionConfig<TOutput, TKey, TSchema>, id: string) {\n this.config = config\n this.id = id\n }\n\n setDeps(deps: {\n indexes: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n events: CollectionEventsManager\n changes: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n sync: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n state: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n }) {\n this.indexes = deps.indexes\n this.events = deps.events\n this.changes = deps.changes\n this.sync = deps.sync\n this.state = deps.state\n }\n\n /**\n * Validates state transitions to prevent invalid status changes\n */\n public validateStatusTransition(\n from: CollectionStatus,\n to: CollectionStatus,\n ): void {\n if (from === to) {\n // Allow same state transitions\n return\n }\n const validTransitions: Record<\n CollectionStatus,\n Array<CollectionStatus>\n > = {\n idle: [`loading`, `error`, `cleaned-up`],\n loading: [`ready`, `error`, `cleaned-up`],\n ready: [`cleaned-up`, `error`],\n error: [`cleaned-up`, `idle`],\n 'cleaned-up': [`loading`, `error`],\n }\n\n if (!validTransitions[from].includes(to)) {\n throw new InvalidCollectionStatusTransitionError(from, to, this.id)\n }\n }\n\n /**\n * Safely update the collection status with validation\n * @private\n */\n public setStatus(\n newStatus: CollectionStatus,\n allowReady: boolean = false,\n ): void {\n if (newStatus === `ready` && !allowReady) {\n // setStatus('ready') is an internal method that should not be called directly\n // Instead, use markReady to transition to ready triggering the necessary events\n // and side effects.\n throw new CollectionStateError(\n `You can't directly call \"setStatus('ready'). You must use markReady instead.`,\n )\n }\n this.validateStatusTransition(this.status, newStatus)\n const previousStatus = this.status\n this.status = newStatus\n\n // Emit event\n this.events.emitStatusChange(newStatus, previousStatus)\n }\n\n /**\n * Validates that the collection is in a usable state for data operations\n * @private\n */\n public validateCollectionUsable(operation: string): void {\n switch (this.status) {\n case `error`:\n throw new CollectionInErrorStateError(operation, this.id)\n case `cleaned-up`:\n // Automatically restart the collection when operations are called on cleaned-up collections\n this.sync.startSync()\n break\n }\n }\n\n /**\n * Mark the collection as ready for use\n * This is called by sync implementations to explicitly signal that the collection is ready,\n * providing a more intuitive alternative to using commits for readiness signaling\n * @private - Should only be called by sync implementations\n */\n public markReady(): void {\n this.validateStatusTransition(this.status, `ready`)\n // Can transition to ready from loading state\n if (this.status === `loading`) {\n this.setStatus(`ready`, true)\n\n // Call any registered first ready callbacks (only on first time becoming ready)\n if (!this.hasBeenReady) {\n this.hasBeenReady = true\n\n // Also mark as having received first commit for backwards compatibility\n if (!this.hasReceivedFirstCommit) {\n this.hasReceivedFirstCommit = true\n }\n\n const callbacks = [...this.onFirstReadyCallbacks]\n this.onFirstReadyCallbacks = []\n callbacks.forEach((callback) => callback())\n }\n // Notify dependents when markReady is called, after status is set\n // This ensures live queries get notified when their dependencies become ready\n if (this.changes.changeSubscriptions.size > 0) {\n this.changes.emitEmptyReadyEvent()\n }\n }\n }\n\n /**\n * Start the garbage collection timer\n * Called when the collection becomes inactive (no subscribers)\n */\n public startGCTimer(): void {\n const gcTime = this.config.gcTime ?? 300000 // 5 minutes default\n\n // If gcTime is 0, negative, or non-finite (Infinity, -Infinity, NaN), GC is disabled.\n // Note: setTimeout with Infinity coerces to 0 via ToInt32, causing immediate GC,\n // so we must explicitly check for non-finite values here.\n if (gcTime <= 0 || !Number.isFinite(gcTime)) {\n return\n }\n\n CleanupQueue.getInstance().schedule(this, gcTime, () => {\n if (this.changes.activeSubscribersCount === 0) {\n // Schedule cleanup during idle time to avoid blocking the UI thread\n this.scheduleIdleCleanup()\n }\n })\n }\n\n /**\n * Cancel the garbage collection timer\n * Called when the collection becomes active again\n */\n public cancelGCTimer(): void {\n CleanupQueue.getInstance().cancel(this)\n // Also cancel any pending idle cleanup\n if (this.idleCallbackId !== null) {\n safeCancelIdleCallback(this.idleCallbackId)\n this.idleCallbackId = null\n }\n }\n\n /**\n * Schedule cleanup to run during browser idle time\n * This prevents blocking the UI thread during cleanup operations\n */\n private scheduleIdleCleanup(): void {\n // Cancel any existing idle callback\n if (this.idleCallbackId !== null) {\n safeCancelIdleCallback(this.idleCallbackId)\n }\n\n // Schedule cleanup with a timeout of 1 second\n // This ensures cleanup happens even if the browser is busy\n this.idleCallbackId = safeRequestIdleCallback(\n (deadline) => {\n // Perform cleanup if we still have no subscribers\n if (this.changes.activeSubscribersCount === 0) {\n const cleanupCompleted = this.performCleanup(deadline)\n // Only clear the callback ID if cleanup actually completed\n if (cleanupCompleted) {\n this.idleCallbackId = null\n }\n } else {\n // No need to cleanup, clear the callback ID\n this.idleCallbackId = null\n }\n },\n { timeout: 1000 },\n )\n }\n\n /**\n * Perform cleanup operations, optionally in chunks during idle time\n * @returns true if cleanup was completed, false if it was rescheduled\n */\n private performCleanup(deadline?: IdleCallbackDeadline): boolean {\n // If we have a deadline, we can potentially split cleanup into chunks\n // For now, we'll do all cleanup at once but check if we have time\n const hasTime =\n !deadline || deadline.timeRemaining() > 0 || deadline.didTimeout\n\n if (hasTime) {\n // Perform all cleanup operations except events\n this.sync.cleanup()\n this.state.cleanup()\n this.changes.cleanup()\n this.indexes.cleanup()\n\n CleanupQueue.getInstance().cancel(this)\n\n this.hasBeenReady = false\n\n // Call any pending onFirstReady callbacks before clearing them.\n // This ensures preload() promises resolve during cleanup instead of hanging.\n const callbacks = [...this.onFirstReadyCallbacks]\n this.onFirstReadyCallbacks = []\n callbacks.forEach((callback) => {\n try {\n callback()\n } catch (error) {\n console.error(\n `${this.config.id ? `[${this.config.id}] ` : ``}Error in onFirstReady callback during cleanup:`,\n error,\n )\n }\n })\n\n // Set status to cleaned-up after everything is cleaned up\n // This fires the status:change event to notify listeners\n this.setStatus(`cleaned-up`)\n\n // Finally, cleanup event handlers after the event has been fired\n this.events.cleanup()\n\n return true\n } else {\n // If we don't have time, reschedule for the next idle period\n this.scheduleIdleCleanup()\n return false\n }\n }\n\n /**\n * Register a callback to be executed when the collection first becomes ready\n * Useful for preloading collections\n * @param callback Function to call when the collection first becomes ready\n */\n public onFirstReady(callback: () => void): void {\n // If already ready, call immediately\n if (this.hasBeenReady) {\n callback()\n return\n }\n\n this.onFirstReadyCallbacks.push(callback)\n }\n\n public cleanup(): void {\n // Cancel any pending idle cleanup\n if (this.idleCallbackId !== null) {\n safeCancelIdleCallback(this.idleCallbackId)\n this.idleCallbackId = null\n }\n\n // Perform cleanup immediately (used when explicitly called)\n this.performCleanup()\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -18,6 +18,7 @@ export declare class CollectionMutationsManager<TOutput extends object = Record<
|
|
|
18
18
|
private ensureStandardSchema;
|
|
19
19
|
validateData(data: unknown, type: `insert` | `update`, key?: TKey): TOutput | never;
|
|
20
20
|
generateGlobalKey(key: any, item: any): string;
|
|
21
|
+
private markPendingLocalOrigins;
|
|
21
22
|
/**
|
|
22
23
|
* Inserts one or more items into the collection
|
|
23
24
|
*/
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { withArrayChangeTracking, withChangeTracking } from "../proxy.js";
|
|
2
2
|
import { getActiveTransaction, createTransaction } from "../transactions.js";
|
|
3
3
|
import { MissingInsertHandlerError, DuplicateKeyError, MissingDeleteHandlerError, NoKeysPassedToDeleteError, DeleteKeyNotFoundError, InvalidSchemaError, SchemaMustBeSynchronousError, SchemaValidationError, UndefinedKeyError, InvalidKeyError, MissingUpdateArgumentError, MissingUpdateHandlerError, NoKeysPassedToUpdateError, UpdateKeyNotFoundError, KeyUpdateNotAllowedError } from "../errors.js";
|
|
4
|
+
import { DIRECT_TRANSACTION_METADATA_KEY } from "./transaction-metadata.js";
|
|
4
5
|
class CollectionMutationsManager {
|
|
5
6
|
constructor(config, id) {
|
|
6
7
|
this.insert = (data, config2) => {
|
|
@@ -54,6 +55,9 @@ class CollectionMutationsManager {
|
|
|
54
55
|
return ambientTransaction;
|
|
55
56
|
} else {
|
|
56
57
|
const directOpTransaction = createTransaction({
|
|
58
|
+
metadata: {
|
|
59
|
+
[DIRECT_TRANSACTION_METADATA_KEY]: true
|
|
60
|
+
},
|
|
57
61
|
mutationFn: async (params) => {
|
|
58
62
|
return await this.config.onInsert({
|
|
59
63
|
transaction: params.transaction,
|
|
@@ -62,6 +66,7 @@ class CollectionMutationsManager {
|
|
|
62
66
|
}
|
|
63
67
|
});
|
|
64
68
|
directOpTransaction.applyMutations(mutations);
|
|
69
|
+
this.markPendingLocalOrigins(mutations);
|
|
65
70
|
directOpTransaction.commit().catch(() => void 0);
|
|
66
71
|
state.transactions.set(directOpTransaction.id, directOpTransaction);
|
|
67
72
|
state.scheduleTransactionCleanup(directOpTransaction);
|
|
@@ -112,6 +117,9 @@ class CollectionMutationsManager {
|
|
|
112
117
|
}
|
|
113
118
|
const directOpTransaction = createTransaction({
|
|
114
119
|
autoCommit: true,
|
|
120
|
+
metadata: {
|
|
121
|
+
[DIRECT_TRANSACTION_METADATA_KEY]: true
|
|
122
|
+
},
|
|
115
123
|
mutationFn: async (params) => {
|
|
116
124
|
return this.config.onDelete({
|
|
117
125
|
transaction: params.transaction,
|
|
@@ -120,6 +128,7 @@ class CollectionMutationsManager {
|
|
|
120
128
|
}
|
|
121
129
|
});
|
|
122
130
|
directOpTransaction.applyMutations(mutations);
|
|
131
|
+
this.markPendingLocalOrigins(mutations);
|
|
123
132
|
directOpTransaction.commit().catch(() => void 0);
|
|
124
133
|
state.transactions.set(directOpTransaction.id, directOpTransaction);
|
|
125
134
|
state.scheduleTransactionCleanup(directOpTransaction);
|
|
@@ -188,6 +197,11 @@ class CollectionMutationsManager {
|
|
|
188
197
|
}
|
|
189
198
|
return `KEY::${this.id}/${key}`;
|
|
190
199
|
}
|
|
200
|
+
markPendingLocalOrigins(mutations) {
|
|
201
|
+
for (const mutation of mutations) {
|
|
202
|
+
this.state.pendingLocalOrigins.add(mutation.key);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
191
205
|
/**
|
|
192
206
|
* Updates one or more items in the collection using a callback function
|
|
193
207
|
*/
|
|
@@ -292,6 +306,9 @@ class CollectionMutationsManager {
|
|
|
292
306
|
return ambientTransaction;
|
|
293
307
|
}
|
|
294
308
|
const directOpTransaction = createTransaction({
|
|
309
|
+
metadata: {
|
|
310
|
+
[DIRECT_TRANSACTION_METADATA_KEY]: true
|
|
311
|
+
},
|
|
295
312
|
mutationFn: async (params) => {
|
|
296
313
|
return this.config.onUpdate({
|
|
297
314
|
transaction: params.transaction,
|
|
@@ -300,6 +317,7 @@ class CollectionMutationsManager {
|
|
|
300
317
|
}
|
|
301
318
|
});
|
|
302
319
|
directOpTransaction.applyMutations(mutations);
|
|
320
|
+
this.markPendingLocalOrigins(mutations);
|
|
303
321
|
directOpTransaction.commit().catch(() => void 0);
|
|
304
322
|
state.transactions.set(directOpTransaction.id, directOpTransaction);
|
|
305
323
|
state.scheduleTransactionCleanup(directOpTransaction);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mutations.js","sources":["../../../src/collection/mutations.ts"],"sourcesContent":["import { withArrayChangeTracking, withChangeTracking } from '../proxy'\nimport { createTransaction, getActiveTransaction } from '../transactions'\nimport {\n DeleteKeyNotFoundError,\n DuplicateKeyError,\n InvalidKeyError,\n InvalidSchemaError,\n KeyUpdateNotAllowedError,\n MissingDeleteHandlerError,\n MissingInsertHandlerError,\n MissingUpdateArgumentError,\n MissingUpdateHandlerError,\n NoKeysPassedToDeleteError,\n NoKeysPassedToUpdateError,\n SchemaMustBeSynchronousError,\n SchemaValidationError,\n UndefinedKeyError,\n UpdateKeyNotFoundError,\n} from '../errors'\nimport type { Collection, CollectionImpl } from './index.js'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type {\n CollectionConfig,\n InsertConfig,\n OperationConfig,\n PendingMutation,\n StandardSchema,\n Transaction as TransactionType,\n TransactionWithMutations,\n UtilsRecord,\n WritableDeep,\n} from '../types'\nimport type { CollectionLifecycleManager } from './lifecycle'\nimport type { CollectionStateManager } from './state'\n\nexport class CollectionMutationsManager<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TUtils extends UtilsRecord = {},\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n private lifecycle!: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n private state!: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n private collection!: CollectionImpl<TOutput, TKey, TUtils, TSchema, TInput>\n private config!: CollectionConfig<TOutput, TKey, TSchema>\n private id: string\n\n constructor(config: CollectionConfig<TOutput, TKey, TSchema>, id: string) {\n this.id = id\n this.config = config\n }\n\n setDeps(deps: {\n lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n state: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n collection: CollectionImpl<TOutput, TKey, TUtils, TSchema, TInput>\n }) {\n this.lifecycle = deps.lifecycle\n this.state = deps.state\n this.collection = deps.collection\n }\n\n private ensureStandardSchema(schema: unknown): StandardSchema<TOutput> {\n // If the schema already implements the standard-schema interface, return it\n if (schema && `~standard` in (schema as {})) {\n return schema as StandardSchema<TOutput>\n }\n\n throw new InvalidSchemaError()\n }\n\n public validateData(\n data: unknown,\n type: `insert` | `update`,\n key?: TKey,\n ): TOutput | never {\n if (!this.config.schema) return data as TOutput\n\n const standardSchema = this.ensureStandardSchema(this.config.schema)\n\n // For updates, we need to merge with the existing data before validation\n if (type === `update` && key) {\n // Get the existing data for this key\n const existingData = this.state.get(key)\n\n if (\n existingData &&\n data &&\n typeof data === `object` &&\n typeof existingData === `object`\n ) {\n // Merge the update with the existing data\n const mergedData = Object.assign({}, existingData, data)\n\n // Validate the merged data\n const result = standardSchema[`~standard`].validate(mergedData)\n\n // Ensure validation is synchronous\n if (result instanceof Promise) {\n throw new SchemaMustBeSynchronousError()\n }\n\n // If validation fails, throw a SchemaValidationError with the issues\n if (`issues` in result && result.issues) {\n const typedIssues = result.issues.map((issue) => ({\n message: issue.message,\n path: issue.path?.map((p) => String(p)),\n }))\n throw new SchemaValidationError(type, typedIssues)\n }\n\n // Extract only the modified keys from the validated result\n const validatedMergedData = result.value as TOutput\n const modifiedKeys = Object.keys(data)\n const extractedChanges = Object.fromEntries(\n modifiedKeys.map((k) => [k, validatedMergedData[k as keyof TOutput]]),\n ) as TOutput\n\n return extractedChanges\n }\n }\n\n // For inserts or updates without existing data, validate the data directly\n const result = standardSchema[`~standard`].validate(data)\n\n // Ensure validation is synchronous\n if (result instanceof Promise) {\n throw new SchemaMustBeSynchronousError()\n }\n\n // If validation fails, throw a SchemaValidationError with the issues\n if (`issues` in result && result.issues) {\n const typedIssues = result.issues.map((issue) => ({\n message: issue.message,\n path: issue.path?.map((p) => String(p)),\n }))\n throw new SchemaValidationError(type, typedIssues)\n }\n\n return result.value as TOutput\n }\n\n public generateGlobalKey(key: any, item: any): string {\n if (typeof key !== `string` && typeof key !== `number`) {\n // Preserve specific error for undefined keys\n if (typeof key === `undefined`) {\n throw new UndefinedKeyError(item)\n }\n throw new InvalidKeyError(key, item)\n }\n\n return `KEY::${this.id}/${key}`\n }\n\n /**\n * Inserts one or more items into the collection\n */\n insert = (data: TInput | Array<TInput>, config?: InsertConfig) => {\n this.lifecycle.validateCollectionUsable(`insert`)\n const state = this.state\n const ambientTransaction = getActiveTransaction()\n\n // If no ambient transaction exists, check for an onInsert handler early\n if (!ambientTransaction && !this.config.onInsert) {\n throw new MissingInsertHandlerError()\n }\n\n const items = Array.isArray(data) ? data : [data]\n const mutations: Array<PendingMutation<TOutput>> = []\n const keysInCurrentBatch = new Set<TKey>()\n\n // Create mutations for each item\n items.forEach((item) => {\n // Validate the data against the schema if one exists\n const validatedData = this.validateData(item, `insert`)\n\n // Check if an item with this ID already exists in the collection or in the current batch\n const key = this.config.getKey(validatedData)\n if (this.state.has(key) || keysInCurrentBatch.has(key)) {\n throw new DuplicateKeyError(key)\n }\n keysInCurrentBatch.add(key)\n const globalKey = this.generateGlobalKey(key, item)\n\n const mutation: PendingMutation<TOutput, `insert`> = {\n mutationId: crypto.randomUUID(),\n original: {},\n modified: validatedData,\n // Pick the values from validatedData based on what's passed in - this is for cases\n // where a schema has default values. The validated data has the extra default\n // values but for changes, we just want to show the data that was actually passed in.\n changes: Object.fromEntries(\n Object.keys(item).map((k) => [\n k,\n validatedData[k as keyof typeof validatedData],\n ]),\n ) as TInput,\n globalKey,\n key,\n metadata: config?.metadata as unknown,\n syncMetadata: this.config.sync.getSyncMetadata?.() || {},\n optimistic: config?.optimistic ?? true,\n type: `insert`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this.collection,\n }\n\n mutations.push(mutation)\n })\n\n // If an ambient transaction exists, use it\n if (ambientTransaction) {\n ambientTransaction.applyMutations(mutations)\n\n state.transactions.set(ambientTransaction.id, ambientTransaction)\n state.scheduleTransactionCleanup(ambientTransaction)\n state.recomputeOptimisticState(true)\n\n return ambientTransaction\n } else {\n // Create a new transaction with a mutation function that calls the onInsert handler\n const directOpTransaction = createTransaction<TOutput>({\n mutationFn: async (params) => {\n // Call the onInsert handler with the transaction and collection\n return await this.config.onInsert!({\n transaction:\n params.transaction as unknown as TransactionWithMutations<\n TOutput,\n `insert`\n >,\n collection: this.collection as unknown as Collection<TOutput, TKey>,\n })\n },\n })\n\n // Apply mutations to the new transaction\n directOpTransaction.applyMutations(mutations)\n // Errors still reject tx.isPersisted.promise; this catch only prevents global unhandled rejections\n directOpTransaction.commit().catch(() => undefined)\n\n // Add the transaction to the collection's transactions store\n state.transactions.set(directOpTransaction.id, directOpTransaction)\n state.scheduleTransactionCleanup(directOpTransaction)\n state.recomputeOptimisticState(true)\n\n return directOpTransaction\n }\n }\n\n /**\n * Updates one or more items in the collection using a callback function\n */\n update(\n keys: (TKey | unknown) | Array<TKey | unknown>,\n configOrCallback:\n | ((draft: WritableDeep<TInput>) => void)\n | ((drafts: Array<WritableDeep<TInput>>) => void)\n | OperationConfig,\n maybeCallback?:\n | ((draft: WritableDeep<TInput>) => void)\n | ((drafts: Array<WritableDeep<TInput>>) => void),\n ) {\n if (typeof keys === `undefined`) {\n throw new MissingUpdateArgumentError()\n }\n\n const state = this.state\n this.lifecycle.validateCollectionUsable(`update`)\n\n const ambientTransaction = getActiveTransaction()\n\n // If no ambient transaction exists, check for an onUpdate handler early\n if (!ambientTransaction && !this.config.onUpdate) {\n throw new MissingUpdateHandlerError()\n }\n\n const isArray = Array.isArray(keys)\n const keysArray = isArray ? keys : [keys]\n\n if (isArray && keysArray.length === 0) {\n throw new NoKeysPassedToUpdateError()\n }\n\n const callback =\n typeof configOrCallback === `function` ? configOrCallback : maybeCallback!\n const config =\n typeof configOrCallback === `function` ? {} : configOrCallback\n\n // Get the current objects or empty objects if they don't exist\n const currentObjects = keysArray.map((key) => {\n const item = this.state.get(key)\n if (!item) {\n throw new UpdateKeyNotFoundError(key)\n }\n\n return item\n }) as unknown as Array<TInput>\n\n let changesArray\n if (isArray) {\n // Use the proxy to track changes for all objects\n changesArray = withArrayChangeTracking(\n currentObjects,\n callback as (draft: Array<TInput>) => void,\n )\n } else {\n const result = withChangeTracking(\n currentObjects[0]!,\n callback as (draft: TInput) => void,\n )\n changesArray = [result]\n }\n\n // Create mutations for each object that has changes\n const mutations: Array<\n PendingMutation<\n TOutput,\n `update`,\n CollectionImpl<TOutput, TKey, TUtils, TSchema, TInput>\n >\n > = keysArray\n .map((key, index) => {\n const itemChanges = changesArray[index] // User-provided changes for this specific item\n\n // Skip items with no changes\n if (!itemChanges || Object.keys(itemChanges).length === 0) {\n return null\n }\n\n const originalItem = currentObjects[index] as unknown as TOutput\n // Validate the user-provided changes for this item\n const validatedUpdatePayload = this.validateData(\n itemChanges,\n `update`,\n key,\n )\n\n // Construct the full modified item by applying the validated update payload to the original item\n const modifiedItem = Object.assign(\n {},\n originalItem,\n validatedUpdatePayload,\n )\n\n // Check if the ID of the item is being changed\n const originalItemId = this.config.getKey(originalItem)\n const modifiedItemId = this.config.getKey(modifiedItem)\n\n if (originalItemId !== modifiedItemId) {\n throw new KeyUpdateNotAllowedError(originalItemId, modifiedItemId)\n }\n\n const globalKey = this.generateGlobalKey(modifiedItemId, modifiedItem)\n\n return {\n mutationId: crypto.randomUUID(),\n original: originalItem,\n modified: modifiedItem,\n // Pick the values from modifiedItem based on what's passed in - this is for cases\n // where a schema has default values or transforms. The modified data has the extra\n // default or transformed values but for changes, we just want to show the data that\n // was actually passed in.\n changes: Object.fromEntries(\n Object.keys(itemChanges).map((k) => [\n k,\n modifiedItem[k as keyof typeof modifiedItem],\n ]),\n ) as TInput,\n globalKey,\n key,\n metadata: config.metadata as unknown,\n syncMetadata: (state.syncedMetadata.get(key) || {}) as Record<\n string,\n unknown\n >,\n optimistic: config.optimistic ?? true,\n type: `update`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this.collection,\n }\n })\n .filter(Boolean) as Array<\n PendingMutation<\n TOutput,\n `update`,\n CollectionImpl<TOutput, TKey, TUtils, TSchema, TInput>\n >\n >\n\n // If no changes were made, return an empty transaction early\n if (mutations.length === 0) {\n const emptyTransaction = createTransaction({\n mutationFn: async () => {},\n })\n // Errors still propagate through tx.isPersisted.promise; suppress the background commit from warning\n emptyTransaction.commit().catch(() => undefined)\n // Schedule cleanup for empty transaction\n state.scheduleTransactionCleanup(emptyTransaction)\n return emptyTransaction\n }\n\n // If an ambient transaction exists, use it\n if (ambientTransaction) {\n ambientTransaction.applyMutations(mutations)\n\n state.transactions.set(ambientTransaction.id, ambientTransaction)\n state.scheduleTransactionCleanup(ambientTransaction)\n state.recomputeOptimisticState(true)\n\n return ambientTransaction\n }\n\n // No need to check for onUpdate handler here as we've already checked at the beginning\n\n // Create a new transaction with a mutation function that calls the onUpdate handler\n const directOpTransaction = createTransaction<TOutput>({\n mutationFn: async (params) => {\n // Call the onUpdate handler with the transaction and collection\n return this.config.onUpdate!({\n transaction:\n params.transaction as unknown as TransactionWithMutations<\n TOutput,\n `update`\n >,\n collection: this.collection as unknown as Collection<TOutput, TKey>,\n })\n },\n })\n\n // Apply mutations to the new transaction\n directOpTransaction.applyMutations(mutations)\n // Errors still hit tx.isPersisted.promise; avoid leaking an unhandled rejection from the fire-and-forget commit\n directOpTransaction.commit().catch(() => undefined)\n\n // Add the transaction to the collection's transactions store\n\n state.transactions.set(directOpTransaction.id, directOpTransaction)\n state.scheduleTransactionCleanup(directOpTransaction)\n state.recomputeOptimisticState(true)\n\n return directOpTransaction\n }\n\n /**\n * Deletes one or more items from the collection\n */\n delete = (\n keys: Array<TKey> | TKey,\n config?: OperationConfig,\n ): TransactionType<any> => {\n const state = this.state\n this.lifecycle.validateCollectionUsable(`delete`)\n\n const ambientTransaction = getActiveTransaction()\n\n // If no ambient transaction exists, check for an onDelete handler early\n if (!ambientTransaction && !this.config.onDelete) {\n throw new MissingDeleteHandlerError()\n }\n\n if (Array.isArray(keys) && keys.length === 0) {\n throw new NoKeysPassedToDeleteError()\n }\n\n const keysArray = Array.isArray(keys) ? keys : [keys]\n const mutations: Array<\n PendingMutation<\n TOutput,\n `delete`,\n CollectionImpl<TOutput, TKey, TUtils, TSchema, TInput>\n >\n > = []\n\n for (const key of keysArray) {\n if (!this.state.has(key)) {\n throw new DeleteKeyNotFoundError(key)\n }\n const globalKey = this.generateGlobalKey(key, this.state.get(key)!)\n const mutation: PendingMutation<\n TOutput,\n `delete`,\n CollectionImpl<TOutput, TKey, TUtils, TSchema, TInput>\n > = {\n mutationId: crypto.randomUUID(),\n original: this.state.get(key)!,\n modified: this.state.get(key)!,\n changes: this.state.get(key)!,\n globalKey,\n key,\n metadata: config?.metadata as unknown,\n syncMetadata: (state.syncedMetadata.get(key) || {}) as Record<\n string,\n unknown\n >,\n optimistic: config?.optimistic ?? true,\n type: `delete`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this.collection,\n }\n\n mutations.push(mutation)\n }\n\n // If an ambient transaction exists, use it\n if (ambientTransaction) {\n ambientTransaction.applyMutations(mutations)\n\n state.transactions.set(ambientTransaction.id, ambientTransaction)\n state.scheduleTransactionCleanup(ambientTransaction)\n state.recomputeOptimisticState(true)\n\n return ambientTransaction\n }\n\n // Create a new transaction with a mutation function that calls the onDelete handler\n const directOpTransaction = createTransaction<TOutput>({\n autoCommit: true,\n mutationFn: async (params) => {\n // Call the onDelete handler with the transaction and collection\n return this.config.onDelete!({\n transaction:\n params.transaction as unknown as TransactionWithMutations<\n TOutput,\n `delete`\n >,\n collection: this.collection as unknown as Collection<TOutput, TKey>,\n })\n },\n })\n\n // Apply mutations to the new transaction\n directOpTransaction.applyMutations(mutations)\n // Errors still reject tx.isPersisted.promise; silence the internal commit promise to prevent test noise\n directOpTransaction.commit().catch(() => undefined)\n\n state.transactions.set(directOpTransaction.id, directOpTransaction)\n state.scheduleTransactionCleanup(directOpTransaction)\n state.recomputeOptimisticState(true)\n\n return directOpTransaction\n }\n}\n"],"names":["config","result"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mutations.js","sources":["../../../src/collection/mutations.ts"],"sourcesContent":["import { withArrayChangeTracking, withChangeTracking } from '../proxy'\nimport { createTransaction, getActiveTransaction } from '../transactions'\nimport {\n DeleteKeyNotFoundError,\n DuplicateKeyError,\n InvalidKeyError,\n InvalidSchemaError,\n KeyUpdateNotAllowedError,\n MissingDeleteHandlerError,\n MissingInsertHandlerError,\n MissingUpdateArgumentError,\n MissingUpdateHandlerError,\n NoKeysPassedToDeleteError,\n NoKeysPassedToUpdateError,\n SchemaMustBeSynchronousError,\n SchemaValidationError,\n UndefinedKeyError,\n UpdateKeyNotFoundError,\n} from '../errors'\nimport { DIRECT_TRANSACTION_METADATA_KEY } from './transaction-metadata.js'\nimport type { Collection, CollectionImpl } from './index.js'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type {\n CollectionConfig,\n InsertConfig,\n OperationConfig,\n PendingMutation,\n StandardSchema,\n Transaction as TransactionType,\n TransactionWithMutations,\n UtilsRecord,\n WritableDeep,\n} from '../types'\nimport type { CollectionLifecycleManager } from './lifecycle'\nimport type { CollectionStateManager } from './state'\n\nexport class CollectionMutationsManager<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TUtils extends UtilsRecord = {},\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n private lifecycle!: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n private state!: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n private collection!: CollectionImpl<TOutput, TKey, TUtils, TSchema, TInput>\n private config!: CollectionConfig<TOutput, TKey, TSchema>\n private id: string\n\n constructor(config: CollectionConfig<TOutput, TKey, TSchema>, id: string) {\n this.id = id\n this.config = config\n }\n\n setDeps(deps: {\n lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n state: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n collection: CollectionImpl<TOutput, TKey, TUtils, TSchema, TInput>\n }) {\n this.lifecycle = deps.lifecycle\n this.state = deps.state\n this.collection = deps.collection\n }\n\n private ensureStandardSchema(schema: unknown): StandardSchema<TOutput> {\n // If the schema already implements the standard-schema interface, return it\n if (schema && `~standard` in (schema as {})) {\n return schema as StandardSchema<TOutput>\n }\n\n throw new InvalidSchemaError()\n }\n\n public validateData(\n data: unknown,\n type: `insert` | `update`,\n key?: TKey,\n ): TOutput | never {\n if (!this.config.schema) return data as TOutput\n\n const standardSchema = this.ensureStandardSchema(this.config.schema)\n\n // For updates, we need to merge with the existing data before validation\n if (type === `update` && key) {\n // Get the existing data for this key\n const existingData = this.state.get(key)\n\n if (\n existingData &&\n data &&\n typeof data === `object` &&\n typeof existingData === `object`\n ) {\n // Merge the update with the existing data\n const mergedData = Object.assign({}, existingData, data)\n\n // Validate the merged data\n const result = standardSchema[`~standard`].validate(mergedData)\n\n // Ensure validation is synchronous\n if (result instanceof Promise) {\n throw new SchemaMustBeSynchronousError()\n }\n\n // If validation fails, throw a SchemaValidationError with the issues\n if (`issues` in result && result.issues) {\n const typedIssues = result.issues.map((issue) => ({\n message: issue.message,\n path: issue.path?.map((p) => String(p)),\n }))\n throw new SchemaValidationError(type, typedIssues)\n }\n\n // Extract only the modified keys from the validated result\n const validatedMergedData = result.value as TOutput\n const modifiedKeys = Object.keys(data)\n const extractedChanges = Object.fromEntries(\n modifiedKeys.map((k) => [k, validatedMergedData[k as keyof TOutput]]),\n ) as TOutput\n\n return extractedChanges\n }\n }\n\n // For inserts or updates without existing data, validate the data directly\n const result = standardSchema[`~standard`].validate(data)\n\n // Ensure validation is synchronous\n if (result instanceof Promise) {\n throw new SchemaMustBeSynchronousError()\n }\n\n // If validation fails, throw a SchemaValidationError with the issues\n if (`issues` in result && result.issues) {\n const typedIssues = result.issues.map((issue) => ({\n message: issue.message,\n path: issue.path?.map((p) => String(p)),\n }))\n throw new SchemaValidationError(type, typedIssues)\n }\n\n return result.value as TOutput\n }\n\n public generateGlobalKey(key: any, item: any): string {\n if (typeof key !== `string` && typeof key !== `number`) {\n // Preserve specific error for undefined keys\n if (typeof key === `undefined`) {\n throw new UndefinedKeyError(item)\n }\n throw new InvalidKeyError(key, item)\n }\n\n return `KEY::${this.id}/${key}`\n }\n\n private markPendingLocalOrigins(\n mutations: Array<PendingMutation<TOutput>>,\n ): void {\n for (const mutation of mutations) {\n this.state.pendingLocalOrigins.add(mutation.key as TKey)\n }\n }\n\n /**\n * Inserts one or more items into the collection\n */\n insert = (data: TInput | Array<TInput>, config?: InsertConfig) => {\n this.lifecycle.validateCollectionUsable(`insert`)\n const state = this.state\n const ambientTransaction = getActiveTransaction()\n\n // If no ambient transaction exists, check for an onInsert handler early\n if (!ambientTransaction && !this.config.onInsert) {\n throw new MissingInsertHandlerError()\n }\n\n const items = Array.isArray(data) ? data : [data]\n const mutations: Array<PendingMutation<TOutput>> = []\n const keysInCurrentBatch = new Set<TKey>()\n\n // Create mutations for each item\n items.forEach((item) => {\n // Validate the data against the schema if one exists\n const validatedData = this.validateData(item, `insert`)\n\n // Check if an item with this ID already exists in the collection or in the current batch\n const key = this.config.getKey(validatedData)\n if (this.state.has(key) || keysInCurrentBatch.has(key)) {\n throw new DuplicateKeyError(key)\n }\n keysInCurrentBatch.add(key)\n const globalKey = this.generateGlobalKey(key, item)\n\n const mutation: PendingMutation<TOutput, `insert`> = {\n mutationId: crypto.randomUUID(),\n original: {},\n modified: validatedData,\n // Pick the values from validatedData based on what's passed in - this is for cases\n // where a schema has default values. The validated data has the extra default\n // values but for changes, we just want to show the data that was actually passed in.\n changes: Object.fromEntries(\n Object.keys(item).map((k) => [\n k,\n validatedData[k as keyof typeof validatedData],\n ]),\n ) as TInput,\n globalKey,\n key,\n metadata: config?.metadata as unknown,\n syncMetadata: this.config.sync.getSyncMetadata?.() || {},\n optimistic: config?.optimistic ?? true,\n type: `insert`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this.collection,\n }\n\n mutations.push(mutation)\n })\n\n // If an ambient transaction exists, use it\n if (ambientTransaction) {\n ambientTransaction.applyMutations(mutations)\n\n state.transactions.set(ambientTransaction.id, ambientTransaction)\n state.scheduleTransactionCleanup(ambientTransaction)\n state.recomputeOptimisticState(true)\n\n return ambientTransaction\n } else {\n // Create a new transaction with a mutation function that calls the onInsert handler\n const directOpTransaction = createTransaction<TOutput>({\n metadata: {\n [DIRECT_TRANSACTION_METADATA_KEY]: true,\n },\n mutationFn: async (params) => {\n // Call the onInsert handler with the transaction and collection\n return await this.config.onInsert!({\n transaction:\n params.transaction as unknown as TransactionWithMutations<\n TOutput,\n `insert`\n >,\n collection: this.collection as unknown as Collection<TOutput, TKey>,\n })\n },\n })\n\n // Apply mutations to the new transaction\n directOpTransaction.applyMutations(mutations)\n this.markPendingLocalOrigins(mutations)\n // Errors still reject tx.isPersisted.promise; this catch only prevents global unhandled rejections\n directOpTransaction.commit().catch(() => undefined)\n\n // Add the transaction to the collection's transactions store\n state.transactions.set(directOpTransaction.id, directOpTransaction)\n state.scheduleTransactionCleanup(directOpTransaction)\n state.recomputeOptimisticState(true)\n\n return directOpTransaction\n }\n }\n\n /**\n * Updates one or more items in the collection using a callback function\n */\n update(\n keys: (TKey | unknown) | Array<TKey | unknown>,\n configOrCallback:\n | ((draft: WritableDeep<TInput>) => void)\n | ((drafts: Array<WritableDeep<TInput>>) => void)\n | OperationConfig,\n maybeCallback?:\n | ((draft: WritableDeep<TInput>) => void)\n | ((drafts: Array<WritableDeep<TInput>>) => void),\n ) {\n if (typeof keys === `undefined`) {\n throw new MissingUpdateArgumentError()\n }\n\n const state = this.state\n this.lifecycle.validateCollectionUsable(`update`)\n\n const ambientTransaction = getActiveTransaction()\n\n // If no ambient transaction exists, check for an onUpdate handler early\n if (!ambientTransaction && !this.config.onUpdate) {\n throw new MissingUpdateHandlerError()\n }\n\n const isArray = Array.isArray(keys)\n const keysArray = isArray ? keys : [keys]\n\n if (isArray && keysArray.length === 0) {\n throw new NoKeysPassedToUpdateError()\n }\n\n const callback =\n typeof configOrCallback === `function` ? configOrCallback : maybeCallback!\n const config =\n typeof configOrCallback === `function` ? {} : configOrCallback\n\n // Get the current objects or empty objects if they don't exist\n const currentObjects = keysArray.map((key) => {\n const item = this.state.get(key)\n if (!item) {\n throw new UpdateKeyNotFoundError(key)\n }\n\n return item\n }) as unknown as Array<TInput>\n\n let changesArray\n if (isArray) {\n // Use the proxy to track changes for all objects\n changesArray = withArrayChangeTracking(\n currentObjects,\n callback as (draft: Array<TInput>) => void,\n )\n } else {\n const result = withChangeTracking(\n currentObjects[0]!,\n callback as (draft: TInput) => void,\n )\n changesArray = [result]\n }\n\n // Create mutations for each object that has changes\n const mutations: Array<\n PendingMutation<\n TOutput,\n `update`,\n CollectionImpl<TOutput, TKey, TUtils, TSchema, TInput>\n >\n > = keysArray\n .map((key, index) => {\n const itemChanges = changesArray[index] // User-provided changes for this specific item\n\n // Skip items with no changes\n if (!itemChanges || Object.keys(itemChanges).length === 0) {\n return null\n }\n\n const originalItem = currentObjects[index] as unknown as TOutput\n // Validate the user-provided changes for this item\n const validatedUpdatePayload = this.validateData(\n itemChanges,\n `update`,\n key,\n )\n\n // Construct the full modified item by applying the validated update payload to the original item\n const modifiedItem = Object.assign(\n {},\n originalItem,\n validatedUpdatePayload,\n )\n\n // Check if the ID of the item is being changed\n const originalItemId = this.config.getKey(originalItem)\n const modifiedItemId = this.config.getKey(modifiedItem)\n\n if (originalItemId !== modifiedItemId) {\n throw new KeyUpdateNotAllowedError(originalItemId, modifiedItemId)\n }\n\n const globalKey = this.generateGlobalKey(modifiedItemId, modifiedItem)\n\n return {\n mutationId: crypto.randomUUID(),\n original: originalItem,\n modified: modifiedItem,\n // Pick the values from modifiedItem based on what's passed in - this is for cases\n // where a schema has default values or transforms. The modified data has the extra\n // default or transformed values but for changes, we just want to show the data that\n // was actually passed in.\n changes: Object.fromEntries(\n Object.keys(itemChanges).map((k) => [\n k,\n modifiedItem[k as keyof typeof modifiedItem],\n ]),\n ) as TInput,\n globalKey,\n key,\n metadata: config.metadata as unknown,\n syncMetadata: (state.syncedMetadata.get(key) || {}) as Record<\n string,\n unknown\n >,\n optimistic: config.optimistic ?? true,\n type: `update`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this.collection,\n }\n })\n .filter(Boolean) as Array<\n PendingMutation<\n TOutput,\n `update`,\n CollectionImpl<TOutput, TKey, TUtils, TSchema, TInput>\n >\n >\n\n // If no changes were made, return an empty transaction early\n if (mutations.length === 0) {\n const emptyTransaction = createTransaction({\n mutationFn: async () => {},\n })\n // Errors still propagate through tx.isPersisted.promise; suppress the background commit from warning\n emptyTransaction.commit().catch(() => undefined)\n // Schedule cleanup for empty transaction\n state.scheduleTransactionCleanup(emptyTransaction)\n return emptyTransaction\n }\n\n // If an ambient transaction exists, use it\n if (ambientTransaction) {\n ambientTransaction.applyMutations(mutations)\n\n state.transactions.set(ambientTransaction.id, ambientTransaction)\n state.scheduleTransactionCleanup(ambientTransaction)\n state.recomputeOptimisticState(true)\n\n return ambientTransaction\n }\n\n // No need to check for onUpdate handler here as we've already checked at the beginning\n\n // Create a new transaction with a mutation function that calls the onUpdate handler\n const directOpTransaction = createTransaction<TOutput>({\n metadata: {\n [DIRECT_TRANSACTION_METADATA_KEY]: true,\n },\n mutationFn: async (params) => {\n // Call the onUpdate handler with the transaction and collection\n return this.config.onUpdate!({\n transaction:\n params.transaction as unknown as TransactionWithMutations<\n TOutput,\n `update`\n >,\n collection: this.collection as unknown as Collection<TOutput, TKey>,\n })\n },\n })\n\n // Apply mutations to the new transaction\n directOpTransaction.applyMutations(mutations)\n this.markPendingLocalOrigins(mutations)\n // Errors still hit tx.isPersisted.promise; avoid leaking an unhandled rejection from the fire-and-forget commit\n directOpTransaction.commit().catch(() => undefined)\n\n // Add the transaction to the collection's transactions store\n\n state.transactions.set(directOpTransaction.id, directOpTransaction)\n state.scheduleTransactionCleanup(directOpTransaction)\n state.recomputeOptimisticState(true)\n\n return directOpTransaction\n }\n\n /**\n * Deletes one or more items from the collection\n */\n delete = (\n keys: Array<TKey> | TKey,\n config?: OperationConfig,\n ): TransactionType<any> => {\n const state = this.state\n this.lifecycle.validateCollectionUsable(`delete`)\n\n const ambientTransaction = getActiveTransaction()\n\n // If no ambient transaction exists, check for an onDelete handler early\n if (!ambientTransaction && !this.config.onDelete) {\n throw new MissingDeleteHandlerError()\n }\n\n if (Array.isArray(keys) && keys.length === 0) {\n throw new NoKeysPassedToDeleteError()\n }\n\n const keysArray = Array.isArray(keys) ? keys : [keys]\n const mutations: Array<\n PendingMutation<\n TOutput,\n `delete`,\n CollectionImpl<TOutput, TKey, TUtils, TSchema, TInput>\n >\n > = []\n\n for (const key of keysArray) {\n if (!this.state.has(key)) {\n throw new DeleteKeyNotFoundError(key)\n }\n const globalKey = this.generateGlobalKey(key, this.state.get(key)!)\n const mutation: PendingMutation<\n TOutput,\n `delete`,\n CollectionImpl<TOutput, TKey, TUtils, TSchema, TInput>\n > = {\n mutationId: crypto.randomUUID(),\n original: this.state.get(key)!,\n modified: this.state.get(key)!,\n changes: this.state.get(key)!,\n globalKey,\n key,\n metadata: config?.metadata as unknown,\n syncMetadata: (state.syncedMetadata.get(key) || {}) as Record<\n string,\n unknown\n >,\n optimistic: config?.optimistic ?? true,\n type: `delete`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this.collection,\n }\n\n mutations.push(mutation)\n }\n\n // If an ambient transaction exists, use it\n if (ambientTransaction) {\n ambientTransaction.applyMutations(mutations)\n\n state.transactions.set(ambientTransaction.id, ambientTransaction)\n state.scheduleTransactionCleanup(ambientTransaction)\n state.recomputeOptimisticState(true)\n\n return ambientTransaction\n }\n\n // Create a new transaction with a mutation function that calls the onDelete handler\n const directOpTransaction = createTransaction<TOutput>({\n autoCommit: true,\n metadata: {\n [DIRECT_TRANSACTION_METADATA_KEY]: true,\n },\n mutationFn: async (params) => {\n // Call the onDelete handler with the transaction and collection\n return this.config.onDelete!({\n transaction:\n params.transaction as unknown as TransactionWithMutations<\n TOutput,\n `delete`\n >,\n collection: this.collection as unknown as Collection<TOutput, TKey>,\n })\n },\n })\n\n // Apply mutations to the new transaction\n directOpTransaction.applyMutations(mutations)\n this.markPendingLocalOrigins(mutations)\n // Errors still reject tx.isPersisted.promise; silence the internal commit promise to prevent test noise\n directOpTransaction.commit().catch(() => undefined)\n\n state.transactions.set(directOpTransaction.id, directOpTransaction)\n state.scheduleTransactionCleanup(directOpTransaction)\n state.recomputeOptimisticState(true)\n\n return directOpTransaction\n }\n}\n"],"names":["config","result"],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { SortedMap } from '../SortedMap.js';
|
|
2
|
+
import { VirtualOrigin, WithVirtualProps } from '../virtual-props.js';
|
|
2
3
|
import { Transaction } from '../transactions.js';
|
|
3
4
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
4
|
-
import { CollectionConfig, OptimisticChangeMessage } from '../types.js';
|
|
5
|
+
import { ChangeMessage, CollectionConfig, OptimisticChangeMessage } from '../types.js';
|
|
5
6
|
import { CollectionImpl } from './index.js';
|
|
6
7
|
import { CollectionLifecycleManager } from './lifecycle.js';
|
|
7
8
|
import { CollectionChangesManager } from './changes.js';
|
|
@@ -12,6 +13,8 @@ interface PendingSyncedTransaction<T extends object = Record<string, unknown>, T
|
|
|
12
13
|
operations: Array<OptimisticChangeMessage<T>>;
|
|
13
14
|
truncate?: boolean;
|
|
14
15
|
deletedKeys: Set<string | number>;
|
|
16
|
+
rowMetadataWrites: Map<TKey, PendingMetadataWrite>;
|
|
17
|
+
collectionMetadataWrites: Map<string, PendingMetadataWrite>;
|
|
15
18
|
optimisticSnapshot?: {
|
|
16
19
|
upserts: Map<TKey, T>;
|
|
17
20
|
deletes: Set<TKey>;
|
|
@@ -23,6 +26,12 @@ interface PendingSyncedTransaction<T extends object = Record<string, unknown>, T
|
|
|
23
26
|
*/
|
|
24
27
|
immediate?: boolean;
|
|
25
28
|
}
|
|
29
|
+
type PendingMetadataWrite = {
|
|
30
|
+
type: `set`;
|
|
31
|
+
value: unknown;
|
|
32
|
+
} | {
|
|
33
|
+
type: `delete`;
|
|
34
|
+
};
|
|
26
35
|
export declare class CollectionStateManager<TOutput extends object = Record<string, unknown>, TKey extends string | number = string | number, TSchema extends StandardSchemaV1 = StandardSchemaV1, TInput extends object = TOutput> {
|
|
27
36
|
config: CollectionConfig<TOutput, TKey, TSchema>;
|
|
28
37
|
collection: CollectionImpl<TOutput, TKey, any, TSchema, TInput>;
|
|
@@ -34,14 +43,38 @@ export declare class CollectionStateManager<TOutput extends object = Record<stri
|
|
|
34
43
|
pendingSyncedTransactions: Array<PendingSyncedTransaction<TOutput, TKey>>;
|
|
35
44
|
syncedData: SortedMap<TKey, TOutput>;
|
|
36
45
|
syncedMetadata: Map<TKey, unknown>;
|
|
46
|
+
syncedCollectionMetadata: Map<string, unknown>;
|
|
37
47
|
optimisticUpserts: Map<TKey, TOutput>;
|
|
38
48
|
optimisticDeletes: Set<TKey>;
|
|
49
|
+
pendingOptimisticUpserts: Map<TKey, TOutput>;
|
|
50
|
+
pendingOptimisticDeletes: Set<TKey>;
|
|
51
|
+
pendingOptimisticDirectUpserts: Set<TKey>;
|
|
52
|
+
pendingOptimisticDirectDeletes: Set<TKey>;
|
|
53
|
+
/**
|
|
54
|
+
* Tracks the origin of confirmed changes for each row.
|
|
55
|
+
* 'local' = change originated from this client
|
|
56
|
+
* 'remote' = change was received via sync
|
|
57
|
+
*
|
|
58
|
+
* This is used for the $origin virtual property.
|
|
59
|
+
* Note: This only tracks *confirmed* changes, not optimistic ones.
|
|
60
|
+
* Optimistic changes are always considered 'local' for $origin.
|
|
61
|
+
*/
|
|
62
|
+
rowOrigins: Map<TKey, VirtualOrigin>;
|
|
63
|
+
/**
|
|
64
|
+
* Tracks keys that have pending local changes.
|
|
65
|
+
* Used to determine whether sync-confirmed data should have 'local' or 'remote' origin.
|
|
66
|
+
* When sync confirms data for a key with pending local changes, it keeps 'local' origin.
|
|
67
|
+
*/
|
|
68
|
+
pendingLocalChanges: Set<TKey>;
|
|
69
|
+
pendingLocalOrigins: Set<TKey>;
|
|
70
|
+
private virtualPropsCache;
|
|
39
71
|
size: number;
|
|
40
72
|
syncedKeys: Set<TKey>;
|
|
41
73
|
preSyncVisibleState: Map<TKey, TOutput>;
|
|
42
74
|
recentlySyncedKeys: Set<TKey>;
|
|
43
75
|
hasReceivedFirstCommit: boolean;
|
|
44
76
|
isCommittingSyncTransactions: boolean;
|
|
77
|
+
isLocalOnly: boolean;
|
|
45
78
|
/**
|
|
46
79
|
* Creates a new CollectionState manager
|
|
47
80
|
*/
|
|
@@ -53,6 +86,37 @@ export declare class CollectionStateManager<TOutput extends object = Record<stri
|
|
|
53
86
|
indexes: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>;
|
|
54
87
|
events: CollectionEventsManager;
|
|
55
88
|
}): void;
|
|
89
|
+
/**
|
|
90
|
+
* Checks if a row has pending optimistic mutations (not yet confirmed by sync).
|
|
91
|
+
* Used to compute the $synced virtual property.
|
|
92
|
+
*/
|
|
93
|
+
isRowSynced(key: TKey): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Gets the origin of the last confirmed change to a row.
|
|
96
|
+
* Returns 'local' if the row has optimistic mutations (optimistic changes are local).
|
|
97
|
+
* Used to compute the $origin virtual property.
|
|
98
|
+
*/
|
|
99
|
+
getRowOrigin(key: TKey): VirtualOrigin;
|
|
100
|
+
private createVirtualPropsSnapshot;
|
|
101
|
+
private getVirtualPropsSnapshotForState;
|
|
102
|
+
private enrichWithVirtualPropsSnapshot;
|
|
103
|
+
private clearOriginTrackingState;
|
|
104
|
+
/**
|
|
105
|
+
* Enriches a row with virtual properties using the "add-if-missing" pattern.
|
|
106
|
+
* If the row already has virtual properties (from an upstream collection),
|
|
107
|
+
* they are preserved. Otherwise, new values are computed.
|
|
108
|
+
*/
|
|
109
|
+
enrichWithVirtualProps(row: TOutput, key: TKey): WithVirtualProps<TOutput, TKey>;
|
|
110
|
+
/**
|
|
111
|
+
* Creates a change message with virtual properties.
|
|
112
|
+
* Uses the "add-if-missing" pattern so that pass-through from upstream
|
|
113
|
+
* collections works correctly.
|
|
114
|
+
*/
|
|
115
|
+
enrichChangeMessage(change: ChangeMessage<TOutput, TKey>): ChangeMessage<WithVirtualProps<TOutput, TKey>, TKey>;
|
|
116
|
+
/**
|
|
117
|
+
* Get the current value for a key enriched with virtual properties.
|
|
118
|
+
*/
|
|
119
|
+
getWithVirtualProps(key: TKey): WithVirtualProps<TOutput, TKey> | undefined;
|
|
56
120
|
/**
|
|
57
121
|
* Get the current value for a key (virtual derived state)
|
|
58
122
|
*/
|