@tanstack/db 0.0.30 → 0.0.32

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.
@@ -129,7 +129,7 @@ function createFilteredCallback(originalCallback, options) {
129
129
  }
130
130
  }
131
131
  }
132
- if (filteredChanges.length > 0) {
132
+ if (filteredChanges.length > 0 || changes.length === 0) {
133
133
  originalCallback(filteredChanges);
134
134
  }
135
135
  };
@@ -1 +1 @@
1
- {"version":3,"file":"change-events.cjs","sources":["../../src/change-events.ts"],"sourcesContent":["import {\n createSingleRowRefProxy,\n toExpression,\n} from \"./query/builder/ref-proxy\"\nimport { compileSingleRowExpression } from \"./query/compiler/evaluators.js\"\nimport { optimizeExpressionWithIndexes } from \"./utils/index-optimization.js\"\nimport type {\n ChangeMessage,\n CurrentStateAsChangesOptions,\n SubscribeChangesOptions,\n} from \"./types\"\nimport type { Collection } from \"./collection\"\nimport type { SingleRowRefProxy } from \"./query/builder/ref-proxy\"\nimport type { BasicExpression } from \"./query/ir.js\"\n\n/**\n * Interface for a collection-like object that provides the necessary methods\n * for the change events system to work\n */\nexport interface CollectionLike<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n> extends Pick<Collection<T, TKey>, `get` | `has` | `entries` | `indexes`> {}\n\n/**\n * Returns the current state of the collection as an array of changes\n * @param collection - The collection to get changes from\n * @param options - Options including optional where filter\n * @returns An array of changes\n * @example\n * // Get all items as changes\n * const allChanges = currentStateAsChanges(collection)\n *\n * // Get only items matching a condition\n * const activeChanges = currentStateAsChanges(collection, {\n * where: (row) => row.status === 'active'\n * })\n *\n * // Get only items using a pre-compiled expression\n * const activeChanges = currentStateAsChanges(collection, {\n * whereExpression: eq(row.status, 'active')\n * })\n */\nexport function currentStateAsChanges<\n T extends object,\n TKey extends string | number,\n>(\n collection: CollectionLike<T, TKey>,\n options: CurrentStateAsChangesOptions<T> = {}\n): Array<ChangeMessage<T>> {\n // Helper function to collect filtered results\n const collectFilteredResults = (\n filterFn?: (value: T) => boolean\n ): Array<ChangeMessage<T>> => {\n const result: Array<ChangeMessage<T>> = []\n for (const [key, value] of collection.entries()) {\n // If no filter function is provided, include all items\n if (filterFn?.(value) ?? true) {\n result.push({\n type: `insert`,\n key,\n value,\n })\n }\n }\n return result\n }\n\n if (!options.where && !options.whereExpression) {\n // No filtering, return all items\n return collectFilteredResults()\n }\n\n // There's a where clause, let's see if we can use an index\n try {\n let expression: BasicExpression<boolean>\n\n if (options.whereExpression) {\n // Use the pre-compiled expression directly\n expression = options.whereExpression\n } else if (options.where) {\n // Create the single-row refProxy for the callback\n const singleRowRefProxy = createSingleRowRefProxy<T>()\n\n // Execute the callback to get the expression\n const whereExpression = options.where(singleRowRefProxy)\n\n // Convert the result to a BasicExpression\n expression = toExpression(whereExpression)\n } else {\n // This should never happen due to the check above, but TypeScript needs it\n return []\n }\n\n // Try to optimize the query using indexes\n const optimizationResult = optimizeExpressionWithIndexes(\n expression,\n collection.indexes\n )\n\n if (optimizationResult.canOptimize) {\n // Use index optimization\n const result: Array<ChangeMessage<T>> = []\n for (const key of optimizationResult.matchingKeys) {\n const value = collection.get(key)\n if (value !== undefined) {\n result.push({\n type: `insert`,\n key,\n value,\n })\n }\n }\n return result\n } else {\n // No index found or complex expression, fall back to full scan with filter\n const filterFn = options.where\n ? createFilterFunction(options.where)\n : createFilterFunctionFromExpression(expression)\n\n return collectFilteredResults(filterFn)\n }\n } catch (error) {\n // If anything goes wrong with the where clause, fall back to full scan\n console.warn(\n `Error processing where clause, falling back to full scan:`,\n error\n )\n\n const filterFn = options.where\n ? createFilterFunction(options.where)\n : createFilterFunctionFromExpression(options.whereExpression!)\n\n return collectFilteredResults(filterFn)\n }\n}\n\n/**\n * Creates a filter function from a where callback\n * @param whereCallback - The callback function that defines the filter condition\n * @returns A function that takes an item and returns true if it matches the filter\n */\nexport function createFilterFunction<T extends object>(\n whereCallback: (row: SingleRowRefProxy<T>) => any\n): (item: T) => boolean {\n return (item: T): boolean => {\n try {\n // First try the RefProxy approach for query builder functions\n const singleRowRefProxy = createSingleRowRefProxy<T>()\n const whereExpression = whereCallback(singleRowRefProxy)\n const expression = toExpression(whereExpression)\n const evaluator = compileSingleRowExpression(expression)\n const result = evaluator(item as Record<string, unknown>)\n // WHERE clauses should always evaluate to boolean predicates (Kevin's feedback)\n return result\n } catch {\n // If RefProxy approach fails (e.g., arithmetic operations), fall back to direct evaluation\n try {\n // Create a simple proxy that returns actual values for arithmetic operations\n const simpleProxy = new Proxy(item as any, {\n get(target, prop) {\n return target[prop]\n },\n }) as SingleRowRefProxy<T>\n\n const result = whereCallback(simpleProxy)\n return result\n } catch {\n // If both approaches fail, exclude the item\n return false\n }\n }\n }\n}\n\n/**\n * Creates a filter function from a pre-compiled expression\n * @param expression - The pre-compiled expression to evaluate\n * @returns A function that takes an item and returns true if it matches the filter\n */\nexport function createFilterFunctionFromExpression<T extends object>(\n expression: BasicExpression<boolean>\n): (item: T) => boolean {\n return (item: T): boolean => {\n try {\n const evaluator = compileSingleRowExpression(expression)\n const result = evaluator(item as Record<string, unknown>)\n return Boolean(result)\n } catch {\n // If evaluation fails, exclude the item\n return false\n }\n }\n}\n\n/**\n * Creates a filtered callback that only calls the original callback with changes that match the where clause\n * @param originalCallback - The original callback to filter\n * @param options - The subscription options containing the where clause\n * @returns A filtered callback function\n */\nexport function createFilteredCallback<T extends object>(\n originalCallback: (changes: Array<ChangeMessage<T>>) => void,\n options: SubscribeChangesOptions<T>\n): (changes: Array<ChangeMessage<T>>) => void {\n const filterFn = options.whereExpression\n ? createFilterFunctionFromExpression(options.whereExpression)\n : createFilterFunction(options.where!)\n\n return (changes: Array<ChangeMessage<T>>) => {\n const filteredChanges: Array<ChangeMessage<T>> = []\n\n for (const change of changes) {\n if (change.type === `insert`) {\n // For inserts, check if the new value matches the filter\n if (filterFn(change.value)) {\n filteredChanges.push(change)\n }\n } else if (change.type === `update`) {\n // For updates, we need to check both old and new values\n const newValueMatches = filterFn(change.value)\n const oldValueMatches = change.previousValue\n ? filterFn(change.previousValue)\n : false\n\n if (newValueMatches && oldValueMatches) {\n // Both old and new match: emit update\n filteredChanges.push(change)\n } else if (newValueMatches && !oldValueMatches) {\n // New matches but old didn't: emit insert\n filteredChanges.push({\n ...change,\n type: `insert`,\n })\n } else if (!newValueMatches && oldValueMatches) {\n // Old matched but new doesn't: emit delete\n filteredChanges.push({\n ...change,\n type: `delete`,\n value: change.previousValue!, // Use the previous value for the delete\n })\n }\n // If neither matches, don't emit anything\n } else {\n // For deletes, include if the previous value would have matched\n // (so subscribers know something they were tracking was deleted)\n if (filterFn(change.value)) {\n filteredChanges.push(change)\n }\n }\n }\n\n if (filteredChanges.length > 0) {\n originalCallback(filteredChanges)\n }\n }\n}\n"],"names":["createSingleRowRefProxy","toExpression","optimizeExpressionWithIndexes","compileSingleRowExpression"],"mappings":";;;;;AA2CO,SAAS,sBAId,YACA,UAA2C,IAClB;AAEzB,QAAM,yBAAyB,CAC7B,aAC4B;AAC5B,UAAM,SAAkC,CAAA;AACxC,eAAW,CAAC,KAAK,KAAK,KAAK,WAAW,WAAW;AAE/C,WAAI,qCAAW,WAAU,MAAM;AAC7B,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN;AAAA,UACA;AAAA,QAAA,CACD;AAAA,MACH;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,QAAQ,SAAS,CAAC,QAAQ,iBAAiB;AAE9C,WAAO,uBAAA;AAAA,EACT;AAGA,MAAI;AACF,QAAI;AAEJ,QAAI,QAAQ,iBAAiB;AAE3B,mBAAa,QAAQ;AAAA,IACvB,WAAW,QAAQ,OAAO;AAExB,YAAM,oBAAoBA,SAAAA,wBAAA;AAG1B,YAAM,kBAAkB,QAAQ,MAAM,iBAAiB;AAGvD,mBAAaC,SAAAA,aAAa,eAAe;AAAA,IAC3C,OAAO;AAEL,aAAO,CAAA;AAAA,IACT;AAGA,UAAM,qBAAqBC,kBAAAA;AAAAA,MACzB;AAAA,MACA,WAAW;AAAA,IAAA;AAGb,QAAI,mBAAmB,aAAa;AAElC,YAAM,SAAkC,CAAA;AACxC,iBAAW,OAAO,mBAAmB,cAAc;AACjD,cAAM,QAAQ,WAAW,IAAI,GAAG;AAChC,YAAI,UAAU,QAAW;AACvB,iBAAO,KAAK;AAAA,YACV,MAAM;AAAA,YACN;AAAA,YACA;AAAA,UAAA,CACD;AAAA,QACH;AAAA,MACF;AACA,aAAO;AAAA,IACT,OAAO;AAEL,YAAM,WAAW,QAAQ,QACrB,qBAAqB,QAAQ,KAAK,IAClC,mCAAmC,UAAU;AAEjD,aAAO,uBAAuB,QAAQ;AAAA,IACxC;AAAA,EACF,SAAS,OAAO;AAEd,YAAQ;AAAA,MACN;AAAA,MACA;AAAA,IAAA;AAGF,UAAM,WAAW,QAAQ,QACrB,qBAAqB,QAAQ,KAAK,IAClC,mCAAmC,QAAQ,eAAgB;AAE/D,WAAO,uBAAuB,QAAQ;AAAA,EACxC;AACF;AAOO,SAAS,qBACd,eACsB;AACtB,SAAO,CAAC,SAAqB;AAC3B,QAAI;AAEF,YAAM,oBAAoBF,SAAAA,wBAAA;AAC1B,YAAM,kBAAkB,cAAc,iBAAiB;AACvD,YAAM,aAAaC,SAAAA,aAAa,eAAe;AAC/C,YAAM,YAAYE,WAAAA,2BAA2B,UAAU;AACvD,YAAM,SAAS,UAAU,IAA+B;AAExD,aAAO;AAAA,IACT,QAAQ;AAEN,UAAI;AAEF,cAAM,cAAc,IAAI,MAAM,MAAa;AAAA,UACzC,IAAI,QAAQ,MAAM;AAChB,mBAAO,OAAO,IAAI;AAAA,UACpB;AAAA,QAAA,CACD;AAED,cAAM,SAAS,cAAc,WAAW;AACxC,eAAO;AAAA,MACT,QAAQ;AAEN,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF;AAOO,SAAS,mCACd,YACsB;AACtB,SAAO,CAAC,SAAqB;AAC3B,QAAI;AACF,YAAM,YAAYA,WAAAA,2BAA2B,UAAU;AACvD,YAAM,SAAS,UAAU,IAA+B;AACxD,aAAO,QAAQ,MAAM;AAAA,IACvB,QAAQ;AAEN,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAQO,SAAS,uBACd,kBACA,SAC4C;AAC5C,QAAM,WAAW,QAAQ,kBACrB,mCAAmC,QAAQ,eAAe,IAC1D,qBAAqB,QAAQ,KAAM;AAEvC,SAAO,CAAC,YAAqC;AAC3C,UAAM,kBAA2C,CAAA;AAEjD,eAAW,UAAU,SAAS;AAC5B,UAAI,OAAO,SAAS,UAAU;AAE5B,YAAI,SAAS,OAAO,KAAK,GAAG;AAC1B,0BAAgB,KAAK,MAAM;AAAA,QAC7B;AAAA,MACF,WAAW,OAAO,SAAS,UAAU;AAEnC,cAAM,kBAAkB,SAAS,OAAO,KAAK;AAC7C,cAAM,kBAAkB,OAAO,gBAC3B,SAAS,OAAO,aAAa,IAC7B;AAEJ,YAAI,mBAAmB,iBAAiB;AAEtC,0BAAgB,KAAK,MAAM;AAAA,QAC7B,WAAW,mBAAmB,CAAC,iBAAiB;AAE9C,0BAAgB,KAAK;AAAA,YACnB,GAAG;AAAA,YACH,MAAM;AAAA,UAAA,CACP;AAAA,QACH,WAAW,CAAC,mBAAmB,iBAAiB;AAE9C,0BAAgB,KAAK;AAAA,YACnB,GAAG;AAAA,YACH,MAAM;AAAA,YACN,OAAO,OAAO;AAAA;AAAA,UAAA,CACf;AAAA,QACH;AAAA,MAEF,OAAO;AAGL,YAAI,SAAS,OAAO,KAAK,GAAG;AAC1B,0BAAgB,KAAK,MAAM;AAAA,QAC7B;AAAA,MACF;AAAA,IACF;AAEA,QAAI,gBAAgB,SAAS,GAAG;AAC9B,uBAAiB,eAAe;AAAA,IAClC;AAAA,EACF;AACF;;;;;"}
1
+ {"version":3,"file":"change-events.cjs","sources":["../../src/change-events.ts"],"sourcesContent":["import {\n createSingleRowRefProxy,\n toExpression,\n} from \"./query/builder/ref-proxy\"\nimport { compileSingleRowExpression } from \"./query/compiler/evaluators.js\"\nimport { optimizeExpressionWithIndexes } from \"./utils/index-optimization.js\"\nimport type {\n ChangeMessage,\n CurrentStateAsChangesOptions,\n SubscribeChangesOptions,\n} from \"./types\"\nimport type { Collection } from \"./collection\"\nimport type { SingleRowRefProxy } from \"./query/builder/ref-proxy\"\nimport type { BasicExpression } from \"./query/ir.js\"\n\n/**\n * Interface for a collection-like object that provides the necessary methods\n * for the change events system to work\n */\nexport interface CollectionLike<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n> extends Pick<Collection<T, TKey>, `get` | `has` | `entries` | `indexes`> {}\n\n/**\n * Returns the current state of the collection as an array of changes\n * @param collection - The collection to get changes from\n * @param options - Options including optional where filter\n * @returns An array of changes\n * @example\n * // Get all items as changes\n * const allChanges = currentStateAsChanges(collection)\n *\n * // Get only items matching a condition\n * const activeChanges = currentStateAsChanges(collection, {\n * where: (row) => row.status === 'active'\n * })\n *\n * // Get only items using a pre-compiled expression\n * const activeChanges = currentStateAsChanges(collection, {\n * whereExpression: eq(row.status, 'active')\n * })\n */\nexport function currentStateAsChanges<\n T extends object,\n TKey extends string | number,\n>(\n collection: CollectionLike<T, TKey>,\n options: CurrentStateAsChangesOptions<T> = {}\n): Array<ChangeMessage<T>> {\n // Helper function to collect filtered results\n const collectFilteredResults = (\n filterFn?: (value: T) => boolean\n ): Array<ChangeMessage<T>> => {\n const result: Array<ChangeMessage<T>> = []\n for (const [key, value] of collection.entries()) {\n // If no filter function is provided, include all items\n if (filterFn?.(value) ?? true) {\n result.push({\n type: `insert`,\n key,\n value,\n })\n }\n }\n return result\n }\n\n if (!options.where && !options.whereExpression) {\n // No filtering, return all items\n return collectFilteredResults()\n }\n\n // There's a where clause, let's see if we can use an index\n try {\n let expression: BasicExpression<boolean>\n\n if (options.whereExpression) {\n // Use the pre-compiled expression directly\n expression = options.whereExpression\n } else if (options.where) {\n // Create the single-row refProxy for the callback\n const singleRowRefProxy = createSingleRowRefProxy<T>()\n\n // Execute the callback to get the expression\n const whereExpression = options.where(singleRowRefProxy)\n\n // Convert the result to a BasicExpression\n expression = toExpression(whereExpression)\n } else {\n // This should never happen due to the check above, but TypeScript needs it\n return []\n }\n\n // Try to optimize the query using indexes\n const optimizationResult = optimizeExpressionWithIndexes(\n expression,\n collection.indexes\n )\n\n if (optimizationResult.canOptimize) {\n // Use index optimization\n const result: Array<ChangeMessage<T>> = []\n for (const key of optimizationResult.matchingKeys) {\n const value = collection.get(key)\n if (value !== undefined) {\n result.push({\n type: `insert`,\n key,\n value,\n })\n }\n }\n return result\n } else {\n // No index found or complex expression, fall back to full scan with filter\n const filterFn = options.where\n ? createFilterFunction(options.where)\n : createFilterFunctionFromExpression(expression)\n\n return collectFilteredResults(filterFn)\n }\n } catch (error) {\n // If anything goes wrong with the where clause, fall back to full scan\n console.warn(\n `Error processing where clause, falling back to full scan:`,\n error\n )\n\n const filterFn = options.where\n ? createFilterFunction(options.where)\n : createFilterFunctionFromExpression(options.whereExpression!)\n\n return collectFilteredResults(filterFn)\n }\n}\n\n/**\n * Creates a filter function from a where callback\n * @param whereCallback - The callback function that defines the filter condition\n * @returns A function that takes an item and returns true if it matches the filter\n */\nexport function createFilterFunction<T extends object>(\n whereCallback: (row: SingleRowRefProxy<T>) => any\n): (item: T) => boolean {\n return (item: T): boolean => {\n try {\n // First try the RefProxy approach for query builder functions\n const singleRowRefProxy = createSingleRowRefProxy<T>()\n const whereExpression = whereCallback(singleRowRefProxy)\n const expression = toExpression(whereExpression)\n const evaluator = compileSingleRowExpression(expression)\n const result = evaluator(item as Record<string, unknown>)\n // WHERE clauses should always evaluate to boolean predicates (Kevin's feedback)\n return result\n } catch {\n // If RefProxy approach fails (e.g., arithmetic operations), fall back to direct evaluation\n try {\n // Create a simple proxy that returns actual values for arithmetic operations\n const simpleProxy = new Proxy(item as any, {\n get(target, prop) {\n return target[prop]\n },\n }) as SingleRowRefProxy<T>\n\n const result = whereCallback(simpleProxy)\n return result\n } catch {\n // If both approaches fail, exclude the item\n return false\n }\n }\n }\n}\n\n/**\n * Creates a filter function from a pre-compiled expression\n * @param expression - The pre-compiled expression to evaluate\n * @returns A function that takes an item and returns true if it matches the filter\n */\nexport function createFilterFunctionFromExpression<T extends object>(\n expression: BasicExpression<boolean>\n): (item: T) => boolean {\n return (item: T): boolean => {\n try {\n const evaluator = compileSingleRowExpression(expression)\n const result = evaluator(item as Record<string, unknown>)\n return Boolean(result)\n } catch {\n // If evaluation fails, exclude the item\n return false\n }\n }\n}\n\n/**\n * Creates a filtered callback that only calls the original callback with changes that match the where clause\n * @param originalCallback - The original callback to filter\n * @param options - The subscription options containing the where clause\n * @returns A filtered callback function\n */\nexport function createFilteredCallback<T extends object>(\n originalCallback: (changes: Array<ChangeMessage<T>>) => void,\n options: SubscribeChangesOptions<T>\n): (changes: Array<ChangeMessage<T>>) => void {\n const filterFn = options.whereExpression\n ? createFilterFunctionFromExpression(options.whereExpression)\n : createFilterFunction(options.where!)\n\n return (changes: Array<ChangeMessage<T>>) => {\n const filteredChanges: Array<ChangeMessage<T>> = []\n\n for (const change of changes) {\n if (change.type === `insert`) {\n // For inserts, check if the new value matches the filter\n if (filterFn(change.value)) {\n filteredChanges.push(change)\n }\n } else if (change.type === `update`) {\n // For updates, we need to check both old and new values\n const newValueMatches = filterFn(change.value)\n const oldValueMatches = change.previousValue\n ? filterFn(change.previousValue)\n : false\n\n if (newValueMatches && oldValueMatches) {\n // Both old and new match: emit update\n filteredChanges.push(change)\n } else if (newValueMatches && !oldValueMatches) {\n // New matches but old didn't: emit insert\n filteredChanges.push({\n ...change,\n type: `insert`,\n })\n } else if (!newValueMatches && oldValueMatches) {\n // Old matched but new doesn't: emit delete\n filteredChanges.push({\n ...change,\n type: `delete`,\n value: change.previousValue!, // Use the previous value for the delete\n })\n }\n // If neither matches, don't emit anything\n } else {\n // For deletes, include if the previous value would have matched\n // (so subscribers know something they were tracking was deleted)\n if (filterFn(change.value)) {\n filteredChanges.push(change)\n }\n }\n }\n\n // Always call the original callback if we have filtered changes OR\n // if the original changes array was empty (which indicates a ready signal)\n if (filteredChanges.length > 0 || changes.length === 0) {\n originalCallback(filteredChanges)\n }\n }\n}\n"],"names":["createSingleRowRefProxy","toExpression","optimizeExpressionWithIndexes","compileSingleRowExpression"],"mappings":"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"}
@@ -9,7 +9,6 @@ const autoIndex = require("./indexes/auto-index.cjs");
9
9
  const transactions = require("./transactions.cjs");
10
10
  const errors = require("./errors.cjs");
11
11
  const changeEvents = require("./change-events.cjs");
12
- const collectionsStore = /* @__PURE__ */ new Map();
13
12
  function createCollection(options) {
14
13
  const collection = new CollectionImpl(options);
15
14
  if (options.utils) {
@@ -255,7 +254,7 @@ class CollectionImpl {
255
254
  if (ambientTransaction) {
256
255
  ambientTransaction.applyMutations(mutations);
257
256
  this.transactions.set(ambientTransaction.id, ambientTransaction);
258
- this.recomputeOptimisticState();
257
+ this.recomputeOptimisticState(true);
259
258
  return ambientTransaction;
260
259
  } else {
261
260
  const directOpTransaction = transactions.createTransaction({
@@ -269,7 +268,7 @@ class CollectionImpl {
269
268
  directOpTransaction.applyMutations(mutations);
270
269
  directOpTransaction.commit();
271
270
  this.transactions.set(directOpTransaction.id, directOpTransaction);
272
- this.recomputeOptimisticState();
271
+ this.recomputeOptimisticState(true);
273
272
  return directOpTransaction;
274
273
  }
275
274
  };
@@ -309,7 +308,7 @@ class CollectionImpl {
309
308
  if (ambientTransaction) {
310
309
  ambientTransaction.applyMutations(mutations);
311
310
  this.transactions.set(ambientTransaction.id, ambientTransaction);
312
- this.recomputeOptimisticState();
311
+ this.recomputeOptimisticState(true);
313
312
  return ambientTransaction;
314
313
  }
315
314
  const directOpTransaction = transactions.createTransaction({
@@ -324,7 +323,7 @@ class CollectionImpl {
324
323
  directOpTransaction.applyMutations(mutations);
325
324
  directOpTransaction.commit();
326
325
  this.transactions.set(directOpTransaction.id, directOpTransaction);
327
- this.recomputeOptimisticState();
326
+ this.recomputeOptimisticState(true);
328
327
  return directOpTransaction;
329
328
  };
330
329
  if (!config) {
@@ -345,7 +344,6 @@ class CollectionImpl {
345
344
  ...config,
346
345
  autoIndex: config.autoIndex ?? `eager`
347
346
  };
348
- collectionsStore.set(this.id, this);
349
347
  if (this.config.compare) {
350
348
  this.syncedData = new SortedMap.SortedMap(this.config.compare);
351
349
  } else {
@@ -404,6 +402,9 @@ class CollectionImpl {
404
402
  const callbacks = [...this.onFirstReadyCallbacks];
405
403
  this.onFirstReadyCallbacks = [];
406
404
  callbacks.forEach((callback) => callback());
405
+ if (this.size === 0 && this.changeListeners.size > 0) {
406
+ this.emitEmptyReadyEvent();
407
+ }
407
408
  }
408
409
  }
409
410
  }
@@ -653,7 +654,7 @@ class CollectionImpl {
653
654
  /**
654
655
  * Recompute optimistic state from active transactions
655
656
  */
656
- recomputeOptimisticState() {
657
+ recomputeOptimisticState(triggeredByUserAction = false) {
657
658
  if (this.isCommittingSyncTransactions) {
658
659
  return;
659
660
  }
@@ -690,10 +691,16 @@ class CollectionImpl {
690
691
  this._size = this.calculateSize();
691
692
  const events = [];
692
693
  this.collectOptimisticChanges(previousState, previousDeletes, events);
693
- const filteredEventsBySyncStatus = events.filter(
694
- (event) => !this.recentlySyncedKeys.has(event.key)
695
- );
696
- if (this.pendingSyncedTransactions.length > 0) {
694
+ const filteredEventsBySyncStatus = events.filter((event) => {
695
+ if (!this.recentlySyncedKeys.has(event.key)) {
696
+ return true;
697
+ }
698
+ if (triggeredByUserAction) {
699
+ return true;
700
+ }
701
+ return false;
702
+ });
703
+ if (this.pendingSyncedTransactions.length > 0 && !triggeredByUserAction) {
697
704
  const pendingSyncKeys = /* @__PURE__ */ new Set();
698
705
  const completedTransactionMutations = /* @__PURE__ */ new Set();
699
706
  for (const transaction of this.pendingSyncedTransactions) {
@@ -724,12 +731,12 @@ class CollectionImpl {
724
731
  if (filteredEvents.length > 0) {
725
732
  this.updateIndexes(filteredEvents);
726
733
  }
727
- this.emitEvents(filteredEvents);
734
+ this.emitEvents(filteredEvents, triggeredByUserAction);
728
735
  } else {
729
736
  if (filteredEventsBySyncStatus.length > 0) {
730
737
  this.updateIndexes(filteredEventsBySyncStatus);
731
738
  }
732
- this.emitEvents(filteredEventsBySyncStatus);
739
+ this.emitEvents(filteredEventsBySyncStatus, triggeredByUserAction);
733
740
  }
734
741
  }
735
742
  /**
@@ -788,19 +795,26 @@ class CollectionImpl {
788
795
  }
789
796
  return this.syncedData.get(key);
790
797
  }
798
+ /**
799
+ * Emit an empty ready event to notify subscribers that the collection is ready
800
+ * This bypasses the normal empty array check in emitEvents
801
+ */
802
+ emitEmptyReadyEvent() {
803
+ for (const listener of this.changeListeners) {
804
+ listener([]);
805
+ }
806
+ }
791
807
  /**
792
808
  * Emit events either immediately or batch them for later emission
793
809
  */
794
- emitEvents(changes, endBatching = false) {
795
- if (this.shouldBatchEvents && !endBatching) {
810
+ emitEvents(changes, forceEmit = false) {
811
+ if (this.shouldBatchEvents && !forceEmit) {
796
812
  this.batchedEvents.push(...changes);
797
813
  return;
798
814
  }
799
815
  let eventsToEmit = changes;
800
- if (endBatching) {
801
- if (this.batchedEvents.length > 0) {
802
- eventsToEmit = [...this.batchedEvents, ...changes];
803
- }
816
+ if (this.batchedEvents.length > 0 && forceEmit) {
817
+ eventsToEmit = [...this.batchedEvents, ...changes];
804
818
  this.batchedEvents = [];
805
819
  this.shouldBatchEvents = false;
806
820
  }
@@ -1206,7 +1220,7 @@ class CollectionImpl {
1206
1220
  if (ambientTransaction) {
1207
1221
  ambientTransaction.applyMutations(mutations);
1208
1222
  this.transactions.set(ambientTransaction.id, ambientTransaction);
1209
- this.recomputeOptimisticState();
1223
+ this.recomputeOptimisticState(true);
1210
1224
  return ambientTransaction;
1211
1225
  }
1212
1226
  const directOpTransaction = transactions.createTransaction({
@@ -1220,7 +1234,7 @@ class CollectionImpl {
1220
1234
  directOpTransaction.applyMutations(mutations);
1221
1235
  directOpTransaction.commit();
1222
1236
  this.transactions.set(directOpTransaction.id, directOpTransaction);
1223
- this.recomputeOptimisticState();
1237
+ this.recomputeOptimisticState(true);
1224
1238
  return directOpTransaction;
1225
1239
  }
1226
1240
  /**
@@ -1424,10 +1438,9 @@ class CollectionImpl {
1424
1438
  onTransactionStateChange() {
1425
1439
  this.shouldBatchEvents = this.pendingSyncedTransactions.length > 0;
1426
1440
  this.capturePreSyncVisibleState();
1427
- this.recomputeOptimisticState();
1441
+ this.recomputeOptimisticState(false);
1428
1442
  }
1429
1443
  }
1430
1444
  exports.CollectionImpl = CollectionImpl;
1431
- exports.collectionsStore = collectionsStore;
1432
1445
  exports.createCollection = createCollection;
1433
1446
  //# sourceMappingURL=collection.cjs.map