@tanstack/db 0.0.6 → 0.0.7
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.cjs +15 -6
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +23 -7
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/query/compiled-query.cjs +1 -1
- package/dist/cjs/query/compiled-query.cjs.map +1 -1
- package/dist/cjs/query/compiled-query.d.cts +1 -1
- package/dist/cjs/types.d.cts +8 -0
- package/dist/esm/collection.d.ts +23 -7
- package/dist/esm/collection.js +15 -6
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/query/compiled-query.d.ts +1 -1
- package/dist/esm/query/compiled-query.js +2 -2
- package/dist/esm/query/compiled-query.js.map +1 -1
- package/dist/esm/types.d.ts +8 -0
- package/package.json +1 -1
- package/src/collection.ts +47 -15
- package/src/index.ts +1 -1
- package/src/query/compiled-query.ts +4 -3
- package/src/types.ts +10 -0
package/dist/cjs/collection.cjs
CHANGED
|
@@ -4,10 +4,18 @@ const store = require("@tanstack/store");
|
|
|
4
4
|
const proxy = require("./proxy.cjs");
|
|
5
5
|
const transactions = require("./transactions.cjs");
|
|
6
6
|
const SortedMap = require("./SortedMap.cjs");
|
|
7
|
-
const collectionsStore = new store.Store(
|
|
7
|
+
const collectionsStore = new store.Store(
|
|
8
|
+
/* @__PURE__ */ new Map()
|
|
9
|
+
);
|
|
8
10
|
const loadingCollections = /* @__PURE__ */ new Map();
|
|
9
|
-
function createCollection(
|
|
10
|
-
|
|
11
|
+
function createCollection(options) {
|
|
12
|
+
const collection = new CollectionImpl(options);
|
|
13
|
+
if (options.utils) {
|
|
14
|
+
collection.utils = { ...options.utils };
|
|
15
|
+
} else {
|
|
16
|
+
collection.utils = {};
|
|
17
|
+
}
|
|
18
|
+
return collection;
|
|
11
19
|
}
|
|
12
20
|
function preloadCollection(config) {
|
|
13
21
|
if (!config.id) {
|
|
@@ -29,7 +37,7 @@ function preloadCollection(config) {
|
|
|
29
37
|
}
|
|
30
38
|
next.set(
|
|
31
39
|
config.id,
|
|
32
|
-
|
|
40
|
+
createCollection({
|
|
33
41
|
id: config.id,
|
|
34
42
|
getId: config.getId,
|
|
35
43
|
sync: config.sync,
|
|
@@ -70,7 +78,7 @@ class SchemaValidationError extends Error {
|
|
|
70
78
|
this.issues = issues;
|
|
71
79
|
}
|
|
72
80
|
}
|
|
73
|
-
class
|
|
81
|
+
class CollectionImpl {
|
|
74
82
|
/**
|
|
75
83
|
* Creates a new Collection instance
|
|
76
84
|
*
|
|
@@ -78,6 +86,7 @@ class Collection {
|
|
|
78
86
|
* @throws Error if sync config is missing
|
|
79
87
|
*/
|
|
80
88
|
constructor(config) {
|
|
89
|
+
this.utils = {};
|
|
81
90
|
this.syncedData = new store.Store(/* @__PURE__ */ new Map());
|
|
82
91
|
this.syncedMetadata = new store.Store(/* @__PURE__ */ new Map());
|
|
83
92
|
this.pendingSyncedTransactions = [];
|
|
@@ -666,7 +675,7 @@ class Collection {
|
|
|
666
675
|
});
|
|
667
676
|
}
|
|
668
677
|
}
|
|
669
|
-
exports.
|
|
678
|
+
exports.CollectionImpl = CollectionImpl;
|
|
670
679
|
exports.SchemaValidationError = SchemaValidationError;
|
|
671
680
|
exports.collectionsStore = collectionsStore;
|
|
672
681
|
exports.createCollection = createCollection;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collection.cjs","sources":["../../src/collection.ts"],"sourcesContent":["import { Derived, Store, batch } from \"@tanstack/store\"\nimport { withArrayChangeTracking, withChangeTracking } from \"./proxy\"\nimport { Transaction, getActiveTransaction } from \"./transactions\"\nimport { SortedMap } from \"./SortedMap\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n InsertConfig,\n OperationConfig,\n OptimisticChangeMessage,\n PendingMutation,\n StandardSchema,\n Transaction as TransactionType,\n} from \"./types\"\n\n// Store collections in memory using Tanstack store\nexport const collectionsStore = new Store(new Map<string, Collection<any>>())\n\n// Map to track loading collections\n\nconst loadingCollections = new Map<\n string,\n Promise<Collection<Record<string, unknown>>>\n>()\n\ninterface PendingSyncedTransaction<T extends object = Record<string, unknown>> {\n committed: boolean\n operations: Array<OptimisticChangeMessage<T>>\n}\n\n/**\n * Creates a new Collection instance with the given configuration\n *\n * @template T - The type of items in the collection\n * @param config - Configuration for the collection, including id and sync\n * @returns A new Collection instance\n */\nexport function createCollection<T extends object = Record<string, unknown>>(\n config: CollectionConfig<T>\n): Collection<T> {\n return new Collection<T>(config)\n}\n\n/**\n * Preloads a collection with the given configuration\n * Returns a promise that resolves once the sync tool has done its first commit (initial sync is finished)\n * If the collection has already loaded, it resolves immediately\n *\n * This function is useful in route loaders or similar pre-rendering scenarios where you want\n * to ensure data is available before a route transition completes. It uses the same shared collection\n * instance that will be used by useCollection, ensuring data consistency.\n *\n * @example\n * ```typescript\n * // In a route loader\n * async function loader({ params }) {\n * await preloadCollection({\n * id: `users-${params.userId}`,\n * sync: { ... },\n * });\n *\n * return null;\n * }\n * ```\n *\n * @template T - The type of items in the collection\n * @param config - Configuration for the collection, including id and sync\n * @returns Promise that resolves when the initial sync is finished\n */\nexport function preloadCollection<T extends object = Record<string, unknown>>(\n config: CollectionConfig<T>\n): Promise<Collection<T>> {\n if (!config.id) {\n throw new Error(`The id property is required for preloadCollection`)\n }\n\n // If the collection is already fully loaded, return a resolved promise\n if (\n collectionsStore.state.has(config.id) &&\n !loadingCollections.has(config.id)\n ) {\n return Promise.resolve(\n collectionsStore.state.get(config.id)! as Collection<T>\n )\n }\n\n // If the collection is in the process of loading, return its promise\n if (loadingCollections.has(config.id)) {\n return loadingCollections.get(config.id)! as Promise<Collection<T>>\n }\n\n // Create a new collection instance if it doesn't exist\n if (!collectionsStore.state.has(config.id)) {\n collectionsStore.setState((prev) => {\n const next = new Map(prev)\n if (!config.id) {\n throw new Error(`The id property is required for preloadCollection`)\n }\n next.set(\n config.id,\n new Collection<T>({\n id: config.id,\n getId: config.getId,\n sync: config.sync,\n schema: config.schema,\n })\n )\n return next\n })\n }\n\n const collection = collectionsStore.state.get(config.id)! as Collection<T>\n\n // Create a promise that will resolve after the first commit\n let resolveFirstCommit: () => void\n const firstCommitPromise = new Promise<Collection<T>>((resolve) => {\n resolveFirstCommit = () => {\n resolve(collection)\n }\n })\n\n // Register a one-time listener for the first commit\n collection.onFirstCommit(() => {\n if (!config.id) {\n throw new Error(`The id property is required for preloadCollection`)\n }\n if (loadingCollections.has(config.id)) {\n loadingCollections.delete(config.id)\n resolveFirstCommit()\n }\n })\n\n // Store the loading promise\n loadingCollections.set(\n config.id,\n firstCommitPromise as Promise<Collection<Record<string, unknown>>>\n )\n\n return firstCommitPromise\n}\n\n/**\n * Custom error class for schema validation errors\n */\nexport class SchemaValidationError extends Error {\n type: `insert` | `update`\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>\n\n constructor(\n type: `insert` | `update`,\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>,\n message?: string\n ) {\n const defaultMessage = `${type === `insert` ? `Insert` : `Update`} validation failed: ${issues\n .map((issue) => issue.message)\n .join(`, `)}`\n\n super(message || defaultMessage)\n this.name = `SchemaValidationError`\n this.type = type\n this.issues = issues\n }\n}\n\nexport class Collection<T extends object = Record<string, unknown>> {\n public transactions: Store<SortedMap<string, TransactionType>>\n public optimisticOperations: Derived<Array<OptimisticChangeMessage<T>>>\n public derivedState: Derived<Map<string, T>>\n public derivedArray: Derived<Array<T>>\n public derivedChanges: Derived<Array<ChangeMessage<T>>>\n public syncedData = new Store<Map<string, T>>(new Map())\n public syncedMetadata = new Store(new Map<string, unknown>())\n private pendingSyncedTransactions: Array<PendingSyncedTransaction<T>> = []\n private syncedKeys = new Set<string>()\n public config: CollectionConfig<T>\n private hasReceivedFirstCommit = false\n\n // Array to store one-time commit listeners\n private onFirstCommitCallbacks: Array<() => void> = []\n\n /**\n * Register a callback to be executed on the next commit\n * Useful for preloading collections\n * @param callback Function to call after the next commit\n */\n public onFirstCommit(callback: () => void): void {\n this.onFirstCommitCallbacks.push(callback)\n }\n\n public id = ``\n\n /**\n * Creates a new Collection instance\n *\n * @param config - Configuration object for the collection\n * @throws Error if sync config is missing\n */\n constructor(config: CollectionConfig<T>) {\n // eslint-disable-next-line\n if (!config) {\n throw new Error(`Collection requires a config`)\n }\n if (config.id) {\n this.id = config.id\n } else {\n this.id = crypto.randomUUID()\n }\n\n // eslint-disable-next-line\n if (!config.sync) {\n throw new Error(`Collection requires a sync config`)\n }\n\n this.transactions = new Store(\n new SortedMap<string, TransactionType>(\n (a, b) => a.createdAt.getTime() - b.createdAt.getTime()\n )\n )\n\n // Copies of live mutations are stored here and removed once the transaction completes.\n this.optimisticOperations = new Derived({\n fn: ({ currDepVals: [transactions] }) => {\n const result = Array.from(transactions.values())\n .map((transaction) => {\n const isActive = ![`completed`, `failed`].includes(\n transaction.state\n )\n return transaction.mutations\n .filter((mutation) => mutation.collection === this)\n .map((mutation) => {\n const message: OptimisticChangeMessage<T> = {\n type: mutation.type,\n key: mutation.key,\n value: mutation.modified as T,\n isActive,\n }\n if (\n mutation.metadata !== undefined &&\n mutation.metadata !== null\n ) {\n message.metadata = mutation.metadata as Record<\n string,\n unknown\n >\n }\n return message\n })\n })\n .flat()\n\n return result\n },\n deps: [this.transactions],\n })\n this.optimisticOperations.mount()\n\n // Combine together synced data & optimistic operations.\n this.derivedState = new Derived({\n fn: ({ currDepVals: [syncedData, operations] }) => {\n const combined = new Map<string, T>(syncedData)\n\n // Apply the optimistic operations on top of the synced state.\n for (const operation of operations) {\n if (operation.isActive) {\n switch (operation.type) {\n case `insert`:\n combined.set(operation.key, operation.value)\n break\n case `update`:\n combined.set(operation.key, operation.value)\n break\n case `delete`:\n combined.delete(operation.key)\n break\n }\n }\n }\n\n return combined\n },\n deps: [this.syncedData, this.optimisticOperations],\n })\n\n // Create a derived array from the map to avoid recalculating it\n this.derivedArray = new Derived({\n fn: ({ currDepVals: [stateMap] }) => {\n // Collections returned by a query that has an orderBy are annotated\n // with the _orderByIndex field.\n // This is used to sort the array when it's derived.\n const array: Array<T & { _orderByIndex?: number }> = Array.from(\n stateMap.values()\n )\n if (array[0] && `_orderByIndex` in array[0]) {\n ;(array as Array<T & { _orderByIndex: number }>).sort((a, b) => {\n if (a._orderByIndex === b._orderByIndex) {\n return 0\n }\n return a._orderByIndex < b._orderByIndex ? -1 : 1\n })\n }\n return array\n },\n deps: [this.derivedState],\n })\n this.derivedArray.mount()\n\n this.derivedChanges = new Derived({\n fn: ({\n currDepVals: [derivedState, optimisticOperations],\n prevDepVals,\n }) => {\n const prevDerivedState = prevDepVals?.[0] ?? new Map<string, T>()\n const prevOptimisticOperations = prevDepVals?.[1] ?? []\n const changedKeys = new Set(this.syncedKeys)\n optimisticOperations\n .flat()\n .filter((op) => op.isActive)\n .forEach((op) => changedKeys.add(op.key))\n prevOptimisticOperations.flat().forEach((op) => {\n changedKeys.add(op.key)\n })\n\n if (changedKeys.size === 0) {\n return []\n }\n\n const changes: Array<ChangeMessage<T>> = []\n for (const key of changedKeys) {\n if (prevDerivedState.has(key) && !derivedState.has(key)) {\n changes.push({\n type: `delete`,\n key,\n value: prevDerivedState.get(key)!,\n })\n } else if (!prevDerivedState.has(key) && derivedState.has(key)) {\n changes.push({ type: `insert`, key, value: derivedState.get(key)! })\n } else if (prevDerivedState.has(key) && derivedState.has(key)) {\n const value = derivedState.get(key)!\n const previousValue = prevDerivedState.get(key)\n if (value !== previousValue) {\n // Comparing objects by reference as records are not mutated\n changes.push({\n type: `update`,\n key,\n value,\n previousValue,\n })\n }\n }\n }\n\n this.syncedKeys.clear()\n\n return changes\n },\n deps: [this.derivedState, this.optimisticOperations],\n })\n this.derivedChanges.mount()\n\n this.config = config\n\n this.derivedState.mount()\n\n // Start the sync process\n config.sync.sync({\n collection: this,\n begin: () => {\n this.pendingSyncedTransactions.push({\n committed: false,\n operations: [],\n })\n },\n write: (messageWithoutKey: Omit<ChangeMessage<T>, `key`>) => {\n const pendingTransaction =\n this.pendingSyncedTransactions[\n this.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new Error(`No pending sync transaction to write to`)\n }\n if (pendingTransaction.committed) {\n throw new Error(\n `The pending sync transaction is already committed, you can't still write to it.`\n )\n }\n const key = this.generateObjectKey(\n this.config.getId(messageWithoutKey.value),\n messageWithoutKey.value\n )\n\n // Check if an item with this ID already exists when inserting\n if (messageWithoutKey.type === `insert`) {\n if (\n this.syncedData.state.has(key) &&\n !pendingTransaction.operations.some(\n (op) => op.key === key && op.type === `delete`\n )\n ) {\n const id = this.config.getId(messageWithoutKey.value)\n throw new Error(\n `Cannot insert document with ID \"${id}\" from sync because it already exists in the collection \"${this.id}\"`\n )\n }\n }\n\n const message: ChangeMessage<T> = {\n ...messageWithoutKey,\n key,\n }\n pendingTransaction.operations.push(message)\n },\n commit: () => {\n const pendingTransaction =\n this.pendingSyncedTransactions[\n this.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new Error(`No pending sync transaction to commit`)\n }\n if (pendingTransaction.committed) {\n throw new Error(\n `The pending sync transaction is already committed, you can't commit it again.`\n )\n }\n\n pendingTransaction.committed = true\n\n this.commitPendingTransactions()\n },\n })\n }\n\n /**\n * Attempts to commit pending synced transactions if there are no active transactions\n * This method processes operations from pending transactions and applies them to the synced data\n */\n commitPendingTransactions = () => {\n if (\n !Array.from(this.transactions.state.values()).some(\n ({ state }) => state === `persisting`\n )\n ) {\n batch(() => {\n for (const transaction of this.pendingSyncedTransactions) {\n for (const operation of transaction.operations) {\n this.syncedKeys.add(operation.key)\n this.syncedMetadata.setState((prevData) => {\n switch (operation.type) {\n case `insert`:\n prevData.set(operation.key, operation.metadata)\n break\n case `update`:\n prevData.set(operation.key, {\n ...prevData.get(operation.key)!,\n ...operation.metadata,\n })\n break\n case `delete`:\n prevData.delete(operation.key)\n break\n }\n return prevData\n })\n this.syncedData.setState((prevData) => {\n switch (operation.type) {\n case `insert`:\n prevData.set(operation.key, operation.value)\n break\n case `update`:\n prevData.set(operation.key, {\n ...prevData.get(operation.key)!,\n ...operation.value,\n })\n break\n case `delete`:\n prevData.delete(operation.key)\n break\n }\n return prevData\n })\n }\n }\n })\n\n this.pendingSyncedTransactions = []\n\n // Call any registered one-time commit listeners\n if (!this.hasReceivedFirstCommit) {\n this.hasReceivedFirstCommit = true\n const callbacks = [...this.onFirstCommitCallbacks]\n this.onFirstCommitCallbacks = []\n callbacks.forEach((callback) => callback())\n }\n }\n }\n\n private ensureStandardSchema(schema: unknown): StandardSchema<T> {\n // If the schema already implements the standard-schema interface, return it\n if (schema && typeof schema === `object` && `~standard` in schema) {\n return schema as StandardSchema<T>\n }\n\n throw new Error(\n `Schema must either implement the standard-schema interface or be a Zod schema`\n )\n }\n\n private getKeyFromId(id: unknown): string {\n if (typeof id === `undefined`) {\n throw new Error(`id is undefined`)\n }\n if (typeof id === `string` && id.startsWith(`KEY::`)) {\n return id\n } else {\n // if it's not a string, then it's some other\n // primitive type and needs turned into a key.\n return this.generateObjectKey(id, null)\n }\n }\n\n public generateObjectKey(id: any, item: any): string {\n if (typeof id === `undefined`) {\n throw new Error(\n `An object was created without a defined id: ${JSON.stringify(item)}`\n )\n }\n\n return `KEY::${this.id}/${id}`\n }\n\n private validateData(\n data: unknown,\n type: `insert` | `update`,\n key?: string\n ): T | never {\n if (!this.config.schema) return data as T\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 = { ...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 TypeError(`Schema validation must be synchronous`)\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 the original update data, not the merged data\n // We only used the merged data for validation\n return data as T\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 TypeError(`Schema validation must be synchronous`)\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 T\n }\n\n /**\n * Inserts one or more items into the collection\n * @param items - Single item or array of items to insert\n * @param config - Optional configuration including metadata and custom keys\n * @returns A TransactionType object representing the insert operation(s)\n * @throws {SchemaValidationError} If the data fails schema validation\n * @example\n * // Insert a single item\n * insert({ text: \"Buy groceries\", completed: false })\n *\n * // Insert multiple items\n * insert([\n * { text: \"Buy groceries\", completed: false },\n * { text: \"Walk dog\", completed: false }\n * ])\n *\n * // Insert with custom key\n * insert({ text: \"Buy groceries\" }, { key: \"grocery-task\" })\n */\n insert = (data: T | Array<T>, config?: InsertConfig) => {\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 Error(\n `Collection.insert called directly (not within an explicit transaction) but no 'onInsert' handler is configured.`\n )\n }\n\n const items = Array.isArray(data) ? data : [data]\n const mutations: Array<PendingMutation<T>> = []\n\n // Handle keys - convert to array if string, or generate if not provided\n const keys: Array<unknown> = items.map((item) =>\n this.generateObjectKey(this.config.getId(item), item)\n )\n\n // Create mutations for each item\n items.forEach((item, index) => {\n // Validate the data against the schema if one exists\n const validatedData = this.validateData(item, `insert`)\n const key = keys[index]!\n\n // Check if an item with this ID already exists in the collection\n const id = this.config.getId(item)\n if (this.state.has(this.getKeyFromId(id))) {\n throw `Cannot insert document with ID \"${id}\" because it already exists in the collection`\n }\n\n const mutation: PendingMutation<T> = {\n mutationId: crypto.randomUUID(),\n original: {},\n modified: validatedData as Record<string, unknown>,\n changes: validatedData as Record<string, unknown>,\n key,\n metadata: config?.metadata as unknown,\n syncMetadata: this.config.sync.getSyncMetadata?.() || {},\n type: `insert`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\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 this.transactions.setState((sortedMap) => {\n sortedMap.set(ambientTransaction.id, ambientTransaction)\n return sortedMap\n })\n\n return ambientTransaction\n } else {\n // Create a new transaction with a mutation function that calls the onInsert handler\n const directOpTransaction = new Transaction({\n mutationFn: async (params) => {\n // Call the onInsert handler with the transaction\n return this.config.onInsert!(params)\n },\n })\n\n // Apply mutations to the new transaction\n directOpTransaction.applyMutations(mutations)\n directOpTransaction.commit()\n\n // Add the transaction to the collection's transactions store\n this.transactions.setState((sortedMap) => {\n sortedMap.set(directOpTransaction.id, directOpTransaction)\n return sortedMap\n })\n\n return directOpTransaction\n }\n }\n\n /**\n * Updates one or more items in the collection using a callback function\n * @param items - Single item/key or array of items/keys to update\n * @param configOrCallback - Either update configuration or update callback\n * @param maybeCallback - Update callback if config was provided\n * @returns A Transaction object representing the update operation(s)\n * @throws {SchemaValidationError} If the updated data fails schema validation\n * @example\n * // Update a single item\n * update(todo, (draft) => { draft.completed = true })\n *\n * // Update multiple items\n * update([todo1, todo2], (drafts) => {\n * drafts.forEach(draft => { draft.completed = true })\n * })\n *\n * // Update with metadata\n * update(todo, { metadata: { reason: \"user update\" } }, (draft) => { draft.text = \"Updated text\" })\n */\n\n /**\n * Updates one or more items in the collection using a callback function\n * @param ids - Single ID or array of IDs to update\n * @param configOrCallback - Either update configuration or update callback\n * @param maybeCallback - Update callback if config was provided\n * @returns A Transaction object representing the update operation(s)\n * @throws {SchemaValidationError} If the updated data fails schema validation\n * @example\n * // Update a single item\n * update(\"todo-1\", (draft) => { draft.completed = true })\n *\n * // Update multiple items\n * update([\"todo-1\", \"todo-2\"], (drafts) => {\n * drafts.forEach(draft => { draft.completed = true })\n * })\n *\n * // Update with metadata\n * update(\"todo-1\", { metadata: { reason: \"user update\" } }, (draft) => { draft.text = \"Updated text\" })\n */\n update<TItem extends object = T>(\n id: unknown,\n configOrCallback: ((draft: TItem) => void) | OperationConfig,\n maybeCallback?: (draft: TItem) => void\n ): TransactionType\n\n update<TItem extends object = T>(\n ids: Array<unknown>,\n configOrCallback: ((draft: Array<TItem>) => void) | OperationConfig,\n maybeCallback?: (draft: Array<TItem>) => void\n ): TransactionType\n\n update<TItem extends object = T>(\n ids: unknown | Array<unknown>,\n configOrCallback: ((draft: TItem | Array<TItem>) => void) | OperationConfig,\n maybeCallback?: (draft: TItem | Array<TItem>) => void\n ) {\n if (typeof ids === `undefined`) {\n throw new Error(`The first argument to update is missing`)\n }\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 Error(\n `Collection.update called directly (not within an explicit transaction) but no 'onUpdate' handler is configured.`\n )\n }\n\n const isArray = Array.isArray(ids)\n const idsArray = (Array.isArray(ids) ? ids : [ids]).map((id) =>\n this.getKeyFromId(id)\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 = idsArray.map((id) => {\n const item = this.state.get(id)\n if (!item) {\n throw new Error(\n `The id \"${id}\" was passed to update but an object for this ID was not found in the collection`\n )\n }\n\n return item\n }) as unknown as Array<TItem>\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<TItem>) => void\n )\n } else {\n const result = withChangeTracking(\n currentObjects[0] as TItem,\n callback as (draft: TItem) => void\n )\n changesArray = [result]\n }\n\n // Create mutations for each object that has changes\n const mutations: Array<PendingMutation<T>> = idsArray\n .map((id, 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 T\n // Validate the user-provided changes for this item\n const validatedUpdatePayload = this.validateData(\n itemChanges,\n `update`,\n id\n )\n\n // Construct the full modified item by applying the validated update payload to the original item\n const modifiedItem = { ...originalItem, ...validatedUpdatePayload }\n\n // Check if the ID of the item is being changed\n const originalItemId = this.config.getId(originalItem)\n const modifiedItemId = this.config.getId(modifiedItem)\n\n if (originalItemId !== modifiedItemId) {\n throw new Error(\n `Updating the ID of an item is not allowed. Original ID: \"${originalItemId}\", Attempted new ID: \"${modifiedItemId}\". Please delete the old item and create a new one if an ID change is necessary.`\n )\n }\n\n return {\n mutationId: crypto.randomUUID(),\n original: originalItem as Record<string, unknown>,\n modified: modifiedItem as Record<string, unknown>,\n changes: validatedUpdatePayload as Record<string, unknown>,\n key: id,\n metadata: config.metadata as unknown,\n syncMetadata: (this.syncedMetadata.state.get(id) || {}) as Record<\n string,\n unknown\n >,\n type: `update`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\n }\n })\n .filter(Boolean) as Array<PendingMutation<T>>\n\n // If no changes were made, return early\n if (mutations.length === 0) {\n throw new Error(`No changes were made to any of the objects`)\n }\n\n // If an ambient transaction exists, use it\n if (ambientTransaction) {\n ambientTransaction.applyMutations(mutations)\n\n this.transactions.setState((sortedMap) => {\n sortedMap.set(ambientTransaction.id, ambientTransaction)\n return sortedMap\n })\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 = new Transaction({\n mutationFn: async (transaction) => {\n // Call the onUpdate handler with the transaction\n return this.config.onUpdate!(transaction)\n },\n })\n\n // Apply mutations to the new transaction\n directOpTransaction.applyMutations(mutations)\n directOpTransaction.commit()\n\n // Add the transaction to the collection's transactions store\n this.transactions.setState((sortedMap) => {\n sortedMap.set(directOpTransaction.id, directOpTransaction)\n return sortedMap\n })\n\n return directOpTransaction\n }\n\n /**\n * Deletes one or more items from the collection\n * @param ids - Single ID or array of IDs to delete\n * @param config - Optional configuration including metadata\n * @returns A TransactionType object representing the delete operation(s)\n * @example\n * // Delete a single item\n * delete(\"todo-1\")\n *\n * // Delete multiple items\n * delete([\"todo-1\", \"todo-2\"])\n *\n * // Delete with metadata\n * delete(\"todo-1\", { metadata: { reason: \"completed\" } })\n */\n delete = (\n ids: Array<string> | string,\n config?: OperationConfig\n ): TransactionType => {\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 Error(\n `Collection.delete called directly (not within an explicit transaction) but no 'onDelete' handler is configured.`\n )\n }\n\n const idsArray = (Array.isArray(ids) ? ids : [ids]).map((id) =>\n this.getKeyFromId(id)\n )\n const mutations: Array<PendingMutation<T>> = []\n\n for (const id of idsArray) {\n const mutation: PendingMutation<T> = {\n mutationId: crypto.randomUUID(),\n original: (this.state.get(id) || {}) as Record<string, unknown>,\n modified: (this.state.get(id) || {}) as Record<string, unknown>,\n changes: (this.state.get(id) || {}) as Record<string, unknown>,\n key: id,\n metadata: config?.metadata as unknown,\n syncMetadata: (this.syncedMetadata.state.get(id) || {}) as Record<\n string,\n unknown\n >,\n type: `delete`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\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 this.transactions.setState((sortedMap) => {\n sortedMap.set(ambientTransaction.id, ambientTransaction)\n return sortedMap\n })\n\n return ambientTransaction\n }\n\n // Create a new transaction with a mutation function that calls the onDelete handler\n const directOpTransaction = new Transaction({\n autoCommit: true,\n mutationFn: async (transaction) => {\n // Call the onDelete handler with the transaction\n return this.config.onDelete!(transaction)\n },\n })\n\n // Apply mutations to the new transaction\n directOpTransaction.applyMutations(mutations)\n directOpTransaction.commit()\n\n // Add the transaction to the collection's transactions store\n this.transactions.setState((sortedMap) => {\n sortedMap.set(directOpTransaction.id, directOpTransaction)\n return sortedMap\n })\n\n return directOpTransaction\n }\n\n /**\n * Gets the current state of the collection as a Map\n *\n * @returns A Map containing all items in the collection, with keys as identifiers\n */\n get state() {\n return this.derivedState.state\n }\n\n /**\n * Gets the current state of the collection as a Map, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to a Map containing all items in the collection\n */\n stateWhenReady(): Promise<Map<string, T>> {\n // If we already have data or there are no loading collections, resolve immediately\n if (this.state.size > 0 || this.hasReceivedFirstCommit === true) {\n return Promise.resolve(this.state)\n }\n\n // Otherwise, wait for the first commit\n return new Promise<Map<string, T>>((resolve) => {\n this.onFirstCommit(() => {\n resolve(this.state)\n })\n })\n }\n\n /**\n * Gets the current state of the collection as an Array\n *\n * @returns An Array containing all items in the collection\n */\n get toArray() {\n return this.derivedArray.state\n }\n\n /**\n * Gets the current state of the collection as an Array, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to an Array containing all items in the collection\n */\n toArrayWhenReady(): Promise<Array<T>> {\n // If we already have data or there are no loading collections, resolve immediately\n if (this.toArray.length > 0 || this.hasReceivedFirstCommit === true) {\n return Promise.resolve(this.toArray)\n }\n\n // Otherwise, wait for the first commit\n return new Promise<Array<T>>((resolve) => {\n this.onFirstCommit(() => {\n resolve(this.toArray)\n })\n })\n }\n\n /**\n * Returns the current state of the collection as an array of changes\n * @returns An array of changes\n */\n public currentStateAsChanges(): Array<ChangeMessage<T>> {\n return [...this.state.entries()].map(([key, value]) => ({\n type: `insert`,\n key,\n value,\n }))\n }\n\n /**\n * Subscribe to changes in the collection\n * @param callback - A function that will be called with the changes in the collection\n * @returns A function that can be called to unsubscribe from the changes\n */\n public subscribeChanges(\n callback: (changes: Array<ChangeMessage<T>>) => void\n ): () => void {\n // First send the current state as changes\n callback(this.currentStateAsChanges())\n\n // Then subscribe to changes, this returns an unsubscribe function\n return this.derivedChanges.subscribe((changes) => {\n if (changes.currentVal.length > 0) {\n callback(changes.currentVal)\n }\n })\n }\n}\n"],"names":["Store","batch","config","getActiveTransaction","Transaction","SortedMap","Derived","transactions","result","withArrayChangeTracking","withChangeTracking"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"collection.cjs","sources":["../../src/collection.ts"],"sourcesContent":["import { Derived, Store, batch } from \"@tanstack/store\"\nimport { withArrayChangeTracking, withChangeTracking } from \"./proxy\"\nimport { Transaction, getActiveTransaction } from \"./transactions\"\nimport { SortedMap } from \"./SortedMap\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n Fn,\n InsertConfig,\n OperationConfig,\n OptimisticChangeMessage,\n PendingMutation,\n StandardSchema,\n Transaction as TransactionType,\n UtilsRecord,\n} from \"./types\"\n\n// Store collections in memory using Tanstack store\nexport const collectionsStore = new Store(\n new Map<string, CollectionImpl<any>>()\n)\n\n// Map to track loading collections\n\nconst loadingCollections = new Map<\n string,\n Promise<CollectionImpl<Record<string, unknown>>>\n>()\n\ninterface PendingSyncedTransaction<T extends object = Record<string, unknown>> {\n committed: boolean\n operations: Array<OptimisticChangeMessage<T>>\n}\n\n/**\n * Enhanced Collection interface that includes both data type T and utilities TUtils\n * @template T - The type of items in the collection\n * @template TUtils - The utilities record type\n */\nexport interface Collection<\n T extends object = Record<string, unknown>,\n TUtils extends UtilsRecord = {},\n> extends CollectionImpl<T> {\n readonly utils: TUtils\n}\n\n/**\n * Creates a new Collection instance with the given configuration\n *\n * @template T - The type of items in the collection\n * @template TUtils - The utilities record type\n * @param options - Collection options with optional utilities\n * @returns A new Collection with utilities exposed both at top level and under .utils\n */\nexport function createCollection<\n T extends object = Record<string, unknown>,\n TUtils extends UtilsRecord = {},\n>(options: CollectionConfig<T> & { utils?: TUtils }): Collection<T, TUtils> {\n const collection = new CollectionImpl<T>(options)\n\n // Copy utils to both top level and .utils namespace\n if (options.utils) {\n collection.utils = { ...options.utils }\n } else {\n collection.utils = {} as TUtils\n }\n\n return collection as Collection<T, TUtils>\n}\n\n/**\n * Preloads a collection with the given configuration\n * Returns a promise that resolves once the sync tool has done its first commit (initial sync is finished)\n * If the collection has already loaded, it resolves immediately\n *\n * This function is useful in route loaders or similar pre-rendering scenarios where you want\n * to ensure data is available before a route transition completes. It uses the same shared collection\n * instance that will be used by useCollection, ensuring data consistency.\n *\n * @example\n * ```typescript\n * // In a route loader\n * async function loader({ params }) {\n * await preloadCollection({\n * id: `users-${params.userId}`,\n * sync: { ... },\n * });\n *\n * return null;\n * }\n * ```\n *\n * @template T - The type of items in the collection\n * @param config - Configuration for the collection, including id and sync\n * @returns Promise that resolves when the initial sync is finished\n */\nexport function preloadCollection<T extends object = Record<string, unknown>>(\n config: CollectionConfig<T>\n): Promise<CollectionImpl<T>> {\n if (!config.id) {\n throw new Error(`The id property is required for preloadCollection`)\n }\n\n // If the collection is already fully loaded, return a resolved promise\n if (\n collectionsStore.state.has(config.id) &&\n !loadingCollections.has(config.id)\n ) {\n return Promise.resolve(\n collectionsStore.state.get(config.id)! as Collection<T>\n )\n }\n\n // If the collection is in the process of loading, return its promise\n if (loadingCollections.has(config.id)) {\n return loadingCollections.get(config.id)! as Promise<CollectionImpl<T>>\n }\n\n // Create a new collection instance if it doesn't exist\n if (!collectionsStore.state.has(config.id)) {\n collectionsStore.setState((prev) => {\n const next = new Map(prev)\n if (!config.id) {\n throw new Error(`The id property is required for preloadCollection`)\n }\n next.set(\n config.id,\n createCollection<T>({\n id: config.id,\n getId: config.getId,\n sync: config.sync,\n schema: config.schema,\n })\n )\n return next\n })\n }\n\n const collection = collectionsStore.state.get(config.id)! as Collection<T>\n\n // Create a promise that will resolve after the first commit\n let resolveFirstCommit: () => void\n const firstCommitPromise = new Promise<CollectionImpl<T>>((resolve) => {\n resolveFirstCommit = () => {\n resolve(collection as CollectionImpl<T>)\n }\n })\n\n // Register a one-time listener for the first commit\n collection.onFirstCommit(() => {\n if (!config.id) {\n throw new Error(`The id property is required for preloadCollection`)\n }\n if (loadingCollections.has(config.id)) {\n loadingCollections.delete(config.id)\n resolveFirstCommit()\n }\n })\n\n // Store the loading promise\n loadingCollections.set(\n config.id,\n firstCommitPromise as Promise<CollectionImpl<Record<string, unknown>>>\n )\n\n return firstCommitPromise\n}\n\n/**\n * Custom error class for schema validation errors\n */\nexport class SchemaValidationError extends Error {\n type: `insert` | `update`\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>\n\n constructor(\n type: `insert` | `update`,\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>,\n message?: string\n ) {\n const defaultMessage = `${type === `insert` ? `Insert` : `Update`} validation failed: ${issues\n .map((issue) => issue.message)\n .join(`, `)}`\n\n super(message || defaultMessage)\n this.name = `SchemaValidationError`\n this.type = type\n this.issues = issues\n }\n}\n\nexport class CollectionImpl<T extends object = Record<string, unknown>> {\n /**\n * Utilities namespace\n * This is populated by createCollection\n */\n public utils: Record<string, Fn> = {}\n public transactions: Store<SortedMap<string, TransactionType>>\n public optimisticOperations: Derived<Array<OptimisticChangeMessage<T>>>\n public derivedState: Derived<Map<string, T>>\n public derivedArray: Derived<Array<T>>\n public derivedChanges: Derived<Array<ChangeMessage<T>>>\n public syncedData = new Store<Map<string, T>>(new Map())\n public syncedMetadata = new Store(new Map<string, unknown>())\n private pendingSyncedTransactions: Array<PendingSyncedTransaction<T>> = []\n private syncedKeys = new Set<string>()\n public config: CollectionConfig<T>\n private hasReceivedFirstCommit = false\n\n // Array to store one-time commit listeners\n private onFirstCommitCallbacks: Array<() => void> = []\n\n /**\n * Register a callback to be executed on the next commit\n * Useful for preloading collections\n * @param callback Function to call after the next commit\n */\n public onFirstCommit(callback: () => void): void {\n this.onFirstCommitCallbacks.push(callback)\n }\n\n public id = ``\n\n /**\n * Creates a new Collection instance\n *\n * @param config - Configuration object for the collection\n * @throws Error if sync config is missing\n */\n constructor(config: CollectionConfig<T>) {\n // eslint-disable-next-line\n if (!config) {\n throw new Error(`Collection requires a config`)\n }\n if (config.id) {\n this.id = config.id\n } else {\n this.id = crypto.randomUUID()\n }\n\n // eslint-disable-next-line\n if (!config.sync) {\n throw new Error(`Collection requires a sync config`)\n }\n\n this.transactions = new Store(\n new SortedMap<string, TransactionType>(\n (a, b) => a.createdAt.getTime() - b.createdAt.getTime()\n )\n )\n\n // Copies of live mutations are stored here and removed once the transaction completes.\n this.optimisticOperations = new Derived({\n fn: ({ currDepVals: [transactions] }) => {\n const result = Array.from(transactions.values())\n .map((transaction) => {\n const isActive = ![`completed`, `failed`].includes(\n transaction.state\n )\n return transaction.mutations\n .filter((mutation) => mutation.collection === this)\n .map((mutation) => {\n const message: OptimisticChangeMessage<T> = {\n type: mutation.type,\n key: mutation.key,\n value: mutation.modified as T,\n isActive,\n }\n if (\n mutation.metadata !== undefined &&\n mutation.metadata !== null\n ) {\n message.metadata = mutation.metadata as Record<\n string,\n unknown\n >\n }\n return message\n })\n })\n .flat()\n\n return result\n },\n deps: [this.transactions],\n })\n this.optimisticOperations.mount()\n\n // Combine together synced data & optimistic operations.\n this.derivedState = new Derived({\n fn: ({ currDepVals: [syncedData, operations] }) => {\n const combined = new Map<string, T>(syncedData)\n\n // Apply the optimistic operations on top of the synced state.\n for (const operation of operations) {\n if (operation.isActive) {\n switch (operation.type) {\n case `insert`:\n combined.set(operation.key, operation.value)\n break\n case `update`:\n combined.set(operation.key, operation.value)\n break\n case `delete`:\n combined.delete(operation.key)\n break\n }\n }\n }\n\n return combined\n },\n deps: [this.syncedData, this.optimisticOperations],\n })\n\n // Create a derived array from the map to avoid recalculating it\n this.derivedArray = new Derived({\n fn: ({ currDepVals: [stateMap] }) => {\n // Collections returned by a query that has an orderBy are annotated\n // with the _orderByIndex field.\n // This is used to sort the array when it's derived.\n const array: Array<T & { _orderByIndex?: number }> = Array.from(\n stateMap.values()\n )\n if (array[0] && `_orderByIndex` in array[0]) {\n ;(array as Array<T & { _orderByIndex: number }>).sort((a, b) => {\n if (a._orderByIndex === b._orderByIndex) {\n return 0\n }\n return a._orderByIndex < b._orderByIndex ? -1 : 1\n })\n }\n return array\n },\n deps: [this.derivedState],\n })\n this.derivedArray.mount()\n\n this.derivedChanges = new Derived({\n fn: ({\n currDepVals: [derivedState, optimisticOperations],\n prevDepVals,\n }) => {\n const prevDerivedState = prevDepVals?.[0] ?? new Map<string, T>()\n const prevOptimisticOperations = prevDepVals?.[1] ?? []\n const changedKeys = new Set(this.syncedKeys)\n optimisticOperations\n .flat()\n .filter((op) => op.isActive)\n .forEach((op) => changedKeys.add(op.key))\n prevOptimisticOperations.flat().forEach((op) => {\n changedKeys.add(op.key)\n })\n\n if (changedKeys.size === 0) {\n return []\n }\n\n const changes: Array<ChangeMessage<T>> = []\n for (const key of changedKeys) {\n if (prevDerivedState.has(key) && !derivedState.has(key)) {\n changes.push({\n type: `delete`,\n key,\n value: prevDerivedState.get(key)!,\n })\n } else if (!prevDerivedState.has(key) && derivedState.has(key)) {\n changes.push({ type: `insert`, key, value: derivedState.get(key)! })\n } else if (prevDerivedState.has(key) && derivedState.has(key)) {\n const value = derivedState.get(key)!\n const previousValue = prevDerivedState.get(key)\n if (value !== previousValue) {\n // Comparing objects by reference as records are not mutated\n changes.push({\n type: `update`,\n key,\n value,\n previousValue,\n })\n }\n }\n }\n\n this.syncedKeys.clear()\n\n return changes\n },\n deps: [this.derivedState, this.optimisticOperations],\n })\n this.derivedChanges.mount()\n\n this.config = config\n\n this.derivedState.mount()\n\n // Start the sync process\n config.sync.sync({\n collection: this,\n begin: () => {\n this.pendingSyncedTransactions.push({\n committed: false,\n operations: [],\n })\n },\n write: (messageWithoutKey: Omit<ChangeMessage<T>, `key`>) => {\n const pendingTransaction =\n this.pendingSyncedTransactions[\n this.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new Error(`No pending sync transaction to write to`)\n }\n if (pendingTransaction.committed) {\n throw new Error(\n `The pending sync transaction is already committed, you can't still write to it.`\n )\n }\n const key = this.generateObjectKey(\n this.config.getId(messageWithoutKey.value),\n messageWithoutKey.value\n )\n\n // Check if an item with this ID already exists when inserting\n if (messageWithoutKey.type === `insert`) {\n if (\n this.syncedData.state.has(key) &&\n !pendingTransaction.operations.some(\n (op) => op.key === key && op.type === `delete`\n )\n ) {\n const id = this.config.getId(messageWithoutKey.value)\n throw new Error(\n `Cannot insert document with ID \"${id}\" from sync because it already exists in the collection \"${this.id}\"`\n )\n }\n }\n\n const message: ChangeMessage<T> = {\n ...messageWithoutKey,\n key,\n }\n pendingTransaction.operations.push(message)\n },\n commit: () => {\n const pendingTransaction =\n this.pendingSyncedTransactions[\n this.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new Error(`No pending sync transaction to commit`)\n }\n if (pendingTransaction.committed) {\n throw new Error(\n `The pending sync transaction is already committed, you can't commit it again.`\n )\n }\n\n pendingTransaction.committed = true\n\n this.commitPendingTransactions()\n },\n })\n }\n\n /**\n * Attempts to commit pending synced transactions if there are no active transactions\n * This method processes operations from pending transactions and applies them to the synced data\n */\n commitPendingTransactions = () => {\n if (\n !Array.from(this.transactions.state.values()).some(\n ({ state }) => state === `persisting`\n )\n ) {\n batch(() => {\n for (const transaction of this.pendingSyncedTransactions) {\n for (const operation of transaction.operations) {\n this.syncedKeys.add(operation.key)\n this.syncedMetadata.setState((prevData) => {\n switch (operation.type) {\n case `insert`:\n prevData.set(operation.key, operation.metadata)\n break\n case `update`:\n prevData.set(operation.key, {\n ...prevData.get(operation.key)!,\n ...operation.metadata,\n })\n break\n case `delete`:\n prevData.delete(operation.key)\n break\n }\n return prevData\n })\n this.syncedData.setState((prevData) => {\n switch (operation.type) {\n case `insert`:\n prevData.set(operation.key, operation.value)\n break\n case `update`:\n prevData.set(operation.key, {\n ...prevData.get(operation.key)!,\n ...operation.value,\n })\n break\n case `delete`:\n prevData.delete(operation.key)\n break\n }\n return prevData\n })\n }\n }\n })\n\n this.pendingSyncedTransactions = []\n\n // Call any registered one-time commit listeners\n if (!this.hasReceivedFirstCommit) {\n this.hasReceivedFirstCommit = true\n const callbacks = [...this.onFirstCommitCallbacks]\n this.onFirstCommitCallbacks = []\n callbacks.forEach((callback) => callback())\n }\n }\n }\n\n private ensureStandardSchema(schema: unknown): StandardSchema<T> {\n // If the schema already implements the standard-schema interface, return it\n if (schema && typeof schema === `object` && `~standard` in schema) {\n return schema as StandardSchema<T>\n }\n\n throw new Error(\n `Schema must either implement the standard-schema interface or be a Zod schema`\n )\n }\n\n private getKeyFromId(id: unknown): string {\n if (typeof id === `undefined`) {\n throw new Error(`id is undefined`)\n }\n if (typeof id === `string` && id.startsWith(`KEY::`)) {\n return id\n } else {\n // if it's not a string, then it's some other\n // primitive type and needs turned into a key.\n return this.generateObjectKey(id, null)\n }\n }\n\n public generateObjectKey(id: any, item: any): string {\n if (typeof id === `undefined`) {\n throw new Error(\n `An object was created without a defined id: ${JSON.stringify(item)}`\n )\n }\n\n return `KEY::${this.id}/${id}`\n }\n\n private validateData(\n data: unknown,\n type: `insert` | `update`,\n key?: string\n ): T | never {\n if (!this.config.schema) return data as T\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 = { ...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 TypeError(`Schema validation must be synchronous`)\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 the original update data, not the merged data\n // We only used the merged data for validation\n return data as T\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 TypeError(`Schema validation must be synchronous`)\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 T\n }\n\n /**\n * Inserts one or more items into the collection\n * @param items - Single item or array of items to insert\n * @param config - Optional configuration including metadata and custom keys\n * @returns A TransactionType object representing the insert operation(s)\n * @throws {SchemaValidationError} If the data fails schema validation\n * @example\n * // Insert a single item\n * insert({ text: \"Buy groceries\", completed: false })\n *\n * // Insert multiple items\n * insert([\n * { text: \"Buy groceries\", completed: false },\n * { text: \"Walk dog\", completed: false }\n * ])\n *\n * // Insert with custom key\n * insert({ text: \"Buy groceries\" }, { key: \"grocery-task\" })\n */\n insert = (data: T | Array<T>, config?: InsertConfig) => {\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 Error(\n `Collection.insert called directly (not within an explicit transaction) but no 'onInsert' handler is configured.`\n )\n }\n\n const items = Array.isArray(data) ? data : [data]\n const mutations: Array<PendingMutation<T>> = []\n\n // Handle keys - convert to array if string, or generate if not provided\n const keys: Array<unknown> = items.map((item) =>\n this.generateObjectKey(this.config.getId(item), item)\n )\n\n // Create mutations for each item\n items.forEach((item, index) => {\n // Validate the data against the schema if one exists\n const validatedData = this.validateData(item, `insert`)\n const key = keys[index]!\n\n // Check if an item with this ID already exists in the collection\n const id = this.config.getId(item)\n if (this.state.has(this.getKeyFromId(id))) {\n throw `Cannot insert document with ID \"${id}\" because it already exists in the collection`\n }\n\n const mutation: PendingMutation<T> = {\n mutationId: crypto.randomUUID(),\n original: {},\n modified: validatedData as Record<string, unknown>,\n changes: validatedData as Record<string, unknown>,\n key,\n metadata: config?.metadata as unknown,\n syncMetadata: this.config.sync.getSyncMetadata?.() || {},\n type: `insert`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\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 this.transactions.setState((sortedMap) => {\n sortedMap.set(ambientTransaction.id, ambientTransaction)\n return sortedMap\n })\n\n return ambientTransaction\n } else {\n // Create a new transaction with a mutation function that calls the onInsert handler\n const directOpTransaction = new Transaction({\n mutationFn: async (params) => {\n // Call the onInsert handler with the transaction\n return this.config.onInsert!(params)\n },\n })\n\n // Apply mutations to the new transaction\n directOpTransaction.applyMutations(mutations)\n directOpTransaction.commit()\n\n // Add the transaction to the collection's transactions store\n this.transactions.setState((sortedMap) => {\n sortedMap.set(directOpTransaction.id, directOpTransaction)\n return sortedMap\n })\n\n return directOpTransaction\n }\n }\n\n /**\n * Updates one or more items in the collection using a callback function\n * @param items - Single item/key or array of items/keys to update\n * @param configOrCallback - Either update configuration or update callback\n * @param maybeCallback - Update callback if config was provided\n * @returns A Transaction object representing the update operation(s)\n * @throws {SchemaValidationError} If the updated data fails schema validation\n * @example\n * // Update a single item\n * update(todo, (draft) => { draft.completed = true })\n *\n * // Update multiple items\n * update([todo1, todo2], (drafts) => {\n * drafts.forEach(draft => { draft.completed = true })\n * })\n *\n * // Update with metadata\n * update(todo, { metadata: { reason: \"user update\" } }, (draft) => { draft.text = \"Updated text\" })\n */\n\n /**\n * Updates one or more items in the collection using a callback function\n * @param ids - Single ID or array of IDs to update\n * @param configOrCallback - Either update configuration or update callback\n * @param maybeCallback - Update callback if config was provided\n * @returns A Transaction object representing the update operation(s)\n * @throws {SchemaValidationError} If the updated data fails schema validation\n * @example\n * // Update a single item\n * update(\"todo-1\", (draft) => { draft.completed = true })\n *\n * // Update multiple items\n * update([\"todo-1\", \"todo-2\"], (drafts) => {\n * drafts.forEach(draft => { draft.completed = true })\n * })\n *\n * // Update with metadata\n * update(\"todo-1\", { metadata: { reason: \"user update\" } }, (draft) => { draft.text = \"Updated text\" })\n */\n update<TItem extends object = T>(\n id: unknown,\n configOrCallback: ((draft: TItem) => void) | OperationConfig,\n maybeCallback?: (draft: TItem) => void\n ): TransactionType\n\n update<TItem extends object = T>(\n ids: Array<unknown>,\n configOrCallback: ((draft: Array<TItem>) => void) | OperationConfig,\n maybeCallback?: (draft: Array<TItem>) => void\n ): TransactionType\n\n update<TItem extends object = T>(\n ids: unknown | Array<unknown>,\n configOrCallback: ((draft: TItem | Array<TItem>) => void) | OperationConfig,\n maybeCallback?: (draft: TItem | Array<TItem>) => void\n ) {\n if (typeof ids === `undefined`) {\n throw new Error(`The first argument to update is missing`)\n }\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 Error(\n `Collection.update called directly (not within an explicit transaction) but no 'onUpdate' handler is configured.`\n )\n }\n\n const isArray = Array.isArray(ids)\n const idsArray = (Array.isArray(ids) ? ids : [ids]).map((id) =>\n this.getKeyFromId(id)\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 = idsArray.map((id) => {\n const item = this.state.get(id)\n if (!item) {\n throw new Error(\n `The id \"${id}\" was passed to update but an object for this ID was not found in the collection`\n )\n }\n\n return item\n }) as unknown as Array<TItem>\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<TItem>) => void\n )\n } else {\n const result = withChangeTracking(\n currentObjects[0] as TItem,\n callback as (draft: TItem) => void\n )\n changesArray = [result]\n }\n\n // Create mutations for each object that has changes\n const mutations: Array<PendingMutation<T>> = idsArray\n .map((id, 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 T\n // Validate the user-provided changes for this item\n const validatedUpdatePayload = this.validateData(\n itemChanges,\n `update`,\n id\n )\n\n // Construct the full modified item by applying the validated update payload to the original item\n const modifiedItem = { ...originalItem, ...validatedUpdatePayload }\n\n // Check if the ID of the item is being changed\n const originalItemId = this.config.getId(originalItem)\n const modifiedItemId = this.config.getId(modifiedItem)\n\n if (originalItemId !== modifiedItemId) {\n throw new Error(\n `Updating the ID of an item is not allowed. Original ID: \"${originalItemId}\", Attempted new ID: \"${modifiedItemId}\". Please delete the old item and create a new one if an ID change is necessary.`\n )\n }\n\n return {\n mutationId: crypto.randomUUID(),\n original: originalItem as Record<string, unknown>,\n modified: modifiedItem as Record<string, unknown>,\n changes: validatedUpdatePayload as Record<string, unknown>,\n key: id,\n metadata: config.metadata as unknown,\n syncMetadata: (this.syncedMetadata.state.get(id) || {}) as Record<\n string,\n unknown\n >,\n type: `update`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\n }\n })\n .filter(Boolean) as Array<PendingMutation<T>>\n\n // If no changes were made, return early\n if (mutations.length === 0) {\n throw new Error(`No changes were made to any of the objects`)\n }\n\n // If an ambient transaction exists, use it\n if (ambientTransaction) {\n ambientTransaction.applyMutations(mutations)\n\n this.transactions.setState((sortedMap) => {\n sortedMap.set(ambientTransaction.id, ambientTransaction)\n return sortedMap\n })\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 = new Transaction({\n mutationFn: async (transaction) => {\n // Call the onUpdate handler with the transaction\n return this.config.onUpdate!(transaction)\n },\n })\n\n // Apply mutations to the new transaction\n directOpTransaction.applyMutations(mutations)\n directOpTransaction.commit()\n\n // Add the transaction to the collection's transactions store\n this.transactions.setState((sortedMap) => {\n sortedMap.set(directOpTransaction.id, directOpTransaction)\n return sortedMap\n })\n\n return directOpTransaction\n }\n\n /**\n * Deletes one or more items from the collection\n * @param ids - Single ID or array of IDs to delete\n * @param config - Optional configuration including metadata\n * @returns A TransactionType object representing the delete operation(s)\n * @example\n * // Delete a single item\n * delete(\"todo-1\")\n *\n * // Delete multiple items\n * delete([\"todo-1\", \"todo-2\"])\n *\n * // Delete with metadata\n * delete(\"todo-1\", { metadata: { reason: \"completed\" } })\n */\n delete = (\n ids: Array<string> | string,\n config?: OperationConfig\n ): TransactionType => {\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 Error(\n `Collection.delete called directly (not within an explicit transaction) but no 'onDelete' handler is configured.`\n )\n }\n\n const idsArray = (Array.isArray(ids) ? ids : [ids]).map((id) =>\n this.getKeyFromId(id)\n )\n const mutations: Array<PendingMutation<T>> = []\n\n for (const id of idsArray) {\n const mutation: PendingMutation<T> = {\n mutationId: crypto.randomUUID(),\n original: (this.state.get(id) || {}) as Record<string, unknown>,\n modified: (this.state.get(id) || {}) as Record<string, unknown>,\n changes: (this.state.get(id) || {}) as Record<string, unknown>,\n key: id,\n metadata: config?.metadata as unknown,\n syncMetadata: (this.syncedMetadata.state.get(id) || {}) as Record<\n string,\n unknown\n >,\n type: `delete`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\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 this.transactions.setState((sortedMap) => {\n sortedMap.set(ambientTransaction.id, ambientTransaction)\n return sortedMap\n })\n\n return ambientTransaction\n }\n\n // Create a new transaction with a mutation function that calls the onDelete handler\n const directOpTransaction = new Transaction({\n autoCommit: true,\n mutationFn: async (transaction) => {\n // Call the onDelete handler with the transaction\n return this.config.onDelete!(transaction)\n },\n })\n\n // Apply mutations to the new transaction\n directOpTransaction.applyMutations(mutations)\n directOpTransaction.commit()\n\n // Add the transaction to the collection's transactions store\n this.transactions.setState((sortedMap) => {\n sortedMap.set(directOpTransaction.id, directOpTransaction)\n return sortedMap\n })\n\n return directOpTransaction\n }\n\n /**\n * Gets the current state of the collection as a Map\n *\n * @returns A Map containing all items in the collection, with keys as identifiers\n */\n get state() {\n return this.derivedState.state\n }\n\n /**\n * Gets the current state of the collection as a Map, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to a Map containing all items in the collection\n */\n stateWhenReady(): Promise<Map<string, T>> {\n // If we already have data or there are no loading collections, resolve immediately\n if (this.state.size > 0 || this.hasReceivedFirstCommit === true) {\n return Promise.resolve(this.state)\n }\n\n // Otherwise, wait for the first commit\n return new Promise<Map<string, T>>((resolve) => {\n this.onFirstCommit(() => {\n resolve(this.state)\n })\n })\n }\n\n /**\n * Gets the current state of the collection as an Array\n *\n * @returns An Array containing all items in the collection\n */\n get toArray() {\n return this.derivedArray.state\n }\n\n /**\n * Gets the current state of the collection as an Array, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to an Array containing all items in the collection\n */\n toArrayWhenReady(): Promise<Array<T>> {\n // If we already have data or there are no loading collections, resolve immediately\n if (this.toArray.length > 0 || this.hasReceivedFirstCommit === true) {\n return Promise.resolve(this.toArray)\n }\n\n // Otherwise, wait for the first commit\n return new Promise<Array<T>>((resolve) => {\n this.onFirstCommit(() => {\n resolve(this.toArray)\n })\n })\n }\n\n /**\n * Returns the current state of the collection as an array of changes\n * @returns An array of changes\n */\n public currentStateAsChanges(): Array<ChangeMessage<T>> {\n return [...this.state.entries()].map(([key, value]) => ({\n type: `insert`,\n key,\n value,\n }))\n }\n\n /**\n * Subscribe to changes in the collection\n * @param callback - A function that will be called with the changes in the collection\n * @returns A function that can be called to unsubscribe from the changes\n */\n public subscribeChanges(\n callback: (changes: Array<ChangeMessage<T>>) => void\n ): () => void {\n // First send the current state as changes\n callback(this.currentStateAsChanges())\n\n // Then subscribe to changes, this returns an unsubscribe function\n return this.derivedChanges.subscribe((changes) => {\n if (changes.currentVal.length > 0) {\n callback(changes.currentVal)\n }\n })\n }\n}\n"],"names":["Store","batch","config","getActiveTransaction","Transaction","SortedMap","Derived","transactions","result","withArrayChangeTracking","withChangeTracking"],"mappings":"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"}
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import { Derived, Store } from '@tanstack/store';
|
|
2
2
|
import { Transaction } from './transactions.cjs';
|
|
3
3
|
import { SortedMap } from './SortedMap.cjs';
|
|
4
|
-
import { ChangeMessage, CollectionConfig, InsertConfig, OperationConfig, OptimisticChangeMessage, Transaction as TransactionType } from './types.cjs';
|
|
5
|
-
export declare const collectionsStore: Store<Map<string,
|
|
4
|
+
import { ChangeMessage, CollectionConfig, Fn, InsertConfig, OperationConfig, OptimisticChangeMessage, Transaction as TransactionType, UtilsRecord } from './types.cjs';
|
|
5
|
+
export declare const collectionsStore: Store<Map<string, CollectionImpl<any>>, (cb: Map<string, CollectionImpl<any>>) => Map<string, CollectionImpl<any>>>;
|
|
6
|
+
/**
|
|
7
|
+
* Enhanced Collection interface that includes both data type T and utilities TUtils
|
|
8
|
+
* @template T - The type of items in the collection
|
|
9
|
+
* @template TUtils - The utilities record type
|
|
10
|
+
*/
|
|
11
|
+
export interface Collection<T extends object = Record<string, unknown>, TUtils extends UtilsRecord = {}> extends CollectionImpl<T> {
|
|
12
|
+
readonly utils: TUtils;
|
|
13
|
+
}
|
|
6
14
|
/**
|
|
7
15
|
* Creates a new Collection instance with the given configuration
|
|
8
16
|
*
|
|
9
17
|
* @template T - The type of items in the collection
|
|
10
|
-
* @
|
|
11
|
-
* @
|
|
18
|
+
* @template TUtils - The utilities record type
|
|
19
|
+
* @param options - Collection options with optional utilities
|
|
20
|
+
* @returns A new Collection with utilities exposed both at top level and under .utils
|
|
12
21
|
*/
|
|
13
|
-
export declare function createCollection<T extends object = Record<string, unknown
|
|
22
|
+
export declare function createCollection<T extends object = Record<string, unknown>, TUtils extends UtilsRecord = {}>(options: CollectionConfig<T> & {
|
|
23
|
+
utils?: TUtils;
|
|
24
|
+
}): Collection<T, TUtils>;
|
|
14
25
|
/**
|
|
15
26
|
* Preloads a collection with the given configuration
|
|
16
27
|
* Returns a promise that resolves once the sync tool has done its first commit (initial sync is finished)
|
|
@@ -37,7 +48,7 @@ export declare function createCollection<T extends object = Record<string, unkno
|
|
|
37
48
|
* @param config - Configuration for the collection, including id and sync
|
|
38
49
|
* @returns Promise that resolves when the initial sync is finished
|
|
39
50
|
*/
|
|
40
|
-
export declare function preloadCollection<T extends object = Record<string, unknown>>(config: CollectionConfig<T>): Promise<
|
|
51
|
+
export declare function preloadCollection<T extends object = Record<string, unknown>>(config: CollectionConfig<T>): Promise<CollectionImpl<T>>;
|
|
41
52
|
/**
|
|
42
53
|
* Custom error class for schema validation errors
|
|
43
54
|
*/
|
|
@@ -52,7 +63,12 @@ export declare class SchemaValidationError extends Error {
|
|
|
52
63
|
path?: ReadonlyArray<string | number | symbol>;
|
|
53
64
|
}>, message?: string);
|
|
54
65
|
}
|
|
55
|
-
export declare class
|
|
66
|
+
export declare class CollectionImpl<T extends object = Record<string, unknown>> {
|
|
67
|
+
/**
|
|
68
|
+
* Utilities namespace
|
|
69
|
+
* This is populated by createCollection
|
|
70
|
+
*/
|
|
71
|
+
utils: Record<string, Fn>;
|
|
56
72
|
transactions: Store<SortedMap<string, TransactionType>>;
|
|
57
73
|
optimisticOperations: Derived<Array<OptimisticChangeMessage<T>>>;
|
|
58
74
|
derivedState: Derived<Map<string, T>>;
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -9,7 +9,7 @@ const proxy = require("./proxy.cjs");
|
|
|
9
9
|
const queryBuilder = require("./query/query-builder.cjs");
|
|
10
10
|
const compiledQuery = require("./query/compiled-query.cjs");
|
|
11
11
|
const pipelineCompiler = require("./query/pipeline-compiler.cjs");
|
|
12
|
-
exports.
|
|
12
|
+
exports.CollectionImpl = collection.CollectionImpl;
|
|
13
13
|
exports.SchemaValidationError = collection.SchemaValidationError;
|
|
14
14
|
exports.collectionsStore = collection.collectionsStore;
|
|
15
15
|
exports.createCollection = collection.createCollection;
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -71,7 +71,7 @@ class CompiledQuery {
|
|
|
71
71
|
};
|
|
72
72
|
this.graph = graph;
|
|
73
73
|
this.inputs = inputs;
|
|
74
|
-
this.resultCollection =
|
|
74
|
+
this.resultCollection = collection.createCollection({
|
|
75
75
|
id: crypto.randomUUID(),
|
|
76
76
|
// TODO: remove when we don't require any more
|
|
77
77
|
getId: (val) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compiled-query.cjs","sources":["../../../src/query/compiled-query.ts"],"sourcesContent":["import { D2, MessageType, MultiSet, output } from \"@electric-sql/d2ts\"\nimport { Effect, batch } from \"@tanstack/store\"\nimport { Collection } from \"../collection.js\"\nimport { compileQueryPipeline } from \"./pipeline-compiler.js\"\nimport type { ChangeMessage, SyncConfig } from \"../types.js\"\nimport type {\n IStreamBuilder,\n MultiSetArray,\n RootStreamBuilder,\n} from \"@electric-sql/d2ts\"\nimport type { QueryBuilder, ResultsFromContext } from \"./query-builder.js\"\nimport type { Context, Schema } from \"./types.js\"\n\nexport function compileQuery<TContext extends Context<Schema>>(\n queryBuilder: QueryBuilder<TContext>\n) {\n return new CompiledQuery<\n ResultsFromContext<TContext> & { _key?: string | number }\n >(queryBuilder)\n}\n\nexport class CompiledQuery<TResults extends object = Record<string, unknown>> {\n private graph: D2\n private inputs: Record<string, RootStreamBuilder<any>>\n private inputCollections: Record<string, Collection<any>>\n private resultCollection: Collection<TResults>\n public state: `compiled` | `running` | `stopped` = `compiled`\n private version = 0\n private unsubscribeEffect?: () => void\n\n constructor(queryBuilder: QueryBuilder<Context<Schema>>) {\n const query = queryBuilder._query\n const collections = query.collections\n\n if (!collections) {\n throw new Error(`No collections provided`)\n }\n\n this.inputCollections = collections\n\n const graph = new D2({ initialFrontier: this.version })\n const inputs = Object.fromEntries(\n Object.entries(collections).map(([key]) => [key, graph.newInput<any>()])\n )\n\n const sync: SyncConfig<TResults>[`sync`] = ({ begin, write, commit }) => {\n compileQueryPipeline<IStreamBuilder<[unknown, TResults]>>(\n query,\n inputs\n ).pipe(\n output(({ type, data }) => {\n if (type === MessageType.DATA) {\n begin()\n data.collection\n .getInner()\n .reduce((acc, [[key, value], multiplicity]) => {\n const changes = acc.get(key) || {\n deletes: 0,\n inserts: 0,\n value,\n }\n if (multiplicity < 0) {\n changes.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n changes.inserts += multiplicity\n changes.value = value\n }\n acc.set(key, changes)\n return acc\n }, new Map<unknown, { deletes: number; inserts: number; value: TResults }>())\n .forEach((changes, rawKey) => {\n const { deletes, inserts, value } = changes\n const valueWithKey = { ...value, _key: rawKey }\n if (inserts && !deletes) {\n write({\n value: valueWithKey,\n type: `insert`,\n })\n } else if (inserts >= deletes) {\n write({\n value: valueWithKey,\n type: `update`,\n })\n } else if (deletes > 0) {\n write({\n value: valueWithKey,\n type: `delete`,\n })\n }\n })\n commit()\n }\n })\n )\n graph.finalize()\n }\n\n this.graph = graph\n this.inputs = inputs\n this.resultCollection = new Collection<TResults>({\n id: crypto.randomUUID(), // TODO: remove when we don't require any more\n getId: (val) => {\n return (val as any)._key\n },\n sync: {\n sync,\n },\n })\n }\n\n get results() {\n return this.resultCollection\n }\n\n private sendChangesToInput(\n inputKey: string,\n changes: Array<ChangeMessage>,\n getId: (item: ChangeMessage[`value`]) => any\n ) {\n const input = this.inputs[inputKey]!\n const multiSetArray: MultiSetArray<unknown> = []\n for (const change of changes) {\n const key = getId(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 input.sendData(this.version, new MultiSet(multiSetArray))\n }\n\n private sendFrontierToInput(inputKey: string) {\n const input = this.inputs[inputKey]!\n input.sendFrontier(this.version)\n }\n\n private sendFrontierToAllInputs() {\n Object.entries(this.inputs).forEach(([key]) => {\n this.sendFrontierToInput(key)\n })\n }\n\n private incrementVersion() {\n this.version++\n }\n\n private runGraph() {\n this.graph.run()\n }\n\n start() {\n if (this.state === `running`) {\n throw new Error(`Query is already running`)\n } else if (this.state === `stopped`) {\n throw new Error(`Query is stopped`)\n }\n\n batch(() => {\n Object.entries(this.inputCollections).forEach(([key, collection]) => {\n this.sendChangesToInput(\n key,\n collection.currentStateAsChanges(),\n collection.config.getId\n )\n })\n this.incrementVersion()\n this.sendFrontierToAllInputs()\n this.runGraph()\n })\n\n const changeEffect = new Effect({\n fn: () => {\n batch(() => {\n Object.entries(this.inputCollections).forEach(([key, collection]) => {\n this.sendChangesToInput(\n key,\n collection.derivedChanges.state,\n collection.config.getId\n )\n })\n this.incrementVersion()\n this.sendFrontierToAllInputs()\n this.runGraph()\n })\n },\n deps: Object.values(this.inputCollections).map(\n (collection) => collection.derivedChanges\n ),\n })\n this.unsubscribeEffect = changeEffect.mount()\n\n this.state = `running`\n return () => {\n this.stop()\n }\n }\n\n stop() {\n this.unsubscribeEffect?.()\n this.unsubscribeEffect = undefined\n this.state = `stopped`\n }\n}\n"],"names":["D2","compileQueryPipeline","output","MessageType","Collection","MultiSet","batch","collection","Effect"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"compiled-query.cjs","sources":["../../../src/query/compiled-query.ts"],"sourcesContent":["import { D2, MessageType, MultiSet, output } from \"@electric-sql/d2ts\"\nimport { Effect, batch } from \"@tanstack/store\"\nimport { createCollection } from \"../collection.js\"\nimport { compileQueryPipeline } from \"./pipeline-compiler.js\"\nimport type { Collection } from \"../collection.js\"\nimport type { ChangeMessage, SyncConfig } from \"../types.js\"\nimport type {\n IStreamBuilder,\n MultiSetArray,\n RootStreamBuilder,\n} from \"@electric-sql/d2ts\"\nimport type { QueryBuilder, ResultsFromContext } from \"./query-builder.js\"\nimport type { Context, Schema } from \"./types.js\"\n\nexport function compileQuery<TContext extends Context<Schema>>(\n queryBuilder: QueryBuilder<TContext>\n) {\n return new CompiledQuery<\n ResultsFromContext<TContext> & { _key?: string | number }\n >(queryBuilder)\n}\n\nexport class CompiledQuery<TResults extends object = Record<string, unknown>> {\n private graph: D2\n private inputs: Record<string, RootStreamBuilder<any>>\n private inputCollections: Record<string, Collection<any>>\n private resultCollection: Collection<TResults>\n public state: `compiled` | `running` | `stopped` = `compiled`\n private version = 0\n private unsubscribeEffect?: () => void\n\n constructor(queryBuilder: QueryBuilder<Context<Schema>>) {\n const query = queryBuilder._query\n const collections = query.collections\n\n if (!collections) {\n throw new Error(`No collections provided`)\n }\n\n this.inputCollections = collections\n\n const graph = new D2({ initialFrontier: this.version })\n const inputs = Object.fromEntries(\n Object.entries(collections).map(([key]) => [key, graph.newInput<any>()])\n )\n\n const sync: SyncConfig<TResults>[`sync`] = ({ begin, write, commit }) => {\n compileQueryPipeline<IStreamBuilder<[unknown, TResults]>>(\n query,\n inputs\n ).pipe(\n output(({ type, data }) => {\n if (type === MessageType.DATA) {\n begin()\n data.collection\n .getInner()\n .reduce((acc, [[key, value], multiplicity]) => {\n const changes = acc.get(key) || {\n deletes: 0,\n inserts: 0,\n value,\n }\n if (multiplicity < 0) {\n changes.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n changes.inserts += multiplicity\n changes.value = value\n }\n acc.set(key, changes)\n return acc\n }, new Map<unknown, { deletes: number; inserts: number; value: TResults }>())\n .forEach((changes, rawKey) => {\n const { deletes, inserts, value } = changes\n const valueWithKey = { ...value, _key: rawKey }\n if (inserts && !deletes) {\n write({\n value: valueWithKey,\n type: `insert`,\n })\n } else if (inserts >= deletes) {\n write({\n value: valueWithKey,\n type: `update`,\n })\n } else if (deletes > 0) {\n write({\n value: valueWithKey,\n type: `delete`,\n })\n }\n })\n commit()\n }\n })\n )\n graph.finalize()\n }\n\n this.graph = graph\n this.inputs = inputs\n this.resultCollection = createCollection<TResults>({\n id: crypto.randomUUID(), // TODO: remove when we don't require any more\n getId: (val: unknown) => {\n return (val as any)._key\n },\n sync: {\n sync,\n },\n })\n }\n\n get results() {\n return this.resultCollection\n }\n\n private sendChangesToInput(\n inputKey: string,\n changes: Array<ChangeMessage>,\n getId: (item: ChangeMessage[`value`]) => any\n ) {\n const input = this.inputs[inputKey]!\n const multiSetArray: MultiSetArray<unknown> = []\n for (const change of changes) {\n const key = getId(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 input.sendData(this.version, new MultiSet(multiSetArray))\n }\n\n private sendFrontierToInput(inputKey: string) {\n const input = this.inputs[inputKey]!\n input.sendFrontier(this.version)\n }\n\n private sendFrontierToAllInputs() {\n Object.entries(this.inputs).forEach(([key]) => {\n this.sendFrontierToInput(key)\n })\n }\n\n private incrementVersion() {\n this.version++\n }\n\n private runGraph() {\n this.graph.run()\n }\n\n start() {\n if (this.state === `running`) {\n throw new Error(`Query is already running`)\n } else if (this.state === `stopped`) {\n throw new Error(`Query is stopped`)\n }\n\n batch(() => {\n Object.entries(this.inputCollections).forEach(([key, collection]) => {\n this.sendChangesToInput(\n key,\n collection.currentStateAsChanges(),\n collection.config.getId\n )\n })\n this.incrementVersion()\n this.sendFrontierToAllInputs()\n this.runGraph()\n })\n\n const changeEffect = new Effect({\n fn: () => {\n batch(() => {\n Object.entries(this.inputCollections).forEach(([key, collection]) => {\n this.sendChangesToInput(\n key,\n collection.derivedChanges.state,\n collection.config.getId\n )\n })\n this.incrementVersion()\n this.sendFrontierToAllInputs()\n this.runGraph()\n })\n },\n deps: Object.values(this.inputCollections).map(\n (collection) => collection.derivedChanges\n ),\n })\n this.unsubscribeEffect = changeEffect.mount()\n\n this.state = `running`\n return () => {\n this.stop()\n }\n }\n\n stop() {\n this.unsubscribeEffect?.()\n this.unsubscribeEffect = undefined\n this.state = `stopped`\n }\n}\n"],"names":["D2","compileQueryPipeline","output","MessageType","createCollection","MultiSet","batch","collection","Effect"],"mappings":"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"}
|
|
@@ -13,7 +13,7 @@ export declare class CompiledQuery<TResults extends object = Record<string, unkn
|
|
|
13
13
|
private version;
|
|
14
14
|
private unsubscribeEffect?;
|
|
15
15
|
constructor(queryBuilder: QueryBuilder<Context<Schema>>);
|
|
16
|
-
get results(): Collection<TResults>;
|
|
16
|
+
get results(): Collection<TResults, {}>;
|
|
17
17
|
private sendChangesToInput;
|
|
18
18
|
private sendFrontierToInput;
|
|
19
19
|
private sendFrontierToAllInputs;
|
package/dist/cjs/types.d.cts
CHANGED
|
@@ -3,6 +3,14 @@ import { Collection } from './collection.cjs';
|
|
|
3
3
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
4
4
|
import { Transaction } from './transactions.cjs';
|
|
5
5
|
export type TransactionState = `pending` | `persisting` | `completed` | `failed`;
|
|
6
|
+
/**
|
|
7
|
+
* Represents a utility function that can be attached to a collection
|
|
8
|
+
*/
|
|
9
|
+
export type Fn = (...args: Array<any>) => any;
|
|
10
|
+
/**
|
|
11
|
+
* A record of utility functions that can be attached to a collection
|
|
12
|
+
*/
|
|
13
|
+
export type UtilsRecord = Record<string, Fn>;
|
|
6
14
|
/**
|
|
7
15
|
* Represents a pending mutation within a transaction
|
|
8
16
|
* Contains information about the original and modified data, as well as metadata
|
package/dist/esm/collection.d.ts
CHANGED
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import { Derived, Store } from '@tanstack/store';
|
|
2
2
|
import { Transaction } from './transactions.js';
|
|
3
3
|
import { SortedMap } from './SortedMap.js';
|
|
4
|
-
import { ChangeMessage, CollectionConfig, InsertConfig, OperationConfig, OptimisticChangeMessage, Transaction as TransactionType } from './types.js';
|
|
5
|
-
export declare const collectionsStore: Store<Map<string,
|
|
4
|
+
import { ChangeMessage, CollectionConfig, Fn, InsertConfig, OperationConfig, OptimisticChangeMessage, Transaction as TransactionType, UtilsRecord } from './types.js';
|
|
5
|
+
export declare const collectionsStore: Store<Map<string, CollectionImpl<any>>, (cb: Map<string, CollectionImpl<any>>) => Map<string, CollectionImpl<any>>>;
|
|
6
|
+
/**
|
|
7
|
+
* Enhanced Collection interface that includes both data type T and utilities TUtils
|
|
8
|
+
* @template T - The type of items in the collection
|
|
9
|
+
* @template TUtils - The utilities record type
|
|
10
|
+
*/
|
|
11
|
+
export interface Collection<T extends object = Record<string, unknown>, TUtils extends UtilsRecord = {}> extends CollectionImpl<T> {
|
|
12
|
+
readonly utils: TUtils;
|
|
13
|
+
}
|
|
6
14
|
/**
|
|
7
15
|
* Creates a new Collection instance with the given configuration
|
|
8
16
|
*
|
|
9
17
|
* @template T - The type of items in the collection
|
|
10
|
-
* @
|
|
11
|
-
* @
|
|
18
|
+
* @template TUtils - The utilities record type
|
|
19
|
+
* @param options - Collection options with optional utilities
|
|
20
|
+
* @returns A new Collection with utilities exposed both at top level and under .utils
|
|
12
21
|
*/
|
|
13
|
-
export declare function createCollection<T extends object = Record<string, unknown
|
|
22
|
+
export declare function createCollection<T extends object = Record<string, unknown>, TUtils extends UtilsRecord = {}>(options: CollectionConfig<T> & {
|
|
23
|
+
utils?: TUtils;
|
|
24
|
+
}): Collection<T, TUtils>;
|
|
14
25
|
/**
|
|
15
26
|
* Preloads a collection with the given configuration
|
|
16
27
|
* Returns a promise that resolves once the sync tool has done its first commit (initial sync is finished)
|
|
@@ -37,7 +48,7 @@ export declare function createCollection<T extends object = Record<string, unkno
|
|
|
37
48
|
* @param config - Configuration for the collection, including id and sync
|
|
38
49
|
* @returns Promise that resolves when the initial sync is finished
|
|
39
50
|
*/
|
|
40
|
-
export declare function preloadCollection<T extends object = Record<string, unknown>>(config: CollectionConfig<T>): Promise<
|
|
51
|
+
export declare function preloadCollection<T extends object = Record<string, unknown>>(config: CollectionConfig<T>): Promise<CollectionImpl<T>>;
|
|
41
52
|
/**
|
|
42
53
|
* Custom error class for schema validation errors
|
|
43
54
|
*/
|
|
@@ -52,7 +63,12 @@ export declare class SchemaValidationError extends Error {
|
|
|
52
63
|
path?: ReadonlyArray<string | number | symbol>;
|
|
53
64
|
}>, message?: string);
|
|
54
65
|
}
|
|
55
|
-
export declare class
|
|
66
|
+
export declare class CollectionImpl<T extends object = Record<string, unknown>> {
|
|
67
|
+
/**
|
|
68
|
+
* Utilities namespace
|
|
69
|
+
* This is populated by createCollection
|
|
70
|
+
*/
|
|
71
|
+
utils: Record<string, Fn>;
|
|
56
72
|
transactions: Store<SortedMap<string, TransactionType>>;
|
|
57
73
|
optimisticOperations: Derived<Array<OptimisticChangeMessage<T>>>;
|
|
58
74
|
derivedState: Derived<Map<string, T>>;
|
package/dist/esm/collection.js
CHANGED
|
@@ -2,10 +2,18 @@ import { Store, batch, Derived } from "@tanstack/store";
|
|
|
2
2
|
import { withArrayChangeTracking, withChangeTracking } from "./proxy.js";
|
|
3
3
|
import { getActiveTransaction, Transaction } from "./transactions.js";
|
|
4
4
|
import { SortedMap } from "./SortedMap.js";
|
|
5
|
-
const collectionsStore = new Store(
|
|
5
|
+
const collectionsStore = new Store(
|
|
6
|
+
/* @__PURE__ */ new Map()
|
|
7
|
+
);
|
|
6
8
|
const loadingCollections = /* @__PURE__ */ new Map();
|
|
7
|
-
function createCollection(
|
|
8
|
-
|
|
9
|
+
function createCollection(options) {
|
|
10
|
+
const collection = new CollectionImpl(options);
|
|
11
|
+
if (options.utils) {
|
|
12
|
+
collection.utils = { ...options.utils };
|
|
13
|
+
} else {
|
|
14
|
+
collection.utils = {};
|
|
15
|
+
}
|
|
16
|
+
return collection;
|
|
9
17
|
}
|
|
10
18
|
function preloadCollection(config) {
|
|
11
19
|
if (!config.id) {
|
|
@@ -27,7 +35,7 @@ function preloadCollection(config) {
|
|
|
27
35
|
}
|
|
28
36
|
next.set(
|
|
29
37
|
config.id,
|
|
30
|
-
|
|
38
|
+
createCollection({
|
|
31
39
|
id: config.id,
|
|
32
40
|
getId: config.getId,
|
|
33
41
|
sync: config.sync,
|
|
@@ -68,7 +76,7 @@ class SchemaValidationError extends Error {
|
|
|
68
76
|
this.issues = issues;
|
|
69
77
|
}
|
|
70
78
|
}
|
|
71
|
-
class
|
|
79
|
+
class CollectionImpl {
|
|
72
80
|
/**
|
|
73
81
|
* Creates a new Collection instance
|
|
74
82
|
*
|
|
@@ -76,6 +84,7 @@ class Collection {
|
|
|
76
84
|
* @throws Error if sync config is missing
|
|
77
85
|
*/
|
|
78
86
|
constructor(config) {
|
|
87
|
+
this.utils = {};
|
|
79
88
|
this.syncedData = new Store(/* @__PURE__ */ new Map());
|
|
80
89
|
this.syncedMetadata = new Store(/* @__PURE__ */ new Map());
|
|
81
90
|
this.pendingSyncedTransactions = [];
|
|
@@ -665,7 +674,7 @@ class Collection {
|
|
|
665
674
|
}
|
|
666
675
|
}
|
|
667
676
|
export {
|
|
668
|
-
|
|
677
|
+
CollectionImpl,
|
|
669
678
|
SchemaValidationError,
|
|
670
679
|
collectionsStore,
|
|
671
680
|
createCollection,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collection.js","sources":["../../src/collection.ts"],"sourcesContent":["import { Derived, Store, batch } from \"@tanstack/store\"\nimport { withArrayChangeTracking, withChangeTracking } from \"./proxy\"\nimport { Transaction, getActiveTransaction } from \"./transactions\"\nimport { SortedMap } from \"./SortedMap\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n InsertConfig,\n OperationConfig,\n OptimisticChangeMessage,\n PendingMutation,\n StandardSchema,\n Transaction as TransactionType,\n} from \"./types\"\n\n// Store collections in memory using Tanstack store\nexport const collectionsStore = new Store(new Map<string, Collection<any>>())\n\n// Map to track loading collections\n\nconst loadingCollections = new Map<\n string,\n Promise<Collection<Record<string, unknown>>>\n>()\n\ninterface PendingSyncedTransaction<T extends object = Record<string, unknown>> {\n committed: boolean\n operations: Array<OptimisticChangeMessage<T>>\n}\n\n/**\n * Creates a new Collection instance with the given configuration\n *\n * @template T - The type of items in the collection\n * @param config - Configuration for the collection, including id and sync\n * @returns A new Collection instance\n */\nexport function createCollection<T extends object = Record<string, unknown>>(\n config: CollectionConfig<T>\n): Collection<T> {\n return new Collection<T>(config)\n}\n\n/**\n * Preloads a collection with the given configuration\n * Returns a promise that resolves once the sync tool has done its first commit (initial sync is finished)\n * If the collection has already loaded, it resolves immediately\n *\n * This function is useful in route loaders or similar pre-rendering scenarios where you want\n * to ensure data is available before a route transition completes. It uses the same shared collection\n * instance that will be used by useCollection, ensuring data consistency.\n *\n * @example\n * ```typescript\n * // In a route loader\n * async function loader({ params }) {\n * await preloadCollection({\n * id: `users-${params.userId}`,\n * sync: { ... },\n * });\n *\n * return null;\n * }\n * ```\n *\n * @template T - The type of items in the collection\n * @param config - Configuration for the collection, including id and sync\n * @returns Promise that resolves when the initial sync is finished\n */\nexport function preloadCollection<T extends object = Record<string, unknown>>(\n config: CollectionConfig<T>\n): Promise<Collection<T>> {\n if (!config.id) {\n throw new Error(`The id property is required for preloadCollection`)\n }\n\n // If the collection is already fully loaded, return a resolved promise\n if (\n collectionsStore.state.has(config.id) &&\n !loadingCollections.has(config.id)\n ) {\n return Promise.resolve(\n collectionsStore.state.get(config.id)! as Collection<T>\n )\n }\n\n // If the collection is in the process of loading, return its promise\n if (loadingCollections.has(config.id)) {\n return loadingCollections.get(config.id)! as Promise<Collection<T>>\n }\n\n // Create a new collection instance if it doesn't exist\n if (!collectionsStore.state.has(config.id)) {\n collectionsStore.setState((prev) => {\n const next = new Map(prev)\n if (!config.id) {\n throw new Error(`The id property is required for preloadCollection`)\n }\n next.set(\n config.id,\n new Collection<T>({\n id: config.id,\n getId: config.getId,\n sync: config.sync,\n schema: config.schema,\n })\n )\n return next\n })\n }\n\n const collection = collectionsStore.state.get(config.id)! as Collection<T>\n\n // Create a promise that will resolve after the first commit\n let resolveFirstCommit: () => void\n const firstCommitPromise = new Promise<Collection<T>>((resolve) => {\n resolveFirstCommit = () => {\n resolve(collection)\n }\n })\n\n // Register a one-time listener for the first commit\n collection.onFirstCommit(() => {\n if (!config.id) {\n throw new Error(`The id property is required for preloadCollection`)\n }\n if (loadingCollections.has(config.id)) {\n loadingCollections.delete(config.id)\n resolveFirstCommit()\n }\n })\n\n // Store the loading promise\n loadingCollections.set(\n config.id,\n firstCommitPromise as Promise<Collection<Record<string, unknown>>>\n )\n\n return firstCommitPromise\n}\n\n/**\n * Custom error class for schema validation errors\n */\nexport class SchemaValidationError extends Error {\n type: `insert` | `update`\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>\n\n constructor(\n type: `insert` | `update`,\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>,\n message?: string\n ) {\n const defaultMessage = `${type === `insert` ? `Insert` : `Update`} validation failed: ${issues\n .map((issue) => issue.message)\n .join(`, `)}`\n\n super(message || defaultMessage)\n this.name = `SchemaValidationError`\n this.type = type\n this.issues = issues\n }\n}\n\nexport class Collection<T extends object = Record<string, unknown>> {\n public transactions: Store<SortedMap<string, TransactionType>>\n public optimisticOperations: Derived<Array<OptimisticChangeMessage<T>>>\n public derivedState: Derived<Map<string, T>>\n public derivedArray: Derived<Array<T>>\n public derivedChanges: Derived<Array<ChangeMessage<T>>>\n public syncedData = new Store<Map<string, T>>(new Map())\n public syncedMetadata = new Store(new Map<string, unknown>())\n private pendingSyncedTransactions: Array<PendingSyncedTransaction<T>> = []\n private syncedKeys = new Set<string>()\n public config: CollectionConfig<T>\n private hasReceivedFirstCommit = false\n\n // Array to store one-time commit listeners\n private onFirstCommitCallbacks: Array<() => void> = []\n\n /**\n * Register a callback to be executed on the next commit\n * Useful for preloading collections\n * @param callback Function to call after the next commit\n */\n public onFirstCommit(callback: () => void): void {\n this.onFirstCommitCallbacks.push(callback)\n }\n\n public id = ``\n\n /**\n * Creates a new Collection instance\n *\n * @param config - Configuration object for the collection\n * @throws Error if sync config is missing\n */\n constructor(config: CollectionConfig<T>) {\n // eslint-disable-next-line\n if (!config) {\n throw new Error(`Collection requires a config`)\n }\n if (config.id) {\n this.id = config.id\n } else {\n this.id = crypto.randomUUID()\n }\n\n // eslint-disable-next-line\n if (!config.sync) {\n throw new Error(`Collection requires a sync config`)\n }\n\n this.transactions = new Store(\n new SortedMap<string, TransactionType>(\n (a, b) => a.createdAt.getTime() - b.createdAt.getTime()\n )\n )\n\n // Copies of live mutations are stored here and removed once the transaction completes.\n this.optimisticOperations = new Derived({\n fn: ({ currDepVals: [transactions] }) => {\n const result = Array.from(transactions.values())\n .map((transaction) => {\n const isActive = ![`completed`, `failed`].includes(\n transaction.state\n )\n return transaction.mutations\n .filter((mutation) => mutation.collection === this)\n .map((mutation) => {\n const message: OptimisticChangeMessage<T> = {\n type: mutation.type,\n key: mutation.key,\n value: mutation.modified as T,\n isActive,\n }\n if (\n mutation.metadata !== undefined &&\n mutation.metadata !== null\n ) {\n message.metadata = mutation.metadata as Record<\n string,\n unknown\n >\n }\n return message\n })\n })\n .flat()\n\n return result\n },\n deps: [this.transactions],\n })\n this.optimisticOperations.mount()\n\n // Combine together synced data & optimistic operations.\n this.derivedState = new Derived({\n fn: ({ currDepVals: [syncedData, operations] }) => {\n const combined = new Map<string, T>(syncedData)\n\n // Apply the optimistic operations on top of the synced state.\n for (const operation of operations) {\n if (operation.isActive) {\n switch (operation.type) {\n case `insert`:\n combined.set(operation.key, operation.value)\n break\n case `update`:\n combined.set(operation.key, operation.value)\n break\n case `delete`:\n combined.delete(operation.key)\n break\n }\n }\n }\n\n return combined\n },\n deps: [this.syncedData, this.optimisticOperations],\n })\n\n // Create a derived array from the map to avoid recalculating it\n this.derivedArray = new Derived({\n fn: ({ currDepVals: [stateMap] }) => {\n // Collections returned by a query that has an orderBy are annotated\n // with the _orderByIndex field.\n // This is used to sort the array when it's derived.\n const array: Array<T & { _orderByIndex?: number }> = Array.from(\n stateMap.values()\n )\n if (array[0] && `_orderByIndex` in array[0]) {\n ;(array as Array<T & { _orderByIndex: number }>).sort((a, b) => {\n if (a._orderByIndex === b._orderByIndex) {\n return 0\n }\n return a._orderByIndex < b._orderByIndex ? -1 : 1\n })\n }\n return array\n },\n deps: [this.derivedState],\n })\n this.derivedArray.mount()\n\n this.derivedChanges = new Derived({\n fn: ({\n currDepVals: [derivedState, optimisticOperations],\n prevDepVals,\n }) => {\n const prevDerivedState = prevDepVals?.[0] ?? new Map<string, T>()\n const prevOptimisticOperations = prevDepVals?.[1] ?? []\n const changedKeys = new Set(this.syncedKeys)\n optimisticOperations\n .flat()\n .filter((op) => op.isActive)\n .forEach((op) => changedKeys.add(op.key))\n prevOptimisticOperations.flat().forEach((op) => {\n changedKeys.add(op.key)\n })\n\n if (changedKeys.size === 0) {\n return []\n }\n\n const changes: Array<ChangeMessage<T>> = []\n for (const key of changedKeys) {\n if (prevDerivedState.has(key) && !derivedState.has(key)) {\n changes.push({\n type: `delete`,\n key,\n value: prevDerivedState.get(key)!,\n })\n } else if (!prevDerivedState.has(key) && derivedState.has(key)) {\n changes.push({ type: `insert`, key, value: derivedState.get(key)! })\n } else if (prevDerivedState.has(key) && derivedState.has(key)) {\n const value = derivedState.get(key)!\n const previousValue = prevDerivedState.get(key)\n if (value !== previousValue) {\n // Comparing objects by reference as records are not mutated\n changes.push({\n type: `update`,\n key,\n value,\n previousValue,\n })\n }\n }\n }\n\n this.syncedKeys.clear()\n\n return changes\n },\n deps: [this.derivedState, this.optimisticOperations],\n })\n this.derivedChanges.mount()\n\n this.config = config\n\n this.derivedState.mount()\n\n // Start the sync process\n config.sync.sync({\n collection: this,\n begin: () => {\n this.pendingSyncedTransactions.push({\n committed: false,\n operations: [],\n })\n },\n write: (messageWithoutKey: Omit<ChangeMessage<T>, `key`>) => {\n const pendingTransaction =\n this.pendingSyncedTransactions[\n this.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new Error(`No pending sync transaction to write to`)\n }\n if (pendingTransaction.committed) {\n throw new Error(\n `The pending sync transaction is already committed, you can't still write to it.`\n )\n }\n const key = this.generateObjectKey(\n this.config.getId(messageWithoutKey.value),\n messageWithoutKey.value\n )\n\n // Check if an item with this ID already exists when inserting\n if (messageWithoutKey.type === `insert`) {\n if (\n this.syncedData.state.has(key) &&\n !pendingTransaction.operations.some(\n (op) => op.key === key && op.type === `delete`\n )\n ) {\n const id = this.config.getId(messageWithoutKey.value)\n throw new Error(\n `Cannot insert document with ID \"${id}\" from sync because it already exists in the collection \"${this.id}\"`\n )\n }\n }\n\n const message: ChangeMessage<T> = {\n ...messageWithoutKey,\n key,\n }\n pendingTransaction.operations.push(message)\n },\n commit: () => {\n const pendingTransaction =\n this.pendingSyncedTransactions[\n this.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new Error(`No pending sync transaction to commit`)\n }\n if (pendingTransaction.committed) {\n throw new Error(\n `The pending sync transaction is already committed, you can't commit it again.`\n )\n }\n\n pendingTransaction.committed = true\n\n this.commitPendingTransactions()\n },\n })\n }\n\n /**\n * Attempts to commit pending synced transactions if there are no active transactions\n * This method processes operations from pending transactions and applies them to the synced data\n */\n commitPendingTransactions = () => {\n if (\n !Array.from(this.transactions.state.values()).some(\n ({ state }) => state === `persisting`\n )\n ) {\n batch(() => {\n for (const transaction of this.pendingSyncedTransactions) {\n for (const operation of transaction.operations) {\n this.syncedKeys.add(operation.key)\n this.syncedMetadata.setState((prevData) => {\n switch (operation.type) {\n case `insert`:\n prevData.set(operation.key, operation.metadata)\n break\n case `update`:\n prevData.set(operation.key, {\n ...prevData.get(operation.key)!,\n ...operation.metadata,\n })\n break\n case `delete`:\n prevData.delete(operation.key)\n break\n }\n return prevData\n })\n this.syncedData.setState((prevData) => {\n switch (operation.type) {\n case `insert`:\n prevData.set(operation.key, operation.value)\n break\n case `update`:\n prevData.set(operation.key, {\n ...prevData.get(operation.key)!,\n ...operation.value,\n })\n break\n case `delete`:\n prevData.delete(operation.key)\n break\n }\n return prevData\n })\n }\n }\n })\n\n this.pendingSyncedTransactions = []\n\n // Call any registered one-time commit listeners\n if (!this.hasReceivedFirstCommit) {\n this.hasReceivedFirstCommit = true\n const callbacks = [...this.onFirstCommitCallbacks]\n this.onFirstCommitCallbacks = []\n callbacks.forEach((callback) => callback())\n }\n }\n }\n\n private ensureStandardSchema(schema: unknown): StandardSchema<T> {\n // If the schema already implements the standard-schema interface, return it\n if (schema && typeof schema === `object` && `~standard` in schema) {\n return schema as StandardSchema<T>\n }\n\n throw new Error(\n `Schema must either implement the standard-schema interface or be a Zod schema`\n )\n }\n\n private getKeyFromId(id: unknown): string {\n if (typeof id === `undefined`) {\n throw new Error(`id is undefined`)\n }\n if (typeof id === `string` && id.startsWith(`KEY::`)) {\n return id\n } else {\n // if it's not a string, then it's some other\n // primitive type and needs turned into a key.\n return this.generateObjectKey(id, null)\n }\n }\n\n public generateObjectKey(id: any, item: any): string {\n if (typeof id === `undefined`) {\n throw new Error(\n `An object was created without a defined id: ${JSON.stringify(item)}`\n )\n }\n\n return `KEY::${this.id}/${id}`\n }\n\n private validateData(\n data: unknown,\n type: `insert` | `update`,\n key?: string\n ): T | never {\n if (!this.config.schema) return data as T\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 = { ...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 TypeError(`Schema validation must be synchronous`)\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 the original update data, not the merged data\n // We only used the merged data for validation\n return data as T\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 TypeError(`Schema validation must be synchronous`)\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 T\n }\n\n /**\n * Inserts one or more items into the collection\n * @param items - Single item or array of items to insert\n * @param config - Optional configuration including metadata and custom keys\n * @returns A TransactionType object representing the insert operation(s)\n * @throws {SchemaValidationError} If the data fails schema validation\n * @example\n * // Insert a single item\n * insert({ text: \"Buy groceries\", completed: false })\n *\n * // Insert multiple items\n * insert([\n * { text: \"Buy groceries\", completed: false },\n * { text: \"Walk dog\", completed: false }\n * ])\n *\n * // Insert with custom key\n * insert({ text: \"Buy groceries\" }, { key: \"grocery-task\" })\n */\n insert = (data: T | Array<T>, config?: InsertConfig) => {\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 Error(\n `Collection.insert called directly (not within an explicit transaction) but no 'onInsert' handler is configured.`\n )\n }\n\n const items = Array.isArray(data) ? data : [data]\n const mutations: Array<PendingMutation<T>> = []\n\n // Handle keys - convert to array if string, or generate if not provided\n const keys: Array<unknown> = items.map((item) =>\n this.generateObjectKey(this.config.getId(item), item)\n )\n\n // Create mutations for each item\n items.forEach((item, index) => {\n // Validate the data against the schema if one exists\n const validatedData = this.validateData(item, `insert`)\n const key = keys[index]!\n\n // Check if an item with this ID already exists in the collection\n const id = this.config.getId(item)\n if (this.state.has(this.getKeyFromId(id))) {\n throw `Cannot insert document with ID \"${id}\" because it already exists in the collection`\n }\n\n const mutation: PendingMutation<T> = {\n mutationId: crypto.randomUUID(),\n original: {},\n modified: validatedData as Record<string, unknown>,\n changes: validatedData as Record<string, unknown>,\n key,\n metadata: config?.metadata as unknown,\n syncMetadata: this.config.sync.getSyncMetadata?.() || {},\n type: `insert`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\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 this.transactions.setState((sortedMap) => {\n sortedMap.set(ambientTransaction.id, ambientTransaction)\n return sortedMap\n })\n\n return ambientTransaction\n } else {\n // Create a new transaction with a mutation function that calls the onInsert handler\n const directOpTransaction = new Transaction({\n mutationFn: async (params) => {\n // Call the onInsert handler with the transaction\n return this.config.onInsert!(params)\n },\n })\n\n // Apply mutations to the new transaction\n directOpTransaction.applyMutations(mutations)\n directOpTransaction.commit()\n\n // Add the transaction to the collection's transactions store\n this.transactions.setState((sortedMap) => {\n sortedMap.set(directOpTransaction.id, directOpTransaction)\n return sortedMap\n })\n\n return directOpTransaction\n }\n }\n\n /**\n * Updates one or more items in the collection using a callback function\n * @param items - Single item/key or array of items/keys to update\n * @param configOrCallback - Either update configuration or update callback\n * @param maybeCallback - Update callback if config was provided\n * @returns A Transaction object representing the update operation(s)\n * @throws {SchemaValidationError} If the updated data fails schema validation\n * @example\n * // Update a single item\n * update(todo, (draft) => { draft.completed = true })\n *\n * // Update multiple items\n * update([todo1, todo2], (drafts) => {\n * drafts.forEach(draft => { draft.completed = true })\n * })\n *\n * // Update with metadata\n * update(todo, { metadata: { reason: \"user update\" } }, (draft) => { draft.text = \"Updated text\" })\n */\n\n /**\n * Updates one or more items in the collection using a callback function\n * @param ids - Single ID or array of IDs to update\n * @param configOrCallback - Either update configuration or update callback\n * @param maybeCallback - Update callback if config was provided\n * @returns A Transaction object representing the update operation(s)\n * @throws {SchemaValidationError} If the updated data fails schema validation\n * @example\n * // Update a single item\n * update(\"todo-1\", (draft) => { draft.completed = true })\n *\n * // Update multiple items\n * update([\"todo-1\", \"todo-2\"], (drafts) => {\n * drafts.forEach(draft => { draft.completed = true })\n * })\n *\n * // Update with metadata\n * update(\"todo-1\", { metadata: { reason: \"user update\" } }, (draft) => { draft.text = \"Updated text\" })\n */\n update<TItem extends object = T>(\n id: unknown,\n configOrCallback: ((draft: TItem) => void) | OperationConfig,\n maybeCallback?: (draft: TItem) => void\n ): TransactionType\n\n update<TItem extends object = T>(\n ids: Array<unknown>,\n configOrCallback: ((draft: Array<TItem>) => void) | OperationConfig,\n maybeCallback?: (draft: Array<TItem>) => void\n ): TransactionType\n\n update<TItem extends object = T>(\n ids: unknown | Array<unknown>,\n configOrCallback: ((draft: TItem | Array<TItem>) => void) | OperationConfig,\n maybeCallback?: (draft: TItem | Array<TItem>) => void\n ) {\n if (typeof ids === `undefined`) {\n throw new Error(`The first argument to update is missing`)\n }\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 Error(\n `Collection.update called directly (not within an explicit transaction) but no 'onUpdate' handler is configured.`\n )\n }\n\n const isArray = Array.isArray(ids)\n const idsArray = (Array.isArray(ids) ? ids : [ids]).map((id) =>\n this.getKeyFromId(id)\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 = idsArray.map((id) => {\n const item = this.state.get(id)\n if (!item) {\n throw new Error(\n `The id \"${id}\" was passed to update but an object for this ID was not found in the collection`\n )\n }\n\n return item\n }) as unknown as Array<TItem>\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<TItem>) => void\n )\n } else {\n const result = withChangeTracking(\n currentObjects[0] as TItem,\n callback as (draft: TItem) => void\n )\n changesArray = [result]\n }\n\n // Create mutations for each object that has changes\n const mutations: Array<PendingMutation<T>> = idsArray\n .map((id, 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 T\n // Validate the user-provided changes for this item\n const validatedUpdatePayload = this.validateData(\n itemChanges,\n `update`,\n id\n )\n\n // Construct the full modified item by applying the validated update payload to the original item\n const modifiedItem = { ...originalItem, ...validatedUpdatePayload }\n\n // Check if the ID of the item is being changed\n const originalItemId = this.config.getId(originalItem)\n const modifiedItemId = this.config.getId(modifiedItem)\n\n if (originalItemId !== modifiedItemId) {\n throw new Error(\n `Updating the ID of an item is not allowed. Original ID: \"${originalItemId}\", Attempted new ID: \"${modifiedItemId}\". Please delete the old item and create a new one if an ID change is necessary.`\n )\n }\n\n return {\n mutationId: crypto.randomUUID(),\n original: originalItem as Record<string, unknown>,\n modified: modifiedItem as Record<string, unknown>,\n changes: validatedUpdatePayload as Record<string, unknown>,\n key: id,\n metadata: config.metadata as unknown,\n syncMetadata: (this.syncedMetadata.state.get(id) || {}) as Record<\n string,\n unknown\n >,\n type: `update`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\n }\n })\n .filter(Boolean) as Array<PendingMutation<T>>\n\n // If no changes were made, return early\n if (mutations.length === 0) {\n throw new Error(`No changes were made to any of the objects`)\n }\n\n // If an ambient transaction exists, use it\n if (ambientTransaction) {\n ambientTransaction.applyMutations(mutations)\n\n this.transactions.setState((sortedMap) => {\n sortedMap.set(ambientTransaction.id, ambientTransaction)\n return sortedMap\n })\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 = new Transaction({\n mutationFn: async (transaction) => {\n // Call the onUpdate handler with the transaction\n return this.config.onUpdate!(transaction)\n },\n })\n\n // Apply mutations to the new transaction\n directOpTransaction.applyMutations(mutations)\n directOpTransaction.commit()\n\n // Add the transaction to the collection's transactions store\n this.transactions.setState((sortedMap) => {\n sortedMap.set(directOpTransaction.id, directOpTransaction)\n return sortedMap\n })\n\n return directOpTransaction\n }\n\n /**\n * Deletes one or more items from the collection\n * @param ids - Single ID or array of IDs to delete\n * @param config - Optional configuration including metadata\n * @returns A TransactionType object representing the delete operation(s)\n * @example\n * // Delete a single item\n * delete(\"todo-1\")\n *\n * // Delete multiple items\n * delete([\"todo-1\", \"todo-2\"])\n *\n * // Delete with metadata\n * delete(\"todo-1\", { metadata: { reason: \"completed\" } })\n */\n delete = (\n ids: Array<string> | string,\n config?: OperationConfig\n ): TransactionType => {\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 Error(\n `Collection.delete called directly (not within an explicit transaction) but no 'onDelete' handler is configured.`\n )\n }\n\n const idsArray = (Array.isArray(ids) ? ids : [ids]).map((id) =>\n this.getKeyFromId(id)\n )\n const mutations: Array<PendingMutation<T>> = []\n\n for (const id of idsArray) {\n const mutation: PendingMutation<T> = {\n mutationId: crypto.randomUUID(),\n original: (this.state.get(id) || {}) as Record<string, unknown>,\n modified: (this.state.get(id) || {}) as Record<string, unknown>,\n changes: (this.state.get(id) || {}) as Record<string, unknown>,\n key: id,\n metadata: config?.metadata as unknown,\n syncMetadata: (this.syncedMetadata.state.get(id) || {}) as Record<\n string,\n unknown\n >,\n type: `delete`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\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 this.transactions.setState((sortedMap) => {\n sortedMap.set(ambientTransaction.id, ambientTransaction)\n return sortedMap\n })\n\n return ambientTransaction\n }\n\n // Create a new transaction with a mutation function that calls the onDelete handler\n const directOpTransaction = new Transaction({\n autoCommit: true,\n mutationFn: async (transaction) => {\n // Call the onDelete handler with the transaction\n return this.config.onDelete!(transaction)\n },\n })\n\n // Apply mutations to the new transaction\n directOpTransaction.applyMutations(mutations)\n directOpTransaction.commit()\n\n // Add the transaction to the collection's transactions store\n this.transactions.setState((sortedMap) => {\n sortedMap.set(directOpTransaction.id, directOpTransaction)\n return sortedMap\n })\n\n return directOpTransaction\n }\n\n /**\n * Gets the current state of the collection as a Map\n *\n * @returns A Map containing all items in the collection, with keys as identifiers\n */\n get state() {\n return this.derivedState.state\n }\n\n /**\n * Gets the current state of the collection as a Map, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to a Map containing all items in the collection\n */\n stateWhenReady(): Promise<Map<string, T>> {\n // If we already have data or there are no loading collections, resolve immediately\n if (this.state.size > 0 || this.hasReceivedFirstCommit === true) {\n return Promise.resolve(this.state)\n }\n\n // Otherwise, wait for the first commit\n return new Promise<Map<string, T>>((resolve) => {\n this.onFirstCommit(() => {\n resolve(this.state)\n })\n })\n }\n\n /**\n * Gets the current state of the collection as an Array\n *\n * @returns An Array containing all items in the collection\n */\n get toArray() {\n return this.derivedArray.state\n }\n\n /**\n * Gets the current state of the collection as an Array, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to an Array containing all items in the collection\n */\n toArrayWhenReady(): Promise<Array<T>> {\n // If we already have data or there are no loading collections, resolve immediately\n if (this.toArray.length > 0 || this.hasReceivedFirstCommit === true) {\n return Promise.resolve(this.toArray)\n }\n\n // Otherwise, wait for the first commit\n return new Promise<Array<T>>((resolve) => {\n this.onFirstCommit(() => {\n resolve(this.toArray)\n })\n })\n }\n\n /**\n * Returns the current state of the collection as an array of changes\n * @returns An array of changes\n */\n public currentStateAsChanges(): Array<ChangeMessage<T>> {\n return [...this.state.entries()].map(([key, value]) => ({\n type: `insert`,\n key,\n value,\n }))\n }\n\n /**\n * Subscribe to changes in the collection\n * @param callback - A function that will be called with the changes in the collection\n * @returns A function that can be called to unsubscribe from the changes\n */\n public subscribeChanges(\n callback: (changes: Array<ChangeMessage<T>>) => void\n ): () => void {\n // First send the current state as changes\n callback(this.currentStateAsChanges())\n\n // Then subscribe to changes, this returns an unsubscribe function\n return this.derivedChanges.subscribe((changes) => {\n if (changes.currentVal.length > 0) {\n callback(changes.currentVal)\n }\n })\n }\n}\n"],"names":["config","result"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"collection.js","sources":["../../src/collection.ts"],"sourcesContent":["import { Derived, Store, batch } from \"@tanstack/store\"\nimport { withArrayChangeTracking, withChangeTracking } from \"./proxy\"\nimport { Transaction, getActiveTransaction } from \"./transactions\"\nimport { SortedMap } from \"./SortedMap\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n Fn,\n InsertConfig,\n OperationConfig,\n OptimisticChangeMessage,\n PendingMutation,\n StandardSchema,\n Transaction as TransactionType,\n UtilsRecord,\n} from \"./types\"\n\n// Store collections in memory using Tanstack store\nexport const collectionsStore = new Store(\n new Map<string, CollectionImpl<any>>()\n)\n\n// Map to track loading collections\n\nconst loadingCollections = new Map<\n string,\n Promise<CollectionImpl<Record<string, unknown>>>\n>()\n\ninterface PendingSyncedTransaction<T extends object = Record<string, unknown>> {\n committed: boolean\n operations: Array<OptimisticChangeMessage<T>>\n}\n\n/**\n * Enhanced Collection interface that includes both data type T and utilities TUtils\n * @template T - The type of items in the collection\n * @template TUtils - The utilities record type\n */\nexport interface Collection<\n T extends object = Record<string, unknown>,\n TUtils extends UtilsRecord = {},\n> extends CollectionImpl<T> {\n readonly utils: TUtils\n}\n\n/**\n * Creates a new Collection instance with the given configuration\n *\n * @template T - The type of items in the collection\n * @template TUtils - The utilities record type\n * @param options - Collection options with optional utilities\n * @returns A new Collection with utilities exposed both at top level and under .utils\n */\nexport function createCollection<\n T extends object = Record<string, unknown>,\n TUtils extends UtilsRecord = {},\n>(options: CollectionConfig<T> & { utils?: TUtils }): Collection<T, TUtils> {\n const collection = new CollectionImpl<T>(options)\n\n // Copy utils to both top level and .utils namespace\n if (options.utils) {\n collection.utils = { ...options.utils }\n } else {\n collection.utils = {} as TUtils\n }\n\n return collection as Collection<T, TUtils>\n}\n\n/**\n * Preloads a collection with the given configuration\n * Returns a promise that resolves once the sync tool has done its first commit (initial sync is finished)\n * If the collection has already loaded, it resolves immediately\n *\n * This function is useful in route loaders or similar pre-rendering scenarios where you want\n * to ensure data is available before a route transition completes. It uses the same shared collection\n * instance that will be used by useCollection, ensuring data consistency.\n *\n * @example\n * ```typescript\n * // In a route loader\n * async function loader({ params }) {\n * await preloadCollection({\n * id: `users-${params.userId}`,\n * sync: { ... },\n * });\n *\n * return null;\n * }\n * ```\n *\n * @template T - The type of items in the collection\n * @param config - Configuration for the collection, including id and sync\n * @returns Promise that resolves when the initial sync is finished\n */\nexport function preloadCollection<T extends object = Record<string, unknown>>(\n config: CollectionConfig<T>\n): Promise<CollectionImpl<T>> {\n if (!config.id) {\n throw new Error(`The id property is required for preloadCollection`)\n }\n\n // If the collection is already fully loaded, return a resolved promise\n if (\n collectionsStore.state.has(config.id) &&\n !loadingCollections.has(config.id)\n ) {\n return Promise.resolve(\n collectionsStore.state.get(config.id)! as Collection<T>\n )\n }\n\n // If the collection is in the process of loading, return its promise\n if (loadingCollections.has(config.id)) {\n return loadingCollections.get(config.id)! as Promise<CollectionImpl<T>>\n }\n\n // Create a new collection instance if it doesn't exist\n if (!collectionsStore.state.has(config.id)) {\n collectionsStore.setState((prev) => {\n const next = new Map(prev)\n if (!config.id) {\n throw new Error(`The id property is required for preloadCollection`)\n }\n next.set(\n config.id,\n createCollection<T>({\n id: config.id,\n getId: config.getId,\n sync: config.sync,\n schema: config.schema,\n })\n )\n return next\n })\n }\n\n const collection = collectionsStore.state.get(config.id)! as Collection<T>\n\n // Create a promise that will resolve after the first commit\n let resolveFirstCommit: () => void\n const firstCommitPromise = new Promise<CollectionImpl<T>>((resolve) => {\n resolveFirstCommit = () => {\n resolve(collection as CollectionImpl<T>)\n }\n })\n\n // Register a one-time listener for the first commit\n collection.onFirstCommit(() => {\n if (!config.id) {\n throw new Error(`The id property is required for preloadCollection`)\n }\n if (loadingCollections.has(config.id)) {\n loadingCollections.delete(config.id)\n resolveFirstCommit()\n }\n })\n\n // Store the loading promise\n loadingCollections.set(\n config.id,\n firstCommitPromise as Promise<CollectionImpl<Record<string, unknown>>>\n )\n\n return firstCommitPromise\n}\n\n/**\n * Custom error class for schema validation errors\n */\nexport class SchemaValidationError extends Error {\n type: `insert` | `update`\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>\n\n constructor(\n type: `insert` | `update`,\n issues: ReadonlyArray<{\n message: string\n path?: ReadonlyArray<string | number | symbol>\n }>,\n message?: string\n ) {\n const defaultMessage = `${type === `insert` ? `Insert` : `Update`} validation failed: ${issues\n .map((issue) => issue.message)\n .join(`, `)}`\n\n super(message || defaultMessage)\n this.name = `SchemaValidationError`\n this.type = type\n this.issues = issues\n }\n}\n\nexport class CollectionImpl<T extends object = Record<string, unknown>> {\n /**\n * Utilities namespace\n * This is populated by createCollection\n */\n public utils: Record<string, Fn> = {}\n public transactions: Store<SortedMap<string, TransactionType>>\n public optimisticOperations: Derived<Array<OptimisticChangeMessage<T>>>\n public derivedState: Derived<Map<string, T>>\n public derivedArray: Derived<Array<T>>\n public derivedChanges: Derived<Array<ChangeMessage<T>>>\n public syncedData = new Store<Map<string, T>>(new Map())\n public syncedMetadata = new Store(new Map<string, unknown>())\n private pendingSyncedTransactions: Array<PendingSyncedTransaction<T>> = []\n private syncedKeys = new Set<string>()\n public config: CollectionConfig<T>\n private hasReceivedFirstCommit = false\n\n // Array to store one-time commit listeners\n private onFirstCommitCallbacks: Array<() => void> = []\n\n /**\n * Register a callback to be executed on the next commit\n * Useful for preloading collections\n * @param callback Function to call after the next commit\n */\n public onFirstCommit(callback: () => void): void {\n this.onFirstCommitCallbacks.push(callback)\n }\n\n public id = ``\n\n /**\n * Creates a new Collection instance\n *\n * @param config - Configuration object for the collection\n * @throws Error if sync config is missing\n */\n constructor(config: CollectionConfig<T>) {\n // eslint-disable-next-line\n if (!config) {\n throw new Error(`Collection requires a config`)\n }\n if (config.id) {\n this.id = config.id\n } else {\n this.id = crypto.randomUUID()\n }\n\n // eslint-disable-next-line\n if (!config.sync) {\n throw new Error(`Collection requires a sync config`)\n }\n\n this.transactions = new Store(\n new SortedMap<string, TransactionType>(\n (a, b) => a.createdAt.getTime() - b.createdAt.getTime()\n )\n )\n\n // Copies of live mutations are stored here and removed once the transaction completes.\n this.optimisticOperations = new Derived({\n fn: ({ currDepVals: [transactions] }) => {\n const result = Array.from(transactions.values())\n .map((transaction) => {\n const isActive = ![`completed`, `failed`].includes(\n transaction.state\n )\n return transaction.mutations\n .filter((mutation) => mutation.collection === this)\n .map((mutation) => {\n const message: OptimisticChangeMessage<T> = {\n type: mutation.type,\n key: mutation.key,\n value: mutation.modified as T,\n isActive,\n }\n if (\n mutation.metadata !== undefined &&\n mutation.metadata !== null\n ) {\n message.metadata = mutation.metadata as Record<\n string,\n unknown\n >\n }\n return message\n })\n })\n .flat()\n\n return result\n },\n deps: [this.transactions],\n })\n this.optimisticOperations.mount()\n\n // Combine together synced data & optimistic operations.\n this.derivedState = new Derived({\n fn: ({ currDepVals: [syncedData, operations] }) => {\n const combined = new Map<string, T>(syncedData)\n\n // Apply the optimistic operations on top of the synced state.\n for (const operation of operations) {\n if (operation.isActive) {\n switch (operation.type) {\n case `insert`:\n combined.set(operation.key, operation.value)\n break\n case `update`:\n combined.set(operation.key, operation.value)\n break\n case `delete`:\n combined.delete(operation.key)\n break\n }\n }\n }\n\n return combined\n },\n deps: [this.syncedData, this.optimisticOperations],\n })\n\n // Create a derived array from the map to avoid recalculating it\n this.derivedArray = new Derived({\n fn: ({ currDepVals: [stateMap] }) => {\n // Collections returned by a query that has an orderBy are annotated\n // with the _orderByIndex field.\n // This is used to sort the array when it's derived.\n const array: Array<T & { _orderByIndex?: number }> = Array.from(\n stateMap.values()\n )\n if (array[0] && `_orderByIndex` in array[0]) {\n ;(array as Array<T & { _orderByIndex: number }>).sort((a, b) => {\n if (a._orderByIndex === b._orderByIndex) {\n return 0\n }\n return a._orderByIndex < b._orderByIndex ? -1 : 1\n })\n }\n return array\n },\n deps: [this.derivedState],\n })\n this.derivedArray.mount()\n\n this.derivedChanges = new Derived({\n fn: ({\n currDepVals: [derivedState, optimisticOperations],\n prevDepVals,\n }) => {\n const prevDerivedState = prevDepVals?.[0] ?? new Map<string, T>()\n const prevOptimisticOperations = prevDepVals?.[1] ?? []\n const changedKeys = new Set(this.syncedKeys)\n optimisticOperations\n .flat()\n .filter((op) => op.isActive)\n .forEach((op) => changedKeys.add(op.key))\n prevOptimisticOperations.flat().forEach((op) => {\n changedKeys.add(op.key)\n })\n\n if (changedKeys.size === 0) {\n return []\n }\n\n const changes: Array<ChangeMessage<T>> = []\n for (const key of changedKeys) {\n if (prevDerivedState.has(key) && !derivedState.has(key)) {\n changes.push({\n type: `delete`,\n key,\n value: prevDerivedState.get(key)!,\n })\n } else if (!prevDerivedState.has(key) && derivedState.has(key)) {\n changes.push({ type: `insert`, key, value: derivedState.get(key)! })\n } else if (prevDerivedState.has(key) && derivedState.has(key)) {\n const value = derivedState.get(key)!\n const previousValue = prevDerivedState.get(key)\n if (value !== previousValue) {\n // Comparing objects by reference as records are not mutated\n changes.push({\n type: `update`,\n key,\n value,\n previousValue,\n })\n }\n }\n }\n\n this.syncedKeys.clear()\n\n return changes\n },\n deps: [this.derivedState, this.optimisticOperations],\n })\n this.derivedChanges.mount()\n\n this.config = config\n\n this.derivedState.mount()\n\n // Start the sync process\n config.sync.sync({\n collection: this,\n begin: () => {\n this.pendingSyncedTransactions.push({\n committed: false,\n operations: [],\n })\n },\n write: (messageWithoutKey: Omit<ChangeMessage<T>, `key`>) => {\n const pendingTransaction =\n this.pendingSyncedTransactions[\n this.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new Error(`No pending sync transaction to write to`)\n }\n if (pendingTransaction.committed) {\n throw new Error(\n `The pending sync transaction is already committed, you can't still write to it.`\n )\n }\n const key = this.generateObjectKey(\n this.config.getId(messageWithoutKey.value),\n messageWithoutKey.value\n )\n\n // Check if an item with this ID already exists when inserting\n if (messageWithoutKey.type === `insert`) {\n if (\n this.syncedData.state.has(key) &&\n !pendingTransaction.operations.some(\n (op) => op.key === key && op.type === `delete`\n )\n ) {\n const id = this.config.getId(messageWithoutKey.value)\n throw new Error(\n `Cannot insert document with ID \"${id}\" from sync because it already exists in the collection \"${this.id}\"`\n )\n }\n }\n\n const message: ChangeMessage<T> = {\n ...messageWithoutKey,\n key,\n }\n pendingTransaction.operations.push(message)\n },\n commit: () => {\n const pendingTransaction =\n this.pendingSyncedTransactions[\n this.pendingSyncedTransactions.length - 1\n ]\n if (!pendingTransaction) {\n throw new Error(`No pending sync transaction to commit`)\n }\n if (pendingTransaction.committed) {\n throw new Error(\n `The pending sync transaction is already committed, you can't commit it again.`\n )\n }\n\n pendingTransaction.committed = true\n\n this.commitPendingTransactions()\n },\n })\n }\n\n /**\n * Attempts to commit pending synced transactions if there are no active transactions\n * This method processes operations from pending transactions and applies them to the synced data\n */\n commitPendingTransactions = () => {\n if (\n !Array.from(this.transactions.state.values()).some(\n ({ state }) => state === `persisting`\n )\n ) {\n batch(() => {\n for (const transaction of this.pendingSyncedTransactions) {\n for (const operation of transaction.operations) {\n this.syncedKeys.add(operation.key)\n this.syncedMetadata.setState((prevData) => {\n switch (operation.type) {\n case `insert`:\n prevData.set(operation.key, operation.metadata)\n break\n case `update`:\n prevData.set(operation.key, {\n ...prevData.get(operation.key)!,\n ...operation.metadata,\n })\n break\n case `delete`:\n prevData.delete(operation.key)\n break\n }\n return prevData\n })\n this.syncedData.setState((prevData) => {\n switch (operation.type) {\n case `insert`:\n prevData.set(operation.key, operation.value)\n break\n case `update`:\n prevData.set(operation.key, {\n ...prevData.get(operation.key)!,\n ...operation.value,\n })\n break\n case `delete`:\n prevData.delete(operation.key)\n break\n }\n return prevData\n })\n }\n }\n })\n\n this.pendingSyncedTransactions = []\n\n // Call any registered one-time commit listeners\n if (!this.hasReceivedFirstCommit) {\n this.hasReceivedFirstCommit = true\n const callbacks = [...this.onFirstCommitCallbacks]\n this.onFirstCommitCallbacks = []\n callbacks.forEach((callback) => callback())\n }\n }\n }\n\n private ensureStandardSchema(schema: unknown): StandardSchema<T> {\n // If the schema already implements the standard-schema interface, return it\n if (schema && typeof schema === `object` && `~standard` in schema) {\n return schema as StandardSchema<T>\n }\n\n throw new Error(\n `Schema must either implement the standard-schema interface or be a Zod schema`\n )\n }\n\n private getKeyFromId(id: unknown): string {\n if (typeof id === `undefined`) {\n throw new Error(`id is undefined`)\n }\n if (typeof id === `string` && id.startsWith(`KEY::`)) {\n return id\n } else {\n // if it's not a string, then it's some other\n // primitive type and needs turned into a key.\n return this.generateObjectKey(id, null)\n }\n }\n\n public generateObjectKey(id: any, item: any): string {\n if (typeof id === `undefined`) {\n throw new Error(\n `An object was created without a defined id: ${JSON.stringify(item)}`\n )\n }\n\n return `KEY::${this.id}/${id}`\n }\n\n private validateData(\n data: unknown,\n type: `insert` | `update`,\n key?: string\n ): T | never {\n if (!this.config.schema) return data as T\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 = { ...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 TypeError(`Schema validation must be synchronous`)\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 the original update data, not the merged data\n // We only used the merged data for validation\n return data as T\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 TypeError(`Schema validation must be synchronous`)\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 T\n }\n\n /**\n * Inserts one or more items into the collection\n * @param items - Single item or array of items to insert\n * @param config - Optional configuration including metadata and custom keys\n * @returns A TransactionType object representing the insert operation(s)\n * @throws {SchemaValidationError} If the data fails schema validation\n * @example\n * // Insert a single item\n * insert({ text: \"Buy groceries\", completed: false })\n *\n * // Insert multiple items\n * insert([\n * { text: \"Buy groceries\", completed: false },\n * { text: \"Walk dog\", completed: false }\n * ])\n *\n * // Insert with custom key\n * insert({ text: \"Buy groceries\" }, { key: \"grocery-task\" })\n */\n insert = (data: T | Array<T>, config?: InsertConfig) => {\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 Error(\n `Collection.insert called directly (not within an explicit transaction) but no 'onInsert' handler is configured.`\n )\n }\n\n const items = Array.isArray(data) ? data : [data]\n const mutations: Array<PendingMutation<T>> = []\n\n // Handle keys - convert to array if string, or generate if not provided\n const keys: Array<unknown> = items.map((item) =>\n this.generateObjectKey(this.config.getId(item), item)\n )\n\n // Create mutations for each item\n items.forEach((item, index) => {\n // Validate the data against the schema if one exists\n const validatedData = this.validateData(item, `insert`)\n const key = keys[index]!\n\n // Check if an item with this ID already exists in the collection\n const id = this.config.getId(item)\n if (this.state.has(this.getKeyFromId(id))) {\n throw `Cannot insert document with ID \"${id}\" because it already exists in the collection`\n }\n\n const mutation: PendingMutation<T> = {\n mutationId: crypto.randomUUID(),\n original: {},\n modified: validatedData as Record<string, unknown>,\n changes: validatedData as Record<string, unknown>,\n key,\n metadata: config?.metadata as unknown,\n syncMetadata: this.config.sync.getSyncMetadata?.() || {},\n type: `insert`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\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 this.transactions.setState((sortedMap) => {\n sortedMap.set(ambientTransaction.id, ambientTransaction)\n return sortedMap\n })\n\n return ambientTransaction\n } else {\n // Create a new transaction with a mutation function that calls the onInsert handler\n const directOpTransaction = new Transaction({\n mutationFn: async (params) => {\n // Call the onInsert handler with the transaction\n return this.config.onInsert!(params)\n },\n })\n\n // Apply mutations to the new transaction\n directOpTransaction.applyMutations(mutations)\n directOpTransaction.commit()\n\n // Add the transaction to the collection's transactions store\n this.transactions.setState((sortedMap) => {\n sortedMap.set(directOpTransaction.id, directOpTransaction)\n return sortedMap\n })\n\n return directOpTransaction\n }\n }\n\n /**\n * Updates one or more items in the collection using a callback function\n * @param items - Single item/key or array of items/keys to update\n * @param configOrCallback - Either update configuration or update callback\n * @param maybeCallback - Update callback if config was provided\n * @returns A Transaction object representing the update operation(s)\n * @throws {SchemaValidationError} If the updated data fails schema validation\n * @example\n * // Update a single item\n * update(todo, (draft) => { draft.completed = true })\n *\n * // Update multiple items\n * update([todo1, todo2], (drafts) => {\n * drafts.forEach(draft => { draft.completed = true })\n * })\n *\n * // Update with metadata\n * update(todo, { metadata: { reason: \"user update\" } }, (draft) => { draft.text = \"Updated text\" })\n */\n\n /**\n * Updates one or more items in the collection using a callback function\n * @param ids - Single ID or array of IDs to update\n * @param configOrCallback - Either update configuration or update callback\n * @param maybeCallback - Update callback if config was provided\n * @returns A Transaction object representing the update operation(s)\n * @throws {SchemaValidationError} If the updated data fails schema validation\n * @example\n * // Update a single item\n * update(\"todo-1\", (draft) => { draft.completed = true })\n *\n * // Update multiple items\n * update([\"todo-1\", \"todo-2\"], (drafts) => {\n * drafts.forEach(draft => { draft.completed = true })\n * })\n *\n * // Update with metadata\n * update(\"todo-1\", { metadata: { reason: \"user update\" } }, (draft) => { draft.text = \"Updated text\" })\n */\n update<TItem extends object = T>(\n id: unknown,\n configOrCallback: ((draft: TItem) => void) | OperationConfig,\n maybeCallback?: (draft: TItem) => void\n ): TransactionType\n\n update<TItem extends object = T>(\n ids: Array<unknown>,\n configOrCallback: ((draft: Array<TItem>) => void) | OperationConfig,\n maybeCallback?: (draft: Array<TItem>) => void\n ): TransactionType\n\n update<TItem extends object = T>(\n ids: unknown | Array<unknown>,\n configOrCallback: ((draft: TItem | Array<TItem>) => void) | OperationConfig,\n maybeCallback?: (draft: TItem | Array<TItem>) => void\n ) {\n if (typeof ids === `undefined`) {\n throw new Error(`The first argument to update is missing`)\n }\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 Error(\n `Collection.update called directly (not within an explicit transaction) but no 'onUpdate' handler is configured.`\n )\n }\n\n const isArray = Array.isArray(ids)\n const idsArray = (Array.isArray(ids) ? ids : [ids]).map((id) =>\n this.getKeyFromId(id)\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 = idsArray.map((id) => {\n const item = this.state.get(id)\n if (!item) {\n throw new Error(\n `The id \"${id}\" was passed to update but an object for this ID was not found in the collection`\n )\n }\n\n return item\n }) as unknown as Array<TItem>\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<TItem>) => void\n )\n } else {\n const result = withChangeTracking(\n currentObjects[0] as TItem,\n callback as (draft: TItem) => void\n )\n changesArray = [result]\n }\n\n // Create mutations for each object that has changes\n const mutations: Array<PendingMutation<T>> = idsArray\n .map((id, 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 T\n // Validate the user-provided changes for this item\n const validatedUpdatePayload = this.validateData(\n itemChanges,\n `update`,\n id\n )\n\n // Construct the full modified item by applying the validated update payload to the original item\n const modifiedItem = { ...originalItem, ...validatedUpdatePayload }\n\n // Check if the ID of the item is being changed\n const originalItemId = this.config.getId(originalItem)\n const modifiedItemId = this.config.getId(modifiedItem)\n\n if (originalItemId !== modifiedItemId) {\n throw new Error(\n `Updating the ID of an item is not allowed. Original ID: \"${originalItemId}\", Attempted new ID: \"${modifiedItemId}\". Please delete the old item and create a new one if an ID change is necessary.`\n )\n }\n\n return {\n mutationId: crypto.randomUUID(),\n original: originalItem as Record<string, unknown>,\n modified: modifiedItem as Record<string, unknown>,\n changes: validatedUpdatePayload as Record<string, unknown>,\n key: id,\n metadata: config.metadata as unknown,\n syncMetadata: (this.syncedMetadata.state.get(id) || {}) as Record<\n string,\n unknown\n >,\n type: `update`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\n }\n })\n .filter(Boolean) as Array<PendingMutation<T>>\n\n // If no changes were made, return early\n if (mutations.length === 0) {\n throw new Error(`No changes were made to any of the objects`)\n }\n\n // If an ambient transaction exists, use it\n if (ambientTransaction) {\n ambientTransaction.applyMutations(mutations)\n\n this.transactions.setState((sortedMap) => {\n sortedMap.set(ambientTransaction.id, ambientTransaction)\n return sortedMap\n })\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 = new Transaction({\n mutationFn: async (transaction) => {\n // Call the onUpdate handler with the transaction\n return this.config.onUpdate!(transaction)\n },\n })\n\n // Apply mutations to the new transaction\n directOpTransaction.applyMutations(mutations)\n directOpTransaction.commit()\n\n // Add the transaction to the collection's transactions store\n this.transactions.setState((sortedMap) => {\n sortedMap.set(directOpTransaction.id, directOpTransaction)\n return sortedMap\n })\n\n return directOpTransaction\n }\n\n /**\n * Deletes one or more items from the collection\n * @param ids - Single ID or array of IDs to delete\n * @param config - Optional configuration including metadata\n * @returns A TransactionType object representing the delete operation(s)\n * @example\n * // Delete a single item\n * delete(\"todo-1\")\n *\n * // Delete multiple items\n * delete([\"todo-1\", \"todo-2\"])\n *\n * // Delete with metadata\n * delete(\"todo-1\", { metadata: { reason: \"completed\" } })\n */\n delete = (\n ids: Array<string> | string,\n config?: OperationConfig\n ): TransactionType => {\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 Error(\n `Collection.delete called directly (not within an explicit transaction) but no 'onDelete' handler is configured.`\n )\n }\n\n const idsArray = (Array.isArray(ids) ? ids : [ids]).map((id) =>\n this.getKeyFromId(id)\n )\n const mutations: Array<PendingMutation<T>> = []\n\n for (const id of idsArray) {\n const mutation: PendingMutation<T> = {\n mutationId: crypto.randomUUID(),\n original: (this.state.get(id) || {}) as Record<string, unknown>,\n modified: (this.state.get(id) || {}) as Record<string, unknown>,\n changes: (this.state.get(id) || {}) as Record<string, unknown>,\n key: id,\n metadata: config?.metadata as unknown,\n syncMetadata: (this.syncedMetadata.state.get(id) || {}) as Record<\n string,\n unknown\n >,\n type: `delete`,\n createdAt: new Date(),\n updatedAt: new Date(),\n collection: this,\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 this.transactions.setState((sortedMap) => {\n sortedMap.set(ambientTransaction.id, ambientTransaction)\n return sortedMap\n })\n\n return ambientTransaction\n }\n\n // Create a new transaction with a mutation function that calls the onDelete handler\n const directOpTransaction = new Transaction({\n autoCommit: true,\n mutationFn: async (transaction) => {\n // Call the onDelete handler with the transaction\n return this.config.onDelete!(transaction)\n },\n })\n\n // Apply mutations to the new transaction\n directOpTransaction.applyMutations(mutations)\n directOpTransaction.commit()\n\n // Add the transaction to the collection's transactions store\n this.transactions.setState((sortedMap) => {\n sortedMap.set(directOpTransaction.id, directOpTransaction)\n return sortedMap\n })\n\n return directOpTransaction\n }\n\n /**\n * Gets the current state of the collection as a Map\n *\n * @returns A Map containing all items in the collection, with keys as identifiers\n */\n get state() {\n return this.derivedState.state\n }\n\n /**\n * Gets the current state of the collection as a Map, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to a Map containing all items in the collection\n */\n stateWhenReady(): Promise<Map<string, T>> {\n // If we already have data or there are no loading collections, resolve immediately\n if (this.state.size > 0 || this.hasReceivedFirstCommit === true) {\n return Promise.resolve(this.state)\n }\n\n // Otherwise, wait for the first commit\n return new Promise<Map<string, T>>((resolve) => {\n this.onFirstCommit(() => {\n resolve(this.state)\n })\n })\n }\n\n /**\n * Gets the current state of the collection as an Array\n *\n * @returns An Array containing all items in the collection\n */\n get toArray() {\n return this.derivedArray.state\n }\n\n /**\n * Gets the current state of the collection as an Array, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to an Array containing all items in the collection\n */\n toArrayWhenReady(): Promise<Array<T>> {\n // If we already have data or there are no loading collections, resolve immediately\n if (this.toArray.length > 0 || this.hasReceivedFirstCommit === true) {\n return Promise.resolve(this.toArray)\n }\n\n // Otherwise, wait for the first commit\n return new Promise<Array<T>>((resolve) => {\n this.onFirstCommit(() => {\n resolve(this.toArray)\n })\n })\n }\n\n /**\n * Returns the current state of the collection as an array of changes\n * @returns An array of changes\n */\n public currentStateAsChanges(): Array<ChangeMessage<T>> {\n return [...this.state.entries()].map(([key, value]) => ({\n type: `insert`,\n key,\n value,\n }))\n }\n\n /**\n * Subscribe to changes in the collection\n * @param callback - A function that will be called with the changes in the collection\n * @returns A function that can be called to unsubscribe from the changes\n */\n public subscribeChanges(\n callback: (changes: Array<ChangeMessage<T>>) => void\n ): () => void {\n // First send the current state as changes\n callback(this.currentStateAsChanges())\n\n // Then subscribe to changes, this returns an unsubscribe function\n return this.derivedChanges.subscribe((changes) => {\n if (changes.currentVal.length > 0) {\n callback(changes.currentVal)\n }\n })\n }\n}\n"],"names":["config","result"],"mappings":";;;;AAkBO,MAAM,mBAAmB,IAAI;AAAA,sBAC9B,IAAiC;AACvC;AAIA,MAAM,yCAAyB,IAG7B;AA2BK,SAAS,iBAGd,SAA0E;AACpE,QAAA,aAAa,IAAI,eAAkB,OAAO;AAGhD,MAAI,QAAQ,OAAO;AACjB,eAAW,QAAQ,EAAE,GAAG,QAAQ,MAAM;AAAA,EAAA,OACjC;AACL,eAAW,QAAQ,CAAC;AAAA,EAAA;AAGf,SAAA;AACT;AA4BO,SAAS,kBACd,QAC4B;AACxB,MAAA,CAAC,OAAO,IAAI;AACR,UAAA,IAAI,MAAM,mDAAmD;AAAA,EAAA;AAKnE,MAAA,iBAAiB,MAAM,IAAI,OAAO,EAAE,KACpC,CAAC,mBAAmB,IAAI,OAAO,EAAE,GACjC;AACA,WAAO,QAAQ;AAAA,MACb,iBAAiB,MAAM,IAAI,OAAO,EAAE;AAAA,IACtC;AAAA,EAAA;AAIF,MAAI,mBAAmB,IAAI,OAAO,EAAE,GAAG;AAC9B,WAAA,mBAAmB,IAAI,OAAO,EAAE;AAAA,EAAA;AAIzC,MAAI,CAAC,iBAAiB,MAAM,IAAI,OAAO,EAAE,GAAG;AACzB,qBAAA,SAAS,CAAC,SAAS;AAC5B,YAAA,OAAO,IAAI,IAAI,IAAI;AACrB,UAAA,CAAC,OAAO,IAAI;AACR,cAAA,IAAI,MAAM,mDAAmD;AAAA,MAAA;AAEhE,WAAA;AAAA,QACH,OAAO;AAAA,QACP,iBAAoB;AAAA,UAClB,IAAI,OAAO;AAAA,UACX,OAAO,OAAO;AAAA,UACd,MAAM,OAAO;AAAA,UACb,QAAQ,OAAO;AAAA,QAChB,CAAA;AAAA,MACH;AACO,aAAA;AAAA,IAAA,CACR;AAAA,EAAA;AAGH,QAAM,aAAa,iBAAiB,MAAM,IAAI,OAAO,EAAE;AAGnD,MAAA;AACJ,QAAM,qBAAqB,IAAI,QAA2B,CAAC,YAAY;AACrE,yBAAqB,MAAM;AACzB,cAAQ,UAA+B;AAAA,IACzC;AAAA,EAAA,CACD;AAGD,aAAW,cAAc,MAAM;AACzB,QAAA,CAAC,OAAO,IAAI;AACR,YAAA,IAAI,MAAM,mDAAmD;AAAA,IAAA;AAErE,QAAI,mBAAmB,IAAI,OAAO,EAAE,GAAG;AAClB,yBAAA,OAAO,OAAO,EAAE;AAChB,yBAAA;AAAA,IAAA;AAAA,EACrB,CACD;AAGkB,qBAAA;AAAA,IACjB,OAAO;AAAA,IACP;AAAA,EACF;AAEO,SAAA;AACT;AAKO,MAAM,8BAA8B,MAAM;AAAA,EAO/C,YACE,MACA,QAIA,SACA;AACA,UAAM,iBAAiB,GAAG,SAAS,WAAW,WAAW,QAAQ,uBAAuB,OACrF,IAAI,CAAC,UAAU,MAAM,OAAO,EAC5B,KAAK,IAAI,CAAC;AAEb,UAAM,WAAW,cAAc;AAC/B,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAAA;AAElB;AAEO,MAAM,eAA2D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsCtE,YAAY,QAA6B;AAjCzC,SAAO,QAA4B,CAAC;AAMpC,SAAO,aAAa,IAAI,MAAsB,oBAAI,KAAK;AACvD,SAAO,iBAAiB,IAAI,MAAM,oBAAI,KAAsB;AAC5D,SAAQ,4BAAgE,CAAC;AACjE,SAAA,iCAAiB,IAAY;AAErC,SAAQ,yBAAyB;AAGjC,SAAQ,yBAA4C,CAAC;AAWrD,SAAO,KAAK;AAuPZ,SAAA,4BAA4B,MAAM;AAE9B,UAAA,CAAC,MAAM,KAAK,KAAK,aAAa,MAAM,OAAQ,CAAA,EAAE;AAAA,QAC5C,CAAC,EAAE,MAAM,MAAM,UAAU;AAAA,MAAA,GAE3B;AACA,cAAM,MAAM;AACC,qBAAA,eAAe,KAAK,2BAA2B;AAC7C,uBAAA,aAAa,YAAY,YAAY;AACzC,mBAAA,WAAW,IAAI,UAAU,GAAG;AAC5B,mBAAA,eAAe,SAAS,CAAC,aAAa;AACzC,wBAAQ,UAAU,MAAM;AAAA,kBACtB,KAAK;AACH,6BAAS,IAAI,UAAU,KAAK,UAAU,QAAQ;AAC9C;AAAA,kBACF,KAAK;AACM,6BAAA,IAAI,UAAU,KAAK;AAAA,sBAC1B,GAAG,SAAS,IAAI,UAAU,GAAG;AAAA,sBAC7B,GAAG,UAAU;AAAA,oBAAA,CACd;AACD;AAAA,kBACF,KAAK;AACM,6BAAA,OAAO,UAAU,GAAG;AAC7B;AAAA,gBAAA;AAEG,uBAAA;AAAA,cAAA,CACR;AACI,mBAAA,WAAW,SAAS,CAAC,aAAa;AACrC,wBAAQ,UAAU,MAAM;AAAA,kBACtB,KAAK;AACH,6BAAS,IAAI,UAAU,KAAK,UAAU,KAAK;AAC3C;AAAA,kBACF,KAAK;AACM,6BAAA,IAAI,UAAU,KAAK;AAAA,sBAC1B,GAAG,SAAS,IAAI,UAAU,GAAG;AAAA,sBAC7B,GAAG,UAAU;AAAA,oBAAA,CACd;AACD;AAAA,kBACF,KAAK;AACM,6BAAA,OAAO,UAAU,GAAG;AAC7B;AAAA,gBAAA;AAEG,uBAAA;AAAA,cAAA,CACR;AAAA,YAAA;AAAA,UACH;AAAA,QACF,CACD;AAED,aAAK,4BAA4B,CAAC;AAG9B,YAAA,CAAC,KAAK,wBAAwB;AAChC,eAAK,yBAAyB;AAC9B,gBAAM,YAAY,CAAC,GAAG,KAAK,sBAAsB;AACjD,eAAK,yBAAyB,CAAC;AAC/B,oBAAU,QAAQ,CAAC,aAAa,SAAA,CAAU;AAAA,QAAA;AAAA,MAC5C;AAAA,IAEJ;AAyHS,SAAA,SAAA,CAAC,MAAoBA,YAA0B;AACtD,YAAM,qBAAqB,qBAAqB;AAGhD,UAAI,CAAC,sBAAsB,CAAC,KAAK,OAAO,UAAU;AAChD,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MAAA;AAGF,YAAM,QAAQ,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI;AAChD,YAAM,YAAuC,CAAC;AAG9C,YAAM,OAAuB,MAAM;AAAA,QAAI,CAAC,SACtC,KAAK,kBAAkB,KAAK,OAAO,MAAM,IAAI,GAAG,IAAI;AAAA,MACtD;AAGM,YAAA,QAAQ,CAAC,MAAM,UAAU;;AAE7B,cAAM,gBAAgB,KAAK,aAAa,MAAM,QAAQ;AAChD,cAAA,MAAM,KAAK,KAAK;AAGtB,cAAM,KAAK,KAAK,OAAO,MAAM,IAAI;AACjC,YAAI,KAAK,MAAM,IAAI,KAAK,aAAa,EAAE,CAAC,GAAG;AACzC,gBAAM,mCAAmC,EAAE;AAAA,QAAA;AAG7C,cAAM,WAA+B;AAAA,UACnC,YAAY,OAAO,WAAW;AAAA,UAC9B,UAAU,CAAC;AAAA,UACX,UAAU;AAAA,UACV,SAAS;AAAA,UACT;AAAA,UACA,UAAUA,WAAA,gBAAAA,QAAQ;AAAA,UAClB,gBAAc,gBAAK,OAAO,MAAK,oBAAjB,gCAAwC,CAAC;AAAA,UACvD,MAAM;AAAA,UACN,+BAAe,KAAK;AAAA,UACpB,+BAAe,KAAK;AAAA,UACpB,YAAY;AAAA,QACd;AAEA,kBAAU,KAAK,QAAQ;AAAA,MAAA,CACxB;AAGD,UAAI,oBAAoB;AACtB,2BAAmB,eAAe,SAAS;AAEtC,aAAA,aAAa,SAAS,CAAC,cAAc;AAC9B,oBAAA,IAAI,mBAAmB,IAAI,kBAAkB;AAChD,iBAAA;AAAA,QAAA,CACR;AAEM,eAAA;AAAA,MAAA,OACF;AAEC,cAAA,sBAAsB,IAAI,YAAY;AAAA,UAC1C,YAAY,OAAO,WAAW;AAErB,mBAAA,KAAK,OAAO,SAAU,MAAM;AAAA,UAAA;AAAA,QACrC,CACD;AAGD,4BAAoB,eAAe,SAAS;AAC5C,4BAAoB,OAAO;AAGtB,aAAA,aAAa,SAAS,CAAC,cAAc;AAC9B,oBAAA,IAAI,oBAAoB,IAAI,mBAAmB;AAClD,iBAAA;AAAA,QAAA,CACR;AAEM,eAAA;AAAA,MAAA;AAAA,IAEX;AAoNS,SAAA,SAAA,CACP,KACAA,YACoB;AACpB,YAAM,qBAAqB,qBAAqB;AAGhD,UAAI,CAAC,sBAAsB,CAAC,KAAK,OAAO,UAAU;AAChD,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MAAA;AAGI,YAAA,YAAY,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,GAAG,GAAG;AAAA,QAAI,CAAC,OACvD,KAAK,aAAa,EAAE;AAAA,MACtB;AACA,YAAM,YAAuC,CAAC;AAE9C,iBAAW,MAAM,UAAU;AACzB,cAAM,WAA+B;AAAA,UACnC,YAAY,OAAO,WAAW;AAAA,UAC9B,UAAW,KAAK,MAAM,IAAI,EAAE,KAAK,CAAC;AAAA,UAClC,UAAW,KAAK,MAAM,IAAI,EAAE,KAAK,CAAC;AAAA,UAClC,SAAU,KAAK,MAAM,IAAI,EAAE,KAAK,CAAC;AAAA,UACjC,KAAK;AAAA,UACL,UAAUA,WAAA,gBAAAA,QAAQ;AAAA,UAClB,cAAe,KAAK,eAAe,MAAM,IAAI,EAAE,KAAK,CAAC;AAAA,UAIrD,MAAM;AAAA,UACN,+BAAe,KAAK;AAAA,UACpB,+BAAe,KAAK;AAAA,UACpB,YAAY;AAAA,QACd;AAEA,kBAAU,KAAK,QAAQ;AAAA,MAAA;AAIzB,UAAI,oBAAoB;AACtB,2BAAmB,eAAe,SAAS;AAEtC,aAAA,aAAa,SAAS,CAAC,cAAc;AAC9B,oBAAA,IAAI,mBAAmB,IAAI,kBAAkB;AAChD,iBAAA;AAAA,QAAA,CACR;AAEM,eAAA;AAAA,MAAA;AAIH,YAAA,sBAAsB,IAAI,YAAY;AAAA,QAC1C,YAAY;AAAA,QACZ,YAAY,OAAO,gBAAgB;AAE1B,iBAAA,KAAK,OAAO,SAAU,WAAW;AAAA,QAAA;AAAA,MAC1C,CACD;AAGD,0BAAoB,eAAe,SAAS;AAC5C,0BAAoB,OAAO;AAGtB,WAAA,aAAa,SAAS,CAAC,cAAc;AAC9B,kBAAA,IAAI,oBAAoB,IAAI,mBAAmB;AAClD,eAAA;AAAA,MAAA,CACR;AAEM,aAAA;AAAA,IACT;AAzwBE,QAAI,CAAC,QAAQ;AACL,YAAA,IAAI,MAAM,8BAA8B;AAAA,IAAA;AAEhD,QAAI,OAAO,IAAI;AACb,WAAK,KAAK,OAAO;AAAA,IAAA,OACZ;AACA,WAAA,KAAK,OAAO,WAAW;AAAA,IAAA;AAI1B,QAAA,CAAC,OAAO,MAAM;AACV,YAAA,IAAI,MAAM,mCAAmC;AAAA,IAAA;AAGrD,SAAK,eAAe,IAAI;AAAA,MACtB,IAAI;AAAA,QACF,CAAC,GAAG,MAAM,EAAE,UAAU,YAAY,EAAE,UAAU,QAAQ;AAAA,MAAA;AAAA,IAE1D;AAGK,SAAA,uBAAuB,IAAI,QAAQ;AAAA,MACtC,IAAI,CAAC,EAAE,aAAa,CAAC,YAAY,QAAQ;AACjC,cAAA,SAAS,MAAM,KAAK,aAAa,QAAQ,EAC5C,IAAI,CAAC,gBAAgB;AACpB,gBAAM,WAAW,CAAC,CAAC,aAAa,QAAQ,EAAE;AAAA,YACxC,YAAY;AAAA,UACd;AACO,iBAAA,YAAY,UAChB,OAAO,CAAC,aAAa,SAAS,eAAe,IAAI,EACjD,IAAI,CAAC,aAAa;AACjB,kBAAM,UAAsC;AAAA,cAC1C,MAAM,SAAS;AAAA,cACf,KAAK,SAAS;AAAA,cACd,OAAO,SAAS;AAAA,cAChB;AAAA,YACF;AACA,gBACE,SAAS,aAAa,UACtB,SAAS,aAAa,MACtB;AACA,sBAAQ,WAAW,SAAS;AAAA,YAAA;AAKvB,mBAAA;AAAA,UAAA,CACR;AAAA,QACJ,CAAA,EACA,KAAK;AAED,eAAA;AAAA,MACT;AAAA,MACA,MAAM,CAAC,KAAK,YAAY;AAAA,IAAA,CACzB;AACD,SAAK,qBAAqB,MAAM;AAG3B,SAAA,eAAe,IAAI,QAAQ;AAAA,MAC9B,IAAI,CAAC,EAAE,aAAa,CAAC,YAAY,UAAU,QAAQ;AAC3C,cAAA,WAAW,IAAI,IAAe,UAAU;AAG9C,mBAAW,aAAa,YAAY;AAClC,cAAI,UAAU,UAAU;AACtB,oBAAQ,UAAU,MAAM;AAAA,cACtB,KAAK;AACH,yBAAS,IAAI,UAAU,KAAK,UAAU,KAAK;AAC3C;AAAA,cACF,KAAK;AACH,yBAAS,IAAI,UAAU,KAAK,UAAU,KAAK;AAC3C;AAAA,cACF,KAAK;AACM,yBAAA,OAAO,UAAU,GAAG;AAC7B;AAAA,YAAA;AAAA,UACJ;AAAA,QACF;AAGK,eAAA;AAAA,MACT;AAAA,MACA,MAAM,CAAC,KAAK,YAAY,KAAK,oBAAoB;AAAA,IAAA,CAClD;AAGI,SAAA,eAAe,IAAI,QAAQ;AAAA,MAC9B,IAAI,CAAC,EAAE,aAAa,CAAC,QAAQ,QAAQ;AAInC,cAAM,QAA+C,MAAM;AAAA,UACzD,SAAS,OAAO;AAAA,QAClB;AACA,YAAI,MAAM,CAAC,KAAK,mBAAmB,MAAM,CAAC,GAAG;AACzC,gBAA+C,KAAK,CAAC,GAAG,MAAM;AAC1D,gBAAA,EAAE,kBAAkB,EAAE,eAAe;AAChC,qBAAA;AAAA,YAAA;AAET,mBAAO,EAAE,gBAAgB,EAAE,gBAAgB,KAAK;AAAA,UAAA,CACjD;AAAA,QAAA;AAEI,eAAA;AAAA,MACT;AAAA,MACA,MAAM,CAAC,KAAK,YAAY;AAAA,IAAA,CACzB;AACD,SAAK,aAAa,MAAM;AAEnB,SAAA,iBAAiB,IAAI,QAAQ;AAAA,MAChC,IAAI,CAAC;AAAA,QACH,aAAa,CAAC,cAAc,oBAAoB;AAAA,QAChD;AAAA,MAAA,MACI;AACJ,cAAM,oBAAmB,2CAAc,2BAAU,IAAe;AAChE,cAAM,4BAA2B,2CAAc,OAAM,CAAC;AACtD,cAAM,cAAc,IAAI,IAAI,KAAK,UAAU;AAC3C,6BACG,KAAK,EACL,OAAO,CAAC,OAAO,GAAG,QAAQ,EAC1B,QAAQ,CAAC,OAAO,YAAY,IAAI,GAAG,GAAG,CAAC;AAC1C,iCAAyB,KAAK,EAAE,QAAQ,CAAC,OAAO;AAClC,sBAAA,IAAI,GAAG,GAAG;AAAA,QAAA,CACvB;AAEG,YAAA,YAAY,SAAS,GAAG;AAC1B,iBAAO,CAAC;AAAA,QAAA;AAGV,cAAM,UAAmC,CAAC;AAC1C,mBAAW,OAAO,aAAa;AACzB,cAAA,iBAAiB,IAAI,GAAG,KAAK,CAAC,aAAa,IAAI,GAAG,GAAG;AACvD,oBAAQ,KAAK;AAAA,cACX,MAAM;AAAA,cACN;AAAA,cACA,OAAO,iBAAiB,IAAI,GAAG;AAAA,YAAA,CAChC;AAAA,UAAA,WACQ,CAAC,iBAAiB,IAAI,GAAG,KAAK,aAAa,IAAI,GAAG,GAAG;AACtD,oBAAA,KAAK,EAAE,MAAM,UAAU,KAAK,OAAO,aAAa,IAAI,GAAG,EAAA,CAAI;AAAA,UAAA,WAC1D,iBAAiB,IAAI,GAAG,KAAK,aAAa,IAAI,GAAG,GAAG;AACvD,kBAAA,QAAQ,aAAa,IAAI,GAAG;AAC5B,kBAAA,gBAAgB,iBAAiB,IAAI,GAAG;AAC9C,gBAAI,UAAU,eAAe;AAE3B,sBAAQ,KAAK;AAAA,gBACX,MAAM;AAAA,gBACN;AAAA,gBACA;AAAA,gBACA;AAAA,cAAA,CACD;AAAA,YAAA;AAAA,UACH;AAAA,QACF;AAGF,aAAK,WAAW,MAAM;AAEf,eAAA;AAAA,MACT;AAAA,MACA,MAAM,CAAC,KAAK,cAAc,KAAK,oBAAoB;AAAA,IAAA,CACpD;AACD,SAAK,eAAe,MAAM;AAE1B,SAAK,SAAS;AAEd,SAAK,aAAa,MAAM;AAGxB,WAAO,KAAK,KAAK;AAAA,MACf,YAAY;AAAA,MACZ,OAAO,MAAM;AACX,aAAK,0BAA0B,KAAK;AAAA,UAClC,WAAW;AAAA,UACX,YAAY,CAAA;AAAA,QAAC,CACd;AAAA,MACH;AAAA,MACA,OAAO,CAAC,sBAAqD;AAC3D,cAAM,qBACJ,KAAK,0BACH,KAAK,0BAA0B,SAAS,CAC1C;AACF,YAAI,CAAC,oBAAoB;AACjB,gBAAA,IAAI,MAAM,yCAAyC;AAAA,QAAA;AAE3D,YAAI,mBAAmB,WAAW;AAChC,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QAAA;AAEF,cAAM,MAAM,KAAK;AAAA,UACf,KAAK,OAAO,MAAM,kBAAkB,KAAK;AAAA,UACzC,kBAAkB;AAAA,QACpB;AAGI,YAAA,kBAAkB,SAAS,UAAU;AAErC,cAAA,KAAK,WAAW,MAAM,IAAI,GAAG,KAC7B,CAAC,mBAAmB,WAAW;AAAA,YAC7B,CAAC,OAAO,GAAG,QAAQ,OAAO,GAAG,SAAS;AAAA,UAAA,GAExC;AACA,kBAAM,KAAK,KAAK,OAAO,MAAM,kBAAkB,KAAK;AACpD,kBAAM,IAAI;AAAA,cACR,mCAAmC,EAAE,4DAA4D,KAAK,EAAE;AAAA,YAC1G;AAAA,UAAA;AAAA,QACF;AAGF,cAAM,UAA4B;AAAA,UAChC,GAAG;AAAA,UACH;AAAA,QACF;AACmB,2BAAA,WAAW,KAAK,OAAO;AAAA,MAC5C;AAAA,MACA,QAAQ,MAAM;AACZ,cAAM,qBACJ,KAAK,0BACH,KAAK,0BAA0B,SAAS,CAC1C;AACF,YAAI,CAAC,oBAAoB;AACjB,gBAAA,IAAI,MAAM,uCAAuC;AAAA,QAAA;AAEzD,YAAI,mBAAmB,WAAW;AAChC,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QAAA;AAGF,2BAAmB,YAAY;AAE/B,aAAK,0BAA0B;AAAA,MAAA;AAAA,IACjC,CACD;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EApPI,cAAc,UAA4B;AAC1C,SAAA,uBAAuB,KAAK,QAAQ;AAAA,EAAA;AAAA,EAsTnC,qBAAqB,QAAoC;AAE/D,QAAI,UAAU,OAAO,WAAW,YAAY,eAAe,QAAQ;AAC1D,aAAA;AAAA,IAAA;AAGT,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EAAA;AAAA,EAGM,aAAa,IAAqB;AACpC,QAAA,OAAO,OAAO,aAAa;AACvB,YAAA,IAAI,MAAM,iBAAiB;AAAA,IAAA;AAEnC,QAAI,OAAO,OAAO,YAAY,GAAG,WAAW,OAAO,GAAG;AAC7C,aAAA;AAAA,IAAA,OACF;AAGE,aAAA,KAAK,kBAAkB,IAAI,IAAI;AAAA,IAAA;AAAA,EACxC;AAAA,EAGK,kBAAkB,IAAS,MAAmB;AAC/C,QAAA,OAAO,OAAO,aAAa;AAC7B,YAAM,IAAI;AAAA,QACR,+CAA+C,KAAK,UAAU,IAAI,CAAC;AAAA,MACrE;AAAA,IAAA;AAGF,WAAO,QAAQ,KAAK,EAAE,IAAI,EAAE;AAAA,EAAA;AAAA,EAGtB,aACN,MACA,MACA,KACW;AACX,QAAI,CAAC,KAAK,OAAO,OAAe,QAAA;AAEhC,UAAM,iBAAiB,KAAK,qBAAqB,KAAK,OAAO,MAAM;AAG/D,QAAA,SAAS,YAAY,KAAK;AAE5B,YAAM,eAAe,KAAK,MAAM,IAAI,GAAG;AAEvC,UACE,gBACA,QACA,OAAO,SAAS,YAChB,OAAO,iBAAiB,UACxB;AAEA,cAAM,aAAa,EAAE,GAAG,cAAc,GAAG,KAAK;AAG9C,cAAMC,UAAS,eAAe,WAAW,EAAE,SAAS,UAAU;AAG9D,YAAIA,mBAAkB,SAAS;AACvB,gBAAA,IAAI,UAAU,uCAAuC;AAAA,QAAA;AAIzD,YAAA,YAAYA,WAAUA,QAAO,QAAQ;AACvC,gBAAM,cAAcA,QAAO,OAAO,IAAI,CAAC,UAAW;;AAAA;AAAA,cAChD,SAAS,MAAM;AAAA,cACf,OAAM,WAAM,SAAN,mBAAY,IAAI,CAAC,MAAM,OAAO,CAAC;AAAA,YAAC;AAAA,WACtC;AACI,gBAAA,IAAI,sBAAsB,MAAM,WAAW;AAAA,QAAA;AAK5C,eAAA;AAAA,MAAA;AAAA,IACT;AAIF,UAAM,SAAS,eAAe,WAAW,EAAE,SAAS,IAAI;AAGxD,QAAI,kBAAkB,SAAS;AACvB,YAAA,IAAI,UAAU,uCAAuC;AAAA,IAAA;AAIzD,QAAA,YAAY,UAAU,OAAO,QAAQ;AACvC,YAAM,cAAc,OAAO,OAAO,IAAI,CAAC,UAAW;;AAAA;AAAA,UAChD,SAAS,MAAM;AAAA,UACf,OAAM,WAAM,SAAN,mBAAY,IAAI,CAAC,MAAM,OAAO,CAAC;AAAA,QAAC;AAAA,OACtC;AACI,YAAA,IAAI,sBAAsB,MAAM,WAAW;AAAA,IAAA;AAGnD,WAAO,OAAO;AAAA,EAAA;AAAA,EAyJhB,OACE,KACA,kBACA,eACA;AACI,QAAA,OAAO,QAAQ,aAAa;AACxB,YAAA,IAAI,MAAM,yCAAyC;AAAA,IAAA;AAG3D,UAAM,qBAAqB,qBAAqB;AAGhD,QAAI,CAAC,sBAAsB,CAAC,KAAK,OAAO,UAAU;AAChD,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IAAA;AAGI,UAAA,UAAU,MAAM,QAAQ,GAAG;AAC3B,UAAA,YAAY,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,GAAG,GAAG;AAAA,MAAI,CAAC,OACvD,KAAK,aAAa,EAAE;AAAA,IACtB;AACA,UAAM,WACJ,OAAO,qBAAqB,aAAa,mBAAmB;AAC9D,UAAM,SACJ,OAAO,qBAAqB,aAAa,CAAK,IAAA;AAGhD,UAAM,iBAAiB,SAAS,IAAI,CAAC,OAAO;AAC1C,YAAM,OAAO,KAAK,MAAM,IAAI,EAAE;AAC9B,UAAI,CAAC,MAAM;AACT,cAAM,IAAI;AAAA,UACR,WAAW,EAAE;AAAA,QACf;AAAA,MAAA;AAGK,aAAA;AAAA,IAAA,CACR;AAEG,QAAA;AACJ,QAAI,SAAS;AAEI,qBAAA;AAAA,QACb;AAAA,QACA;AAAA,MACF;AAAA,IAAA,OACK;AACL,YAAM,SAAS;AAAA,QACb,eAAe,CAAC;AAAA,QAChB;AAAA,MACF;AACA,qBAAe,CAAC,MAAM;AAAA,IAAA;AAIxB,UAAM,YAAuC,SAC1C,IAAI,CAAC,IAAI,UAAU;AACZ,YAAA,cAAc,aAAa,KAAK;AAGtC,UAAI,CAAC,eAAe,OAAO,KAAK,WAAW,EAAE,WAAW,GAAG;AAClD,eAAA;AAAA,MAAA;AAGH,YAAA,eAAe,eAAe,KAAK;AAEzC,YAAM,yBAAyB,KAAK;AAAA,QAClC;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAGA,YAAM,eAAe,EAAE,GAAG,cAAc,GAAG,uBAAuB;AAGlE,YAAM,iBAAiB,KAAK,OAAO,MAAM,YAAY;AACrD,YAAM,iBAAiB,KAAK,OAAO,MAAM,YAAY;AAErD,UAAI,mBAAmB,gBAAgB;AACrC,cAAM,IAAI;AAAA,UACR,4DAA4D,cAAc,yBAAyB,cAAc;AAAA,QACnH;AAAA,MAAA;AAGK,aAAA;AAAA,QACL,YAAY,OAAO,WAAW;AAAA,QAC9B,UAAU;AAAA,QACV,UAAU;AAAA,QACV,SAAS;AAAA,QACT,KAAK;AAAA,QACL,UAAU,OAAO;AAAA,QACjB,cAAe,KAAK,eAAe,MAAM,IAAI,EAAE,KAAK,CAAC;AAAA,QAIrD,MAAM;AAAA,QACN,+BAAe,KAAK;AAAA,QACpB,+BAAe,KAAK;AAAA,QACpB,YAAY;AAAA,MACd;AAAA,IAAA,CACD,EACA,OAAO,OAAO;AAGb,QAAA,UAAU,WAAW,GAAG;AACpB,YAAA,IAAI,MAAM,4CAA4C;AAAA,IAAA;AAI9D,QAAI,oBAAoB;AACtB,yBAAmB,eAAe,SAAS;AAEtC,WAAA,aAAa,SAAS,CAAC,cAAc;AAC9B,kBAAA,IAAI,mBAAmB,IAAI,kBAAkB;AAChD,eAAA;AAAA,MAAA,CACR;AAEM,aAAA;AAAA,IAAA;AAMH,UAAA,sBAAsB,IAAI,YAAY;AAAA,MAC1C,YAAY,OAAO,gBAAgB;AAE1B,eAAA,KAAK,OAAO,SAAU,WAAW;AAAA,MAAA;AAAA,IAC1C,CACD;AAGD,wBAAoB,eAAe,SAAS;AAC5C,wBAAoB,OAAO;AAGtB,SAAA,aAAa,SAAS,CAAC,cAAc;AAC9B,gBAAA,IAAI,oBAAoB,IAAI,mBAAmB;AAClD,aAAA;AAAA,IAAA,CACR;AAEM,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgGT,IAAI,QAAQ;AACV,WAAO,KAAK,aAAa;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS3B,iBAA0C;AAExC,QAAI,KAAK,MAAM,OAAO,KAAK,KAAK,2BAA2B,MAAM;AACxD,aAAA,QAAQ,QAAQ,KAAK,KAAK;AAAA,IAAA;AAI5B,WAAA,IAAI,QAAwB,CAAC,YAAY;AAC9C,WAAK,cAAc,MAAM;AACvB,gBAAQ,KAAK,KAAK;AAAA,MAAA,CACnB;AAAA,IAAA,CACF;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQH,IAAI,UAAU;AACZ,WAAO,KAAK,aAAa;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS3B,mBAAsC;AAEpC,QAAI,KAAK,QAAQ,SAAS,KAAK,KAAK,2BAA2B,MAAM;AAC5D,aAAA,QAAQ,QAAQ,KAAK,OAAO;AAAA,IAAA;AAI9B,WAAA,IAAI,QAAkB,CAAC,YAAY;AACxC,WAAK,cAAc,MAAM;AACvB,gBAAQ,KAAK,OAAO;AAAA,MAAA,CACrB;AAAA,IAAA,CACF;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOI,wBAAiD;AACtD,WAAO,CAAC,GAAG,KAAK,MAAM,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO;AAAA,MACtD,MAAM;AAAA,MACN;AAAA,MACA;AAAA,IAAA,EACA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQG,iBACL,UACY;AAEH,aAAA,KAAK,uBAAuB;AAGrC,WAAO,KAAK,eAAe,UAAU,CAAC,YAAY;AAC5C,UAAA,QAAQ,WAAW,SAAS,GAAG;AACjC,iBAAS,QAAQ,UAAU;AAAA,MAAA;AAAA,IAC7B,CACD;AAAA,EAAA;AAEL;"}
|
package/dist/esm/index.d.ts
CHANGED
package/dist/esm/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CollectionImpl, SchemaValidationError, collectionsStore, createCollection, preloadCollection } from "./collection.js";
|
|
2
2
|
import { SortedMap } from "./SortedMap.js";
|
|
3
3
|
import { Transaction, createTransaction, getActiveTransaction } from "./transactions.js";
|
|
4
4
|
import { NonRetriableError } from "./errors.js";
|
|
@@ -9,7 +9,7 @@ import { CompiledQuery, compileQuery } from "./query/compiled-query.js";
|
|
|
9
9
|
import { compileQueryPipeline } from "./query/pipeline-compiler.js";
|
|
10
10
|
export {
|
|
11
11
|
BaseQueryBuilder,
|
|
12
|
-
|
|
12
|
+
CollectionImpl,
|
|
13
13
|
CompiledQuery,
|
|
14
14
|
NonRetriableError,
|
|
15
15
|
SchemaValidationError,
|
|
@@ -13,7 +13,7 @@ export declare class CompiledQuery<TResults extends object = Record<string, unkn
|
|
|
13
13
|
private version;
|
|
14
14
|
private unsubscribeEffect?;
|
|
15
15
|
constructor(queryBuilder: QueryBuilder<Context<Schema>>);
|
|
16
|
-
get results(): Collection<TResults>;
|
|
16
|
+
get results(): Collection<TResults, {}>;
|
|
17
17
|
private sendChangesToInput;
|
|
18
18
|
private sendFrontierToInput;
|
|
19
19
|
private sendFrontierToAllInputs;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { D2, MultiSet, output, MessageType } from "@electric-sql/d2ts";
|
|
2
2
|
import { batch, Effect } from "@tanstack/store";
|
|
3
|
-
import {
|
|
3
|
+
import { createCollection } from "../collection.js";
|
|
4
4
|
import { compileQueryPipeline } from "./pipeline-compiler.js";
|
|
5
5
|
function compileQuery(queryBuilder) {
|
|
6
6
|
return new CompiledQuery(queryBuilder);
|
|
@@ -69,7 +69,7 @@ class CompiledQuery {
|
|
|
69
69
|
};
|
|
70
70
|
this.graph = graph;
|
|
71
71
|
this.inputs = inputs;
|
|
72
|
-
this.resultCollection =
|
|
72
|
+
this.resultCollection = createCollection({
|
|
73
73
|
id: crypto.randomUUID(),
|
|
74
74
|
// TODO: remove when we don't require any more
|
|
75
75
|
getId: (val) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compiled-query.js","sources":["../../../src/query/compiled-query.ts"],"sourcesContent":["import { D2, MessageType, MultiSet, output } from \"@electric-sql/d2ts\"\nimport { Effect, batch } from \"@tanstack/store\"\nimport {
|
|
1
|
+
{"version":3,"file":"compiled-query.js","sources":["../../../src/query/compiled-query.ts"],"sourcesContent":["import { D2, MessageType, MultiSet, output } from \"@electric-sql/d2ts\"\nimport { Effect, batch } from \"@tanstack/store\"\nimport { createCollection } from \"../collection.js\"\nimport { compileQueryPipeline } from \"./pipeline-compiler.js\"\nimport type { Collection } from \"../collection.js\"\nimport type { ChangeMessage, SyncConfig } from \"../types.js\"\nimport type {\n IStreamBuilder,\n MultiSetArray,\n RootStreamBuilder,\n} from \"@electric-sql/d2ts\"\nimport type { QueryBuilder, ResultsFromContext } from \"./query-builder.js\"\nimport type { Context, Schema } from \"./types.js\"\n\nexport function compileQuery<TContext extends Context<Schema>>(\n queryBuilder: QueryBuilder<TContext>\n) {\n return new CompiledQuery<\n ResultsFromContext<TContext> & { _key?: string | number }\n >(queryBuilder)\n}\n\nexport class CompiledQuery<TResults extends object = Record<string, unknown>> {\n private graph: D2\n private inputs: Record<string, RootStreamBuilder<any>>\n private inputCollections: Record<string, Collection<any>>\n private resultCollection: Collection<TResults>\n public state: `compiled` | `running` | `stopped` = `compiled`\n private version = 0\n private unsubscribeEffect?: () => void\n\n constructor(queryBuilder: QueryBuilder<Context<Schema>>) {\n const query = queryBuilder._query\n const collections = query.collections\n\n if (!collections) {\n throw new Error(`No collections provided`)\n }\n\n this.inputCollections = collections\n\n const graph = new D2({ initialFrontier: this.version })\n const inputs = Object.fromEntries(\n Object.entries(collections).map(([key]) => [key, graph.newInput<any>()])\n )\n\n const sync: SyncConfig<TResults>[`sync`] = ({ begin, write, commit }) => {\n compileQueryPipeline<IStreamBuilder<[unknown, TResults]>>(\n query,\n inputs\n ).pipe(\n output(({ type, data }) => {\n if (type === MessageType.DATA) {\n begin()\n data.collection\n .getInner()\n .reduce((acc, [[key, value], multiplicity]) => {\n const changes = acc.get(key) || {\n deletes: 0,\n inserts: 0,\n value,\n }\n if (multiplicity < 0) {\n changes.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n changes.inserts += multiplicity\n changes.value = value\n }\n acc.set(key, changes)\n return acc\n }, new Map<unknown, { deletes: number; inserts: number; value: TResults }>())\n .forEach((changes, rawKey) => {\n const { deletes, inserts, value } = changes\n const valueWithKey = { ...value, _key: rawKey }\n if (inserts && !deletes) {\n write({\n value: valueWithKey,\n type: `insert`,\n })\n } else if (inserts >= deletes) {\n write({\n value: valueWithKey,\n type: `update`,\n })\n } else if (deletes > 0) {\n write({\n value: valueWithKey,\n type: `delete`,\n })\n }\n })\n commit()\n }\n })\n )\n graph.finalize()\n }\n\n this.graph = graph\n this.inputs = inputs\n this.resultCollection = createCollection<TResults>({\n id: crypto.randomUUID(), // TODO: remove when we don't require any more\n getId: (val: unknown) => {\n return (val as any)._key\n },\n sync: {\n sync,\n },\n })\n }\n\n get results() {\n return this.resultCollection\n }\n\n private sendChangesToInput(\n inputKey: string,\n changes: Array<ChangeMessage>,\n getId: (item: ChangeMessage[`value`]) => any\n ) {\n const input = this.inputs[inputKey]!\n const multiSetArray: MultiSetArray<unknown> = []\n for (const change of changes) {\n const key = getId(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 input.sendData(this.version, new MultiSet(multiSetArray))\n }\n\n private sendFrontierToInput(inputKey: string) {\n const input = this.inputs[inputKey]!\n input.sendFrontier(this.version)\n }\n\n private sendFrontierToAllInputs() {\n Object.entries(this.inputs).forEach(([key]) => {\n this.sendFrontierToInput(key)\n })\n }\n\n private incrementVersion() {\n this.version++\n }\n\n private runGraph() {\n this.graph.run()\n }\n\n start() {\n if (this.state === `running`) {\n throw new Error(`Query is already running`)\n } else if (this.state === `stopped`) {\n throw new Error(`Query is stopped`)\n }\n\n batch(() => {\n Object.entries(this.inputCollections).forEach(([key, collection]) => {\n this.sendChangesToInput(\n key,\n collection.currentStateAsChanges(),\n collection.config.getId\n )\n })\n this.incrementVersion()\n this.sendFrontierToAllInputs()\n this.runGraph()\n })\n\n const changeEffect = new Effect({\n fn: () => {\n batch(() => {\n Object.entries(this.inputCollections).forEach(([key, collection]) => {\n this.sendChangesToInput(\n key,\n collection.derivedChanges.state,\n collection.config.getId\n )\n })\n this.incrementVersion()\n this.sendFrontierToAllInputs()\n this.runGraph()\n })\n },\n deps: Object.values(this.inputCollections).map(\n (collection) => collection.derivedChanges\n ),\n })\n this.unsubscribeEffect = changeEffect.mount()\n\n this.state = `running`\n return () => {\n this.stop()\n }\n }\n\n stop() {\n this.unsubscribeEffect?.()\n this.unsubscribeEffect = undefined\n this.state = `stopped`\n }\n}\n"],"names":[],"mappings":"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"}
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -3,6 +3,14 @@ import { Collection } from './collection.js';
|
|
|
3
3
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
4
4
|
import { Transaction } from './transactions.js';
|
|
5
5
|
export type TransactionState = `pending` | `persisting` | `completed` | `failed`;
|
|
6
|
+
/**
|
|
7
|
+
* Represents a utility function that can be attached to a collection
|
|
8
|
+
*/
|
|
9
|
+
export type Fn = (...args: Array<any>) => any;
|
|
10
|
+
/**
|
|
11
|
+
* A record of utility functions that can be attached to a collection
|
|
12
|
+
*/
|
|
13
|
+
export type UtilsRecord = Record<string, Fn>;
|
|
6
14
|
/**
|
|
7
15
|
* Represents a pending mutation within a transaction
|
|
8
16
|
* Contains information about the original and modified data, as well as metadata
|
package/package.json
CHANGED
package/src/collection.ts
CHANGED
|
@@ -5,22 +5,26 @@ import { SortedMap } from "./SortedMap"
|
|
|
5
5
|
import type {
|
|
6
6
|
ChangeMessage,
|
|
7
7
|
CollectionConfig,
|
|
8
|
+
Fn,
|
|
8
9
|
InsertConfig,
|
|
9
10
|
OperationConfig,
|
|
10
11
|
OptimisticChangeMessage,
|
|
11
12
|
PendingMutation,
|
|
12
13
|
StandardSchema,
|
|
13
14
|
Transaction as TransactionType,
|
|
15
|
+
UtilsRecord,
|
|
14
16
|
} from "./types"
|
|
15
17
|
|
|
16
18
|
// Store collections in memory using Tanstack store
|
|
17
|
-
export const collectionsStore = new Store(
|
|
19
|
+
export const collectionsStore = new Store(
|
|
20
|
+
new Map<string, CollectionImpl<any>>()
|
|
21
|
+
)
|
|
18
22
|
|
|
19
23
|
// Map to track loading collections
|
|
20
24
|
|
|
21
25
|
const loadingCollections = new Map<
|
|
22
26
|
string,
|
|
23
|
-
Promise<
|
|
27
|
+
Promise<CollectionImpl<Record<string, unknown>>>
|
|
24
28
|
>()
|
|
25
29
|
|
|
26
30
|
interface PendingSyncedTransaction<T extends object = Record<string, unknown>> {
|
|
@@ -28,17 +32,40 @@ interface PendingSyncedTransaction<T extends object = Record<string, unknown>> {
|
|
|
28
32
|
operations: Array<OptimisticChangeMessage<T>>
|
|
29
33
|
}
|
|
30
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Enhanced Collection interface that includes both data type T and utilities TUtils
|
|
37
|
+
* @template T - The type of items in the collection
|
|
38
|
+
* @template TUtils - The utilities record type
|
|
39
|
+
*/
|
|
40
|
+
export interface Collection<
|
|
41
|
+
T extends object = Record<string, unknown>,
|
|
42
|
+
TUtils extends UtilsRecord = {},
|
|
43
|
+
> extends CollectionImpl<T> {
|
|
44
|
+
readonly utils: TUtils
|
|
45
|
+
}
|
|
46
|
+
|
|
31
47
|
/**
|
|
32
48
|
* Creates a new Collection instance with the given configuration
|
|
33
49
|
*
|
|
34
50
|
* @template T - The type of items in the collection
|
|
35
|
-
* @
|
|
36
|
-
* @
|
|
51
|
+
* @template TUtils - The utilities record type
|
|
52
|
+
* @param options - Collection options with optional utilities
|
|
53
|
+
* @returns A new Collection with utilities exposed both at top level and under .utils
|
|
37
54
|
*/
|
|
38
|
-
export function createCollection<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
export function createCollection<
|
|
56
|
+
T extends object = Record<string, unknown>,
|
|
57
|
+
TUtils extends UtilsRecord = {},
|
|
58
|
+
>(options: CollectionConfig<T> & { utils?: TUtils }): Collection<T, TUtils> {
|
|
59
|
+
const collection = new CollectionImpl<T>(options)
|
|
60
|
+
|
|
61
|
+
// Copy utils to both top level and .utils namespace
|
|
62
|
+
if (options.utils) {
|
|
63
|
+
collection.utils = { ...options.utils }
|
|
64
|
+
} else {
|
|
65
|
+
collection.utils = {} as TUtils
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return collection as Collection<T, TUtils>
|
|
42
69
|
}
|
|
43
70
|
|
|
44
71
|
/**
|
|
@@ -69,7 +96,7 @@ export function createCollection<T extends object = Record<string, unknown>>(
|
|
|
69
96
|
*/
|
|
70
97
|
export function preloadCollection<T extends object = Record<string, unknown>>(
|
|
71
98
|
config: CollectionConfig<T>
|
|
72
|
-
): Promise<
|
|
99
|
+
): Promise<CollectionImpl<T>> {
|
|
73
100
|
if (!config.id) {
|
|
74
101
|
throw new Error(`The id property is required for preloadCollection`)
|
|
75
102
|
}
|
|
@@ -86,7 +113,7 @@ export function preloadCollection<T extends object = Record<string, unknown>>(
|
|
|
86
113
|
|
|
87
114
|
// If the collection is in the process of loading, return its promise
|
|
88
115
|
if (loadingCollections.has(config.id)) {
|
|
89
|
-
return loadingCollections.get(config.id)! as Promise<
|
|
116
|
+
return loadingCollections.get(config.id)! as Promise<CollectionImpl<T>>
|
|
90
117
|
}
|
|
91
118
|
|
|
92
119
|
// Create a new collection instance if it doesn't exist
|
|
@@ -98,7 +125,7 @@ export function preloadCollection<T extends object = Record<string, unknown>>(
|
|
|
98
125
|
}
|
|
99
126
|
next.set(
|
|
100
127
|
config.id,
|
|
101
|
-
|
|
128
|
+
createCollection<T>({
|
|
102
129
|
id: config.id,
|
|
103
130
|
getId: config.getId,
|
|
104
131
|
sync: config.sync,
|
|
@@ -113,9 +140,9 @@ export function preloadCollection<T extends object = Record<string, unknown>>(
|
|
|
113
140
|
|
|
114
141
|
// Create a promise that will resolve after the first commit
|
|
115
142
|
let resolveFirstCommit: () => void
|
|
116
|
-
const firstCommitPromise = new Promise<
|
|
143
|
+
const firstCommitPromise = new Promise<CollectionImpl<T>>((resolve) => {
|
|
117
144
|
resolveFirstCommit = () => {
|
|
118
|
-
resolve(collection)
|
|
145
|
+
resolve(collection as CollectionImpl<T>)
|
|
119
146
|
}
|
|
120
147
|
})
|
|
121
148
|
|
|
@@ -133,7 +160,7 @@ export function preloadCollection<T extends object = Record<string, unknown>>(
|
|
|
133
160
|
// Store the loading promise
|
|
134
161
|
loadingCollections.set(
|
|
135
162
|
config.id,
|
|
136
|
-
firstCommitPromise as Promise<
|
|
163
|
+
firstCommitPromise as Promise<CollectionImpl<Record<string, unknown>>>
|
|
137
164
|
)
|
|
138
165
|
|
|
139
166
|
return firstCommitPromise
|
|
@@ -168,7 +195,12 @@ export class SchemaValidationError extends Error {
|
|
|
168
195
|
}
|
|
169
196
|
}
|
|
170
197
|
|
|
171
|
-
export class
|
|
198
|
+
export class CollectionImpl<T extends object = Record<string, unknown>> {
|
|
199
|
+
/**
|
|
200
|
+
* Utilities namespace
|
|
201
|
+
* This is populated by createCollection
|
|
202
|
+
*/
|
|
203
|
+
public utils: Record<string, Fn> = {}
|
|
172
204
|
public transactions: Store<SortedMap<string, TransactionType>>
|
|
173
205
|
public optimisticOperations: Derived<Array<OptimisticChangeMessage<T>>>
|
|
174
206
|
public derivedState: Derived<Map<string, T>>
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { D2, MessageType, MultiSet, output } from "@electric-sql/d2ts"
|
|
2
2
|
import { Effect, batch } from "@tanstack/store"
|
|
3
|
-
import {
|
|
3
|
+
import { createCollection } from "../collection.js"
|
|
4
4
|
import { compileQueryPipeline } from "./pipeline-compiler.js"
|
|
5
|
+
import type { Collection } from "../collection.js"
|
|
5
6
|
import type { ChangeMessage, SyncConfig } from "../types.js"
|
|
6
7
|
import type {
|
|
7
8
|
IStreamBuilder,
|
|
@@ -97,9 +98,9 @@ export class CompiledQuery<TResults extends object = Record<string, unknown>> {
|
|
|
97
98
|
|
|
98
99
|
this.graph = graph
|
|
99
100
|
this.inputs = inputs
|
|
100
|
-
this.resultCollection =
|
|
101
|
+
this.resultCollection = createCollection<TResults>({
|
|
101
102
|
id: crypto.randomUUID(), // TODO: remove when we don't require any more
|
|
102
|
-
getId: (val) => {
|
|
103
|
+
getId: (val: unknown) => {
|
|
103
104
|
return (val as any)._key
|
|
104
105
|
},
|
|
105
106
|
sync: {
|
package/src/types.ts
CHANGED
|
@@ -5,6 +5,16 @@ import type { Transaction } from "./transactions"
|
|
|
5
5
|
|
|
6
6
|
export type TransactionState = `pending` | `persisting` | `completed` | `failed`
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Represents a utility function that can be attached to a collection
|
|
10
|
+
*/
|
|
11
|
+
export type Fn = (...args: Array<any>) => any
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A record of utility functions that can be attached to a collection
|
|
15
|
+
*/
|
|
16
|
+
export type UtilsRecord = Record<string, Fn>
|
|
17
|
+
|
|
8
18
|
/**
|
|
9
19
|
* Represents a pending mutation within a transaction
|
|
10
20
|
* Contains information about the original and modified data, as well as metadata
|