@tanstack/db 0.5.6 → 0.5.8
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/subscription.cjs +10 -1
- package/dist/cjs/collection/subscription.cjs.map +1 -1
- package/dist/cjs/proxy.cjs +294 -167
- package/dist/cjs/proxy.cjs.map +1 -1
- package/dist/cjs/query/predicate-utils.cjs +15 -0
- package/dist/cjs/query/predicate-utils.cjs.map +1 -1
- package/dist/esm/collection/subscription.js +11 -2
- package/dist/esm/collection/subscription.js.map +1 -1
- package/dist/esm/proxy.js +294 -167
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query/predicate-utils.js +15 -0
- package/dist/esm/query/predicate-utils.js.map +1 -1
- package/package.json +1 -1
- package/src/collection/subscription.ts +14 -2
- package/src/proxy.ts +492 -250
- package/src/query/predicate-utils.ts +44 -0
|
@@ -224,7 +224,16 @@ class CollectionSubscription extends eventEmitter.EventEmitter {
|
|
|
224
224
|
const promises = [];
|
|
225
225
|
if (typeof minValue !== `undefined`) {
|
|
226
226
|
const { expression } = orderBy[0];
|
|
227
|
-
|
|
227
|
+
let exactValueFilter;
|
|
228
|
+
if (minValue instanceof Date) {
|
|
229
|
+
const minValuePlus1ms = new Date(minValue.getTime() + 1);
|
|
230
|
+
exactValueFilter = functions.and(
|
|
231
|
+
functions.gte(expression, new ir.Value(minValue)),
|
|
232
|
+
functions.lt(expression, new ir.Value(minValuePlus1ms))
|
|
233
|
+
);
|
|
234
|
+
} else {
|
|
235
|
+
exactValueFilter = functions.eq(expression, new ir.Value(minValue));
|
|
236
|
+
}
|
|
228
237
|
const loadOptions2 = {
|
|
229
238
|
where: exactValueFilter,
|
|
230
239
|
subscription: this
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscription.cjs","sources":["../../../src/collection/subscription.ts"],"sourcesContent":["import { ensureIndexForExpression } from \"../indexes/auto-index.js\"\nimport { and, eq, gt, lt } from \"../query/builder/functions.js\"\nimport { Value } from \"../query/ir.js\"\nimport { EventEmitter } from \"../event-emitter.js\"\nimport {\n createFilterFunctionFromExpression,\n createFilteredCallback,\n} from \"./change-events.js\"\nimport type { BasicExpression, OrderBy } from \"../query/ir.js\"\nimport type { IndexInterface } from \"../indexes/base-index.js\"\nimport type {\n ChangeMessage,\n LoadSubsetOptions,\n Subscription,\n SubscriptionEvents,\n SubscriptionStatus,\n SubscriptionUnsubscribedEvent,\n} from \"../types.js\"\nimport type { CollectionImpl } from \"./index.js\"\n\ntype RequestSnapshotOptions = {\n where?: BasicExpression<boolean>\n optimizedOnly?: boolean\n trackLoadSubsetPromise?: boolean\n}\n\ntype RequestLimitedSnapshotOptions = {\n orderBy: OrderBy\n limit: number\n minValue?: any\n}\n\ntype CollectionSubscriptionOptions = {\n includeInitialState?: boolean\n /** Pre-compiled expression for filtering changes */\n whereExpression?: BasicExpression<boolean>\n /** Callback to call when the subscription is unsubscribed */\n onUnsubscribe?: (event: SubscriptionUnsubscribedEvent) => void\n}\n\nexport class CollectionSubscription\n extends EventEmitter<SubscriptionEvents>\n implements Subscription\n{\n private loadedInitialState = false\n\n // Flag to indicate that we have sent at least 1 snapshot.\n // While `snapshotSent` is false we filter out all changes from subscription to the collection.\n private snapshotSent = false\n\n /**\n * Track all loadSubset calls made by this subscription so we can unload them on cleanup.\n * We store the exact LoadSubsetOptions we passed to loadSubset to ensure symmetric unload.\n */\n private loadedSubsets: Array<LoadSubsetOptions> = []\n\n // Keep track of the keys we've sent (needed for join and orderBy optimizations)\n private sentKeys = new Set<string | number>()\n\n private filteredCallback: (changes: Array<ChangeMessage<any, any>>) => void\n\n private orderByIndex: IndexInterface<string | number> | undefined\n\n // Status tracking\n private _status: SubscriptionStatus = `ready`\n private pendingLoadSubsetPromises: Set<Promise<void>> = new Set()\n\n public get status(): SubscriptionStatus {\n return this._status\n }\n\n constructor(\n private collection: CollectionImpl<any, any, any, any, any>,\n private callback: (changes: Array<ChangeMessage<any, any>>) => void,\n private options: CollectionSubscriptionOptions\n ) {\n super()\n if (options.onUnsubscribe) {\n this.on(`unsubscribed`, (event) => options.onUnsubscribe!(event))\n }\n\n // Auto-index for where expressions if enabled\n if (options.whereExpression) {\n ensureIndexForExpression(options.whereExpression, this.collection)\n }\n\n const callbackWithSentKeysTracking = (\n changes: Array<ChangeMessage<any, any>>\n ) => {\n callback(changes)\n this.trackSentKeys(changes)\n }\n\n this.callback = callbackWithSentKeysTracking\n\n // Create a filtered callback if where clause is provided\n this.filteredCallback = options.whereExpression\n ? createFilteredCallback(this.callback, options)\n : this.callback\n }\n\n setOrderByIndex(index: IndexInterface<any>) {\n this.orderByIndex = index\n }\n\n /**\n * Set subscription status and emit events if changed\n */\n private setStatus(newStatus: SubscriptionStatus) {\n if (this._status === newStatus) {\n return // No change\n }\n\n const previousStatus = this._status\n this._status = newStatus\n\n // Emit status:change event\n this.emitInner(`status:change`, {\n type: `status:change`,\n subscription: this,\n previousStatus,\n status: newStatus,\n })\n\n // Emit specific status event\n const eventKey: `status:${SubscriptionStatus}` = `status:${newStatus}`\n this.emitInner(eventKey, {\n type: eventKey,\n subscription: this,\n previousStatus,\n status: newStatus,\n } as SubscriptionEvents[typeof eventKey])\n }\n\n /**\n * Track a loadSubset promise and manage loading status\n */\n private trackLoadSubsetPromise(syncResult: Promise<void> | true) {\n // Track the promise if it's actually a promise (async work)\n if (syncResult instanceof Promise) {\n this.pendingLoadSubsetPromises.add(syncResult)\n this.setStatus(`loadingSubset`)\n\n syncResult.finally(() => {\n this.pendingLoadSubsetPromises.delete(syncResult)\n if (this.pendingLoadSubsetPromises.size === 0) {\n this.setStatus(`ready`)\n }\n })\n }\n }\n\n hasLoadedInitialState() {\n return this.loadedInitialState\n }\n\n hasSentAtLeastOneSnapshot() {\n return this.snapshotSent\n }\n\n emitEvents(changes: Array<ChangeMessage<any, any>>) {\n const newChanges = this.filterAndFlipChanges(changes)\n this.filteredCallback(newChanges)\n }\n\n /**\n * Sends the snapshot to the callback.\n * Returns a boolean indicating if it succeeded.\n * It can only fail if there is no index to fulfill the request\n * and the optimizedOnly option is set to true,\n * or, the entire state was already loaded.\n */\n requestSnapshot(opts?: RequestSnapshotOptions): boolean {\n if (this.loadedInitialState) {\n // Subscription was deoptimized so we already sent the entire initial state\n return false\n }\n\n const stateOpts: RequestSnapshotOptions = {\n where: this.options.whereExpression,\n optimizedOnly: opts?.optimizedOnly ?? false,\n }\n\n if (opts) {\n if (`where` in opts) {\n const snapshotWhereExp = opts.where\n if (stateOpts.where) {\n // Combine the two where expressions\n const subWhereExp = stateOpts.where\n const combinedWhereExp = and(subWhereExp, snapshotWhereExp)\n stateOpts.where = combinedWhereExp\n } else {\n stateOpts.where = snapshotWhereExp\n }\n }\n } else {\n // No options provided so it's loading the entire initial state\n this.loadedInitialState = true\n }\n\n // Request the sync layer to load more data\n // don't await it, we will load the data into the collection when it comes in\n const loadOptions: LoadSubsetOptions = {\n where: stateOpts.where,\n subscription: this,\n }\n const syncResult = this.collection._sync.loadSubset(loadOptions)\n\n // Track this loadSubset call so we can unload it later\n this.loadedSubsets.push(loadOptions)\n\n const trackLoadSubsetPromise = opts?.trackLoadSubsetPromise ?? true\n if (trackLoadSubsetPromise) {\n this.trackLoadSubsetPromise(syncResult)\n }\n\n // Also load data immediately from the collection\n const snapshot = this.collection.currentStateAsChanges(stateOpts)\n\n if (snapshot === undefined) {\n // Couldn't load from indexes\n return false\n }\n\n // Only send changes that have not been sent yet\n const filteredSnapshot = snapshot.filter(\n (change) => !this.sentKeys.has(change.key)\n )\n\n this.snapshotSent = true\n this.callback(filteredSnapshot)\n return true\n }\n\n /**\n * Sends a snapshot that fulfills the `where` clause and all rows are bigger or equal to `minValue`.\n * Requires a range index to be set with `setOrderByIndex` prior to calling this method.\n * It uses that range index to load the items in the order of the index.\n * Note 1: it may load more rows than the provided LIMIT because it loads all values equal to `minValue` + limit values greater than `minValue`.\n * This is needed to ensure that it does not accidentally skip duplicate values when the limit falls in the middle of some duplicated values.\n * Note 2: it does not send keys that have already been sent before.\n */\n requestLimitedSnapshot({\n orderBy,\n limit,\n minValue,\n }: RequestLimitedSnapshotOptions) {\n if (!limit) throw new Error(`limit is required`)\n\n if (!this.orderByIndex) {\n throw new Error(\n `Ordered snapshot was requested but no index was found. You have to call setOrderByIndex before requesting an ordered snapshot.`\n )\n }\n\n const index = this.orderByIndex\n const where = this.options.whereExpression\n const whereFilterFn = where\n ? createFilterFunctionFromExpression(where)\n : undefined\n\n const filterFn = (key: string | number): boolean => {\n if (this.sentKeys.has(key)) {\n return false\n }\n\n const value = this.collection.get(key)\n if (value === undefined) {\n return false\n }\n\n return whereFilterFn?.(value) ?? true\n }\n\n let biggestObservedValue = minValue\n const changes: Array<ChangeMessage<any, string | number>> = []\n\n // If we have a minValue we need to handle the case\n // where there might be duplicate values equal to minValue that we need to include\n // because we can have data like this: [1, 2, 3, 3, 3, 4, 5]\n // so if minValue is 3 then the previous snapshot may not have included all 3s\n // e.g. if it was offset 0 and limit 3 it would only have loaded the first 3\n // so we load all rows equal to minValue first, to be sure we don't skip any duplicate values\n let keys: Array<string | number> = []\n if (minValue !== undefined) {\n // First, get all items with the same value as minValue\n const { expression } = orderBy[0]!\n const allRowsWithMinValue = this.collection.currentStateAsChanges({\n where: eq(expression, new Value(minValue)),\n })\n\n if (allRowsWithMinValue) {\n const keysWithMinValue = allRowsWithMinValue\n .map((change) => change.key)\n .filter((key) => !this.sentKeys.has(key) && filterFn(key))\n\n // Add items with the minValue first\n keys.push(...keysWithMinValue)\n\n // Then get items greater than minValue\n const keysGreaterThanMin = index.take(\n limit - keys.length,\n minValue,\n filterFn\n )\n keys.push(...keysGreaterThanMin)\n } else {\n keys = index.take(limit, minValue, filterFn)\n }\n } else {\n keys = index.take(limit, minValue, filterFn)\n }\n\n const valuesNeeded = () => Math.max(limit - changes.length, 0)\n const collectionExhausted = () => keys.length === 0\n\n while (valuesNeeded() > 0 && !collectionExhausted()) {\n const insertedKeys = new Set<string | number>() // Track keys we add to `changes` in this iteration\n\n for (const key of keys) {\n const value = this.collection.get(key)!\n changes.push({\n type: `insert`,\n key,\n value,\n })\n biggestObservedValue = value\n insertedKeys.add(key) // Track this key\n }\n\n keys = index.take(valuesNeeded(), biggestObservedValue, filterFn)\n }\n\n this.callback(changes)\n\n let whereWithValueFilter = where\n if (typeof minValue !== `undefined`) {\n // Only request data that we haven't seen yet (i.e. is bigger than the minValue)\n const { expression, compareOptions } = orderBy[0]!\n const operator = compareOptions.direction === `asc` ? gt : lt\n const valueFilter = operator(expression, new Value(minValue))\n whereWithValueFilter = where ? and(where, valueFilter) : valueFilter\n }\n\n // Request the sync layer to load more data\n // don't await it, we will load the data into the collection when it comes in\n const loadOptions1: LoadSubsetOptions = {\n where: whereWithValueFilter,\n limit,\n orderBy,\n subscription: this,\n }\n const syncResult = this.collection._sync.loadSubset(loadOptions1)\n\n // Track this loadSubset call\n this.loadedSubsets.push(loadOptions1)\n\n // Make parallel loadSubset calls for values equal to minValue and values greater than minValue\n const promises: Array<Promise<void>> = []\n\n // First promise: load all values equal to minValue\n if (typeof minValue !== `undefined`) {\n const { expression } = orderBy[0]!\n const exactValueFilter = eq(expression, new Value(minValue))\n\n const loadOptions2: LoadSubsetOptions = {\n where: exactValueFilter,\n subscription: this,\n }\n const equalValueResult = this.collection._sync.loadSubset(loadOptions2)\n\n // Track this loadSubset call\n this.loadedSubsets.push(loadOptions2)\n\n if (equalValueResult instanceof Promise) {\n promises.push(equalValueResult)\n }\n }\n\n // Second promise: load values greater than minValue\n if (syncResult instanceof Promise) {\n promises.push(syncResult)\n }\n\n // Track the combined promise\n if (promises.length > 0) {\n const combinedPromise = Promise.all(promises).then(() => {})\n this.trackLoadSubsetPromise(combinedPromise)\n } else {\n this.trackLoadSubsetPromise(syncResult)\n }\n }\n\n // TODO: also add similar test but that checks that it can also load it from the collection's loadSubset function\n // and that that also works properly (i.e. does not skip duplicate values)\n\n /**\n * Filters and flips changes for keys that have not been sent yet.\n * Deletes are filtered out for keys that have not been sent yet.\n * Updates are flipped into inserts for keys that have not been sent yet.\n */\n private filterAndFlipChanges(changes: Array<ChangeMessage<any, any>>) {\n if (this.loadedInitialState) {\n // We loaded the entire initial state\n // so no need to filter or flip changes\n return changes\n }\n\n const newChanges = []\n for (const change of changes) {\n let newChange = change\n if (!this.sentKeys.has(change.key)) {\n if (change.type === `update`) {\n newChange = { ...change, type: `insert`, previousValue: undefined }\n } else if (change.type === `delete`) {\n // filter out deletes for keys that have not been sent\n continue\n }\n this.sentKeys.add(change.key)\n }\n newChanges.push(newChange)\n }\n return newChanges\n }\n\n private trackSentKeys(changes: Array<ChangeMessage<any, string | number>>) {\n if (this.loadedInitialState) {\n // No need to track sent keys if we loaded the entire state.\n // Since we sent everything, all keys must have been observed.\n return\n }\n\n for (const change of changes) {\n this.sentKeys.add(change.key)\n }\n }\n\n unsubscribe() {\n // Unload all subsets that this subscription loaded\n // We pass the exact same LoadSubsetOptions we used for loadSubset\n for (const options of this.loadedSubsets) {\n this.collection._sync.unloadSubset(options)\n }\n this.loadedSubsets = []\n\n this.emitInner(`unsubscribed`, {\n type: `unsubscribed`,\n subscription: this,\n })\n // Clear all event listeners to prevent memory leaks\n this.clearListeners()\n }\n}\n"],"names":["EventEmitter","ensureIndexForExpression","createFilteredCallback","and","createFilterFunctionFromExpression","eq","Value","gt","lt"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"subscription.cjs","sources":["../../../src/collection/subscription.ts"],"sourcesContent":["import { ensureIndexForExpression } from \"../indexes/auto-index.js\"\nimport { and, eq, gt, gte, lt } from \"../query/builder/functions.js\"\nimport { Value } from \"../query/ir.js\"\nimport { EventEmitter } from \"../event-emitter.js\"\nimport {\n createFilterFunctionFromExpression,\n createFilteredCallback,\n} from \"./change-events.js\"\nimport type { BasicExpression, OrderBy } from \"../query/ir.js\"\nimport type { IndexInterface } from \"../indexes/base-index.js\"\nimport type {\n ChangeMessage,\n LoadSubsetOptions,\n Subscription,\n SubscriptionEvents,\n SubscriptionStatus,\n SubscriptionUnsubscribedEvent,\n} from \"../types.js\"\nimport type { CollectionImpl } from \"./index.js\"\n\ntype RequestSnapshotOptions = {\n where?: BasicExpression<boolean>\n optimizedOnly?: boolean\n trackLoadSubsetPromise?: boolean\n}\n\ntype RequestLimitedSnapshotOptions = {\n orderBy: OrderBy\n limit: number\n minValue?: any\n}\n\ntype CollectionSubscriptionOptions = {\n includeInitialState?: boolean\n /** Pre-compiled expression for filtering changes */\n whereExpression?: BasicExpression<boolean>\n /** Callback to call when the subscription is unsubscribed */\n onUnsubscribe?: (event: SubscriptionUnsubscribedEvent) => void\n}\n\nexport class CollectionSubscription\n extends EventEmitter<SubscriptionEvents>\n implements Subscription\n{\n private loadedInitialState = false\n\n // Flag to indicate that we have sent at least 1 snapshot.\n // While `snapshotSent` is false we filter out all changes from subscription to the collection.\n private snapshotSent = false\n\n /**\n * Track all loadSubset calls made by this subscription so we can unload them on cleanup.\n * We store the exact LoadSubsetOptions we passed to loadSubset to ensure symmetric unload.\n */\n private loadedSubsets: Array<LoadSubsetOptions> = []\n\n // Keep track of the keys we've sent (needed for join and orderBy optimizations)\n private sentKeys = new Set<string | number>()\n\n private filteredCallback: (changes: Array<ChangeMessage<any, any>>) => void\n\n private orderByIndex: IndexInterface<string | number> | undefined\n\n // Status tracking\n private _status: SubscriptionStatus = `ready`\n private pendingLoadSubsetPromises: Set<Promise<void>> = new Set()\n\n public get status(): SubscriptionStatus {\n return this._status\n }\n\n constructor(\n private collection: CollectionImpl<any, any, any, any, any>,\n private callback: (changes: Array<ChangeMessage<any, any>>) => void,\n private options: CollectionSubscriptionOptions\n ) {\n super()\n if (options.onUnsubscribe) {\n this.on(`unsubscribed`, (event) => options.onUnsubscribe!(event))\n }\n\n // Auto-index for where expressions if enabled\n if (options.whereExpression) {\n ensureIndexForExpression(options.whereExpression, this.collection)\n }\n\n const callbackWithSentKeysTracking = (\n changes: Array<ChangeMessage<any, any>>\n ) => {\n callback(changes)\n this.trackSentKeys(changes)\n }\n\n this.callback = callbackWithSentKeysTracking\n\n // Create a filtered callback if where clause is provided\n this.filteredCallback = options.whereExpression\n ? createFilteredCallback(this.callback, options)\n : this.callback\n }\n\n setOrderByIndex(index: IndexInterface<any>) {\n this.orderByIndex = index\n }\n\n /**\n * Set subscription status and emit events if changed\n */\n private setStatus(newStatus: SubscriptionStatus) {\n if (this._status === newStatus) {\n return // No change\n }\n\n const previousStatus = this._status\n this._status = newStatus\n\n // Emit status:change event\n this.emitInner(`status:change`, {\n type: `status:change`,\n subscription: this,\n previousStatus,\n status: newStatus,\n })\n\n // Emit specific status event\n const eventKey: `status:${SubscriptionStatus}` = `status:${newStatus}`\n this.emitInner(eventKey, {\n type: eventKey,\n subscription: this,\n previousStatus,\n status: newStatus,\n } as SubscriptionEvents[typeof eventKey])\n }\n\n /**\n * Track a loadSubset promise and manage loading status\n */\n private trackLoadSubsetPromise(syncResult: Promise<void> | true) {\n // Track the promise if it's actually a promise (async work)\n if (syncResult instanceof Promise) {\n this.pendingLoadSubsetPromises.add(syncResult)\n this.setStatus(`loadingSubset`)\n\n syncResult.finally(() => {\n this.pendingLoadSubsetPromises.delete(syncResult)\n if (this.pendingLoadSubsetPromises.size === 0) {\n this.setStatus(`ready`)\n }\n })\n }\n }\n\n hasLoadedInitialState() {\n return this.loadedInitialState\n }\n\n hasSentAtLeastOneSnapshot() {\n return this.snapshotSent\n }\n\n emitEvents(changes: Array<ChangeMessage<any, any>>) {\n const newChanges = this.filterAndFlipChanges(changes)\n this.filteredCallback(newChanges)\n }\n\n /**\n * Sends the snapshot to the callback.\n * Returns a boolean indicating if it succeeded.\n * It can only fail if there is no index to fulfill the request\n * and the optimizedOnly option is set to true,\n * or, the entire state was already loaded.\n */\n requestSnapshot(opts?: RequestSnapshotOptions): boolean {\n if (this.loadedInitialState) {\n // Subscription was deoptimized so we already sent the entire initial state\n return false\n }\n\n const stateOpts: RequestSnapshotOptions = {\n where: this.options.whereExpression,\n optimizedOnly: opts?.optimizedOnly ?? false,\n }\n\n if (opts) {\n if (`where` in opts) {\n const snapshotWhereExp = opts.where\n if (stateOpts.where) {\n // Combine the two where expressions\n const subWhereExp = stateOpts.where\n const combinedWhereExp = and(subWhereExp, snapshotWhereExp)\n stateOpts.where = combinedWhereExp\n } else {\n stateOpts.where = snapshotWhereExp\n }\n }\n } else {\n // No options provided so it's loading the entire initial state\n this.loadedInitialState = true\n }\n\n // Request the sync layer to load more data\n // don't await it, we will load the data into the collection when it comes in\n const loadOptions: LoadSubsetOptions = {\n where: stateOpts.where,\n subscription: this,\n }\n const syncResult = this.collection._sync.loadSubset(loadOptions)\n\n // Track this loadSubset call so we can unload it later\n this.loadedSubsets.push(loadOptions)\n\n const trackLoadSubsetPromise = opts?.trackLoadSubsetPromise ?? true\n if (trackLoadSubsetPromise) {\n this.trackLoadSubsetPromise(syncResult)\n }\n\n // Also load data immediately from the collection\n const snapshot = this.collection.currentStateAsChanges(stateOpts)\n\n if (snapshot === undefined) {\n // Couldn't load from indexes\n return false\n }\n\n // Only send changes that have not been sent yet\n const filteredSnapshot = snapshot.filter(\n (change) => !this.sentKeys.has(change.key)\n )\n\n this.snapshotSent = true\n this.callback(filteredSnapshot)\n return true\n }\n\n /**\n * Sends a snapshot that fulfills the `where` clause and all rows are bigger or equal to `minValue`.\n * Requires a range index to be set with `setOrderByIndex` prior to calling this method.\n * It uses that range index to load the items in the order of the index.\n * Note 1: it may load more rows than the provided LIMIT because it loads all values equal to `minValue` + limit values greater than `minValue`.\n * This is needed to ensure that it does not accidentally skip duplicate values when the limit falls in the middle of some duplicated values.\n * Note 2: it does not send keys that have already been sent before.\n */\n requestLimitedSnapshot({\n orderBy,\n limit,\n minValue,\n }: RequestLimitedSnapshotOptions) {\n if (!limit) throw new Error(`limit is required`)\n\n if (!this.orderByIndex) {\n throw new Error(\n `Ordered snapshot was requested but no index was found. You have to call setOrderByIndex before requesting an ordered snapshot.`\n )\n }\n\n const index = this.orderByIndex\n const where = this.options.whereExpression\n const whereFilterFn = where\n ? createFilterFunctionFromExpression(where)\n : undefined\n\n const filterFn = (key: string | number): boolean => {\n if (this.sentKeys.has(key)) {\n return false\n }\n\n const value = this.collection.get(key)\n if (value === undefined) {\n return false\n }\n\n return whereFilterFn?.(value) ?? true\n }\n\n let biggestObservedValue = minValue\n const changes: Array<ChangeMessage<any, string | number>> = []\n\n // If we have a minValue we need to handle the case\n // where there might be duplicate values equal to minValue that we need to include\n // because we can have data like this: [1, 2, 3, 3, 3, 4, 5]\n // so if minValue is 3 then the previous snapshot may not have included all 3s\n // e.g. if it was offset 0 and limit 3 it would only have loaded the first 3\n // so we load all rows equal to minValue first, to be sure we don't skip any duplicate values\n let keys: Array<string | number> = []\n if (minValue !== undefined) {\n // First, get all items with the same value as minValue\n const { expression } = orderBy[0]!\n const allRowsWithMinValue = this.collection.currentStateAsChanges({\n where: eq(expression, new Value(minValue)),\n })\n\n if (allRowsWithMinValue) {\n const keysWithMinValue = allRowsWithMinValue\n .map((change) => change.key)\n .filter((key) => !this.sentKeys.has(key) && filterFn(key))\n\n // Add items with the minValue first\n keys.push(...keysWithMinValue)\n\n // Then get items greater than minValue\n const keysGreaterThanMin = index.take(\n limit - keys.length,\n minValue,\n filterFn\n )\n keys.push(...keysGreaterThanMin)\n } else {\n keys = index.take(limit, minValue, filterFn)\n }\n } else {\n keys = index.take(limit, minValue, filterFn)\n }\n\n const valuesNeeded = () => Math.max(limit - changes.length, 0)\n const collectionExhausted = () => keys.length === 0\n\n while (valuesNeeded() > 0 && !collectionExhausted()) {\n const insertedKeys = new Set<string | number>() // Track keys we add to `changes` in this iteration\n\n for (const key of keys) {\n const value = this.collection.get(key)!\n changes.push({\n type: `insert`,\n key,\n value,\n })\n biggestObservedValue = value\n insertedKeys.add(key) // Track this key\n }\n\n keys = index.take(valuesNeeded(), biggestObservedValue, filterFn)\n }\n\n this.callback(changes)\n\n let whereWithValueFilter = where\n if (typeof minValue !== `undefined`) {\n // Only request data that we haven't seen yet (i.e. is bigger than the minValue)\n const { expression, compareOptions } = orderBy[0]!\n const operator = compareOptions.direction === `asc` ? gt : lt\n const valueFilter = operator(expression, new Value(minValue))\n whereWithValueFilter = where ? and(where, valueFilter) : valueFilter\n }\n\n // Request the sync layer to load more data\n // don't await it, we will load the data into the collection when it comes in\n const loadOptions1: LoadSubsetOptions = {\n where: whereWithValueFilter,\n limit,\n orderBy,\n subscription: this,\n }\n const syncResult = this.collection._sync.loadSubset(loadOptions1)\n\n // Track this loadSubset call\n this.loadedSubsets.push(loadOptions1)\n\n // Make parallel loadSubset calls for values equal to minValue and values greater than minValue\n const promises: Array<Promise<void>> = []\n\n // First promise: load all values equal to minValue\n if (typeof minValue !== `undefined`) {\n const { expression } = orderBy[0]!\n\n // For Date values, we need to handle precision differences between JS (ms) and backends (μs)\n // A JS Date represents a 1ms range, so we query for all values within that range\n let exactValueFilter\n if (minValue instanceof Date) {\n const minValuePlus1ms = new Date(minValue.getTime() + 1)\n exactValueFilter = and(\n gte(expression, new Value(minValue)),\n lt(expression, new Value(minValuePlus1ms))\n )\n } else {\n exactValueFilter = eq(expression, new Value(minValue))\n }\n\n const loadOptions2: LoadSubsetOptions = {\n where: exactValueFilter,\n subscription: this,\n }\n const equalValueResult = this.collection._sync.loadSubset(loadOptions2)\n\n // Track this loadSubset call\n this.loadedSubsets.push(loadOptions2)\n\n if (equalValueResult instanceof Promise) {\n promises.push(equalValueResult)\n }\n }\n\n // Second promise: load values greater than minValue\n if (syncResult instanceof Promise) {\n promises.push(syncResult)\n }\n\n // Track the combined promise\n if (promises.length > 0) {\n const combinedPromise = Promise.all(promises).then(() => {})\n this.trackLoadSubsetPromise(combinedPromise)\n } else {\n this.trackLoadSubsetPromise(syncResult)\n }\n }\n\n // TODO: also add similar test but that checks that it can also load it from the collection's loadSubset function\n // and that that also works properly (i.e. does not skip duplicate values)\n\n /**\n * Filters and flips changes for keys that have not been sent yet.\n * Deletes are filtered out for keys that have not been sent yet.\n * Updates are flipped into inserts for keys that have not been sent yet.\n */\n private filterAndFlipChanges(changes: Array<ChangeMessage<any, any>>) {\n if (this.loadedInitialState) {\n // We loaded the entire initial state\n // so no need to filter or flip changes\n return changes\n }\n\n const newChanges = []\n for (const change of changes) {\n let newChange = change\n if (!this.sentKeys.has(change.key)) {\n if (change.type === `update`) {\n newChange = { ...change, type: `insert`, previousValue: undefined }\n } else if (change.type === `delete`) {\n // filter out deletes for keys that have not been sent\n continue\n }\n this.sentKeys.add(change.key)\n }\n newChanges.push(newChange)\n }\n return newChanges\n }\n\n private trackSentKeys(changes: Array<ChangeMessage<any, string | number>>) {\n if (this.loadedInitialState) {\n // No need to track sent keys if we loaded the entire state.\n // Since we sent everything, all keys must have been observed.\n return\n }\n\n for (const change of changes) {\n this.sentKeys.add(change.key)\n }\n }\n\n unsubscribe() {\n // Unload all subsets that this subscription loaded\n // We pass the exact same LoadSubsetOptions we used for loadSubset\n for (const options of this.loadedSubsets) {\n this.collection._sync.unloadSubset(options)\n }\n this.loadedSubsets = []\n\n this.emitInner(`unsubscribed`, {\n type: `unsubscribed`,\n subscription: this,\n })\n // Clear all event listeners to prevent memory leaks\n this.clearListeners()\n }\n}\n"],"names":["EventEmitter","ensureIndexForExpression","createFilteredCallback","and","createFilterFunctionFromExpression","eq","Value","gt","lt","gte"],"mappings":"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"}
|
package/dist/cjs/proxy.cjs
CHANGED
|
@@ -1,6 +1,261 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const utils = require("./utils.cjs");
|
|
4
|
+
const CALLBACK_ITERATION_METHODS = /* @__PURE__ */ new Set([
|
|
5
|
+
`find`,
|
|
6
|
+
`findLast`,
|
|
7
|
+
`findIndex`,
|
|
8
|
+
`findLastIndex`,
|
|
9
|
+
`filter`,
|
|
10
|
+
`map`,
|
|
11
|
+
`flatMap`,
|
|
12
|
+
`forEach`,
|
|
13
|
+
`some`,
|
|
14
|
+
`every`,
|
|
15
|
+
`reduce`,
|
|
16
|
+
`reduceRight`
|
|
17
|
+
]);
|
|
18
|
+
const ARRAY_MODIFYING_METHODS = /* @__PURE__ */ new Set([
|
|
19
|
+
`pop`,
|
|
20
|
+
`push`,
|
|
21
|
+
`shift`,
|
|
22
|
+
`unshift`,
|
|
23
|
+
`splice`,
|
|
24
|
+
`sort`,
|
|
25
|
+
`reverse`,
|
|
26
|
+
`fill`,
|
|
27
|
+
`copyWithin`
|
|
28
|
+
]);
|
|
29
|
+
const MAP_SET_MODIFYING_METHODS = /* @__PURE__ */ new Set([`set`, `delete`, `clear`, `add`]);
|
|
30
|
+
const MAP_SET_ITERATOR_METHODS = /* @__PURE__ */ new Set([
|
|
31
|
+
`entries`,
|
|
32
|
+
`keys`,
|
|
33
|
+
`values`,
|
|
34
|
+
`forEach`
|
|
35
|
+
]);
|
|
36
|
+
function isProxiableObject(value) {
|
|
37
|
+
return value !== null && typeof value === `object` && !(value instanceof Date) && !(value instanceof RegExp) && !utils.isTemporal(value);
|
|
38
|
+
}
|
|
39
|
+
function createArrayIterationHandler(methodName, methodFn, changeTracker, memoizedCreateChangeProxy) {
|
|
40
|
+
if (!CALLBACK_ITERATION_METHODS.has(methodName)) {
|
|
41
|
+
return void 0;
|
|
42
|
+
}
|
|
43
|
+
return function(...args) {
|
|
44
|
+
const callback = args[0];
|
|
45
|
+
if (typeof callback !== `function`) {
|
|
46
|
+
return methodFn.apply(changeTracker.copy_, args);
|
|
47
|
+
}
|
|
48
|
+
const getProxiedElement = (element, index) => {
|
|
49
|
+
if (isProxiableObject(element)) {
|
|
50
|
+
const nestedParent = {
|
|
51
|
+
tracker: changeTracker,
|
|
52
|
+
prop: String(index)
|
|
53
|
+
};
|
|
54
|
+
const { proxy: elementProxy } = memoizedCreateChangeProxy(
|
|
55
|
+
element,
|
|
56
|
+
nestedParent
|
|
57
|
+
);
|
|
58
|
+
return elementProxy;
|
|
59
|
+
}
|
|
60
|
+
return element;
|
|
61
|
+
};
|
|
62
|
+
const wrappedCallback = function(element, index, array) {
|
|
63
|
+
const proxiedElement = getProxiedElement(element, index);
|
|
64
|
+
return callback.call(this, proxiedElement, index, array);
|
|
65
|
+
};
|
|
66
|
+
if (methodName === `reduce` || methodName === `reduceRight`) {
|
|
67
|
+
const reduceCallback = function(accumulator, element, index, array) {
|
|
68
|
+
const proxiedElement = getProxiedElement(element, index);
|
|
69
|
+
return callback.call(this, accumulator, proxiedElement, index, array);
|
|
70
|
+
};
|
|
71
|
+
return methodFn.apply(changeTracker.copy_, [
|
|
72
|
+
reduceCallback,
|
|
73
|
+
...args.slice(1)
|
|
74
|
+
]);
|
|
75
|
+
}
|
|
76
|
+
const result = methodFn.apply(changeTracker.copy_, [
|
|
77
|
+
wrappedCallback,
|
|
78
|
+
...args.slice(1)
|
|
79
|
+
]);
|
|
80
|
+
if ((methodName === `find` || methodName === `findLast`) && result && typeof result === `object`) {
|
|
81
|
+
const foundIndex = changeTracker.copy_.indexOf(result);
|
|
82
|
+
if (foundIndex !== -1) {
|
|
83
|
+
return getProxiedElement(result, foundIndex);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (methodName === `filter` && Array.isArray(result)) {
|
|
87
|
+
return result.map((element) => {
|
|
88
|
+
const originalIndex = changeTracker.copy_.indexOf(element);
|
|
89
|
+
if (originalIndex !== -1) {
|
|
90
|
+
return getProxiedElement(element, originalIndex);
|
|
91
|
+
}
|
|
92
|
+
return element;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function createArrayIteratorHandler(changeTracker, memoizedCreateChangeProxy) {
|
|
99
|
+
return function() {
|
|
100
|
+
const array = changeTracker.copy_;
|
|
101
|
+
let index = 0;
|
|
102
|
+
return {
|
|
103
|
+
next() {
|
|
104
|
+
if (index >= array.length) {
|
|
105
|
+
return { done: true, value: void 0 };
|
|
106
|
+
}
|
|
107
|
+
const element = array[index];
|
|
108
|
+
let proxiedElement = element;
|
|
109
|
+
if (isProxiableObject(element)) {
|
|
110
|
+
const nestedParent = {
|
|
111
|
+
tracker: changeTracker,
|
|
112
|
+
prop: String(index)
|
|
113
|
+
};
|
|
114
|
+
const { proxy: elementProxy } = memoizedCreateChangeProxy(
|
|
115
|
+
element,
|
|
116
|
+
nestedParent
|
|
117
|
+
);
|
|
118
|
+
proxiedElement = elementProxy;
|
|
119
|
+
}
|
|
120
|
+
index++;
|
|
121
|
+
return { done: false, value: proxiedElement };
|
|
122
|
+
},
|
|
123
|
+
[Symbol.iterator]() {
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function createModifyingMethodHandler(methodFn, changeTracker, markChanged) {
|
|
130
|
+
return function(...args) {
|
|
131
|
+
const result = methodFn.apply(changeTracker.copy_, args);
|
|
132
|
+
markChanged(changeTracker);
|
|
133
|
+
return result;
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function createMapSetIteratorHandler(methodName, prop, methodFn, target, changeTracker, memoizedCreateChangeProxy, markChanged) {
|
|
137
|
+
const isIteratorMethod = MAP_SET_ITERATOR_METHODS.has(methodName) || prop === Symbol.iterator;
|
|
138
|
+
if (!isIteratorMethod) {
|
|
139
|
+
return void 0;
|
|
140
|
+
}
|
|
141
|
+
return function(...args) {
|
|
142
|
+
const result = methodFn.apply(changeTracker.copy_, args);
|
|
143
|
+
if (methodName === `forEach`) {
|
|
144
|
+
const callback = args[0];
|
|
145
|
+
if (typeof callback === `function`) {
|
|
146
|
+
const wrappedCallback = function(value, key, collection) {
|
|
147
|
+
const cbresult = callback.call(this, value, key, collection);
|
|
148
|
+
markChanged(changeTracker);
|
|
149
|
+
return cbresult;
|
|
150
|
+
};
|
|
151
|
+
return methodFn.apply(target, [wrappedCallback, ...args.slice(1)]);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const isValueIterator = methodName === `entries` || methodName === `values` || methodName === Symbol.iterator.toString() || prop === Symbol.iterator;
|
|
155
|
+
if (isValueIterator) {
|
|
156
|
+
const originalIterator = result;
|
|
157
|
+
const valueToKeyMap = /* @__PURE__ */ new Map();
|
|
158
|
+
if (methodName === `values` && target instanceof Map) {
|
|
159
|
+
for (const [key, mapValue] of changeTracker.copy_.entries()) {
|
|
160
|
+
valueToKeyMap.set(mapValue, key);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const originalToModifiedMap = /* @__PURE__ */ new Map();
|
|
164
|
+
if (target instanceof Set) {
|
|
165
|
+
for (const setValue of changeTracker.copy_.values()) {
|
|
166
|
+
originalToModifiedMap.set(setValue, setValue);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
next() {
|
|
171
|
+
const nextResult = originalIterator.next();
|
|
172
|
+
if (!nextResult.done && nextResult.value && typeof nextResult.value === `object`) {
|
|
173
|
+
if (methodName === `entries` && Array.isArray(nextResult.value) && nextResult.value.length === 2) {
|
|
174
|
+
if (nextResult.value[1] && typeof nextResult.value[1] === `object`) {
|
|
175
|
+
const mapKey = nextResult.value[0];
|
|
176
|
+
const mapParent = {
|
|
177
|
+
tracker: changeTracker,
|
|
178
|
+
prop: mapKey,
|
|
179
|
+
updateMap: (newValue) => {
|
|
180
|
+
if (changeTracker.copy_ instanceof Map) {
|
|
181
|
+
changeTracker.copy_.set(
|
|
182
|
+
mapKey,
|
|
183
|
+
newValue
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
const { proxy: valueProxy } = memoizedCreateChangeProxy(
|
|
189
|
+
nextResult.value[1],
|
|
190
|
+
mapParent
|
|
191
|
+
);
|
|
192
|
+
nextResult.value[1] = valueProxy;
|
|
193
|
+
}
|
|
194
|
+
} else if (methodName === `values` || methodName === Symbol.iterator.toString() || prop === Symbol.iterator) {
|
|
195
|
+
if (methodName === `values` && target instanceof Map) {
|
|
196
|
+
const mapKey = valueToKeyMap.get(nextResult.value);
|
|
197
|
+
if (mapKey !== void 0) {
|
|
198
|
+
const mapParent = {
|
|
199
|
+
tracker: changeTracker,
|
|
200
|
+
prop: mapKey,
|
|
201
|
+
updateMap: (newValue) => {
|
|
202
|
+
if (changeTracker.copy_ instanceof Map) {
|
|
203
|
+
changeTracker.copy_.set(
|
|
204
|
+
mapKey,
|
|
205
|
+
newValue
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
const { proxy: valueProxy } = memoizedCreateChangeProxy(
|
|
211
|
+
nextResult.value,
|
|
212
|
+
mapParent
|
|
213
|
+
);
|
|
214
|
+
nextResult.value = valueProxy;
|
|
215
|
+
}
|
|
216
|
+
} else if (target instanceof Set) {
|
|
217
|
+
const setOriginalValue = nextResult.value;
|
|
218
|
+
const setParent = {
|
|
219
|
+
tracker: changeTracker,
|
|
220
|
+
prop: setOriginalValue,
|
|
221
|
+
updateSet: (newValue) => {
|
|
222
|
+
if (changeTracker.copy_ instanceof Set) {
|
|
223
|
+
changeTracker.copy_.delete(
|
|
224
|
+
setOriginalValue
|
|
225
|
+
);
|
|
226
|
+
changeTracker.copy_.add(newValue);
|
|
227
|
+
originalToModifiedMap.set(setOriginalValue, newValue);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
const { proxy: valueProxy } = memoizedCreateChangeProxy(
|
|
232
|
+
nextResult.value,
|
|
233
|
+
setParent
|
|
234
|
+
);
|
|
235
|
+
nextResult.value = valueProxy;
|
|
236
|
+
} else {
|
|
237
|
+
const tempKey = Symbol(`iterator-value`);
|
|
238
|
+
const { proxy: valueProxy } = memoizedCreateChangeProxy(
|
|
239
|
+
nextResult.value,
|
|
240
|
+
{
|
|
241
|
+
tracker: changeTracker,
|
|
242
|
+
prop: tempKey
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
nextResult.value = valueProxy;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return nextResult;
|
|
250
|
+
},
|
|
251
|
+
[Symbol.iterator]() {
|
|
252
|
+
return this;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
return result;
|
|
257
|
+
};
|
|
258
|
+
}
|
|
4
259
|
function debugLog(...args) {
|
|
5
260
|
const isBrowser = typeof window !== `undefined` && typeof localStorage !== `undefined`;
|
|
6
261
|
if (isBrowser && localStorage.getItem(`DEBUG`) === `true`) {
|
|
@@ -213,182 +468,54 @@ function createChangeProxy(target, parent) {
|
|
|
213
468
|
if (typeof value === `function`) {
|
|
214
469
|
if (Array.isArray(ptarget)) {
|
|
215
470
|
const methodName = prop.toString();
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
471
|
+
if (ARRAY_MODIFYING_METHODS.has(methodName)) {
|
|
472
|
+
return createModifyingMethodHandler(
|
|
473
|
+
value,
|
|
474
|
+
changeTracker,
|
|
475
|
+
markChanged
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
const iterationHandler = createArrayIterationHandler(
|
|
479
|
+
methodName,
|
|
480
|
+
value,
|
|
481
|
+
changeTracker,
|
|
482
|
+
memoizedCreateChangeProxy
|
|
483
|
+
);
|
|
484
|
+
if (iterationHandler) {
|
|
485
|
+
return iterationHandler;
|
|
486
|
+
}
|
|
487
|
+
if (prop === Symbol.iterator) {
|
|
488
|
+
return createArrayIteratorHandler(
|
|
489
|
+
changeTracker,
|
|
490
|
+
memoizedCreateChangeProxy
|
|
491
|
+
);
|
|
233
492
|
}
|
|
234
493
|
}
|
|
235
494
|
if (ptarget instanceof Map || ptarget instanceof Set) {
|
|
236
495
|
const methodName = prop.toString();
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
`push`,
|
|
244
|
-
`shift`,
|
|
245
|
-
`unshift`,
|
|
246
|
-
`splice`,
|
|
247
|
-
`sort`,
|
|
248
|
-
`reverse`
|
|
249
|
-
]);
|
|
250
|
-
if (modifyingMethods.has(methodName)) {
|
|
251
|
-
return function(...args) {
|
|
252
|
-
const result = value.apply(changeTracker.copy_, args);
|
|
253
|
-
markChanged(changeTracker);
|
|
254
|
-
return result;
|
|
255
|
-
};
|
|
496
|
+
if (MAP_SET_MODIFYING_METHODS.has(methodName)) {
|
|
497
|
+
return createModifyingMethodHandler(
|
|
498
|
+
value,
|
|
499
|
+
changeTracker,
|
|
500
|
+
markChanged
|
|
501
|
+
);
|
|
256
502
|
}
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const callback = args[0];
|
|
269
|
-
if (typeof callback === `function`) {
|
|
270
|
-
const wrappedCallback = function(value2, key, collection) {
|
|
271
|
-
const cbresult = callback.call(
|
|
272
|
-
this,
|
|
273
|
-
value2,
|
|
274
|
-
key,
|
|
275
|
-
collection
|
|
276
|
-
);
|
|
277
|
-
markChanged(changeTracker);
|
|
278
|
-
return cbresult;
|
|
279
|
-
};
|
|
280
|
-
return value.apply(ptarget, [
|
|
281
|
-
wrappedCallback,
|
|
282
|
-
...args.slice(1)
|
|
283
|
-
]);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
if (methodName === `entries` || methodName === `values` || methodName === Symbol.iterator.toString() || prop === Symbol.iterator) {
|
|
287
|
-
const originalIterator = result;
|
|
288
|
-
const valueToKeyMap = /* @__PURE__ */ new Map();
|
|
289
|
-
if (methodName === `values` && ptarget instanceof Map) {
|
|
290
|
-
for (const [
|
|
291
|
-
key,
|
|
292
|
-
mapValue
|
|
293
|
-
] of changeTracker.copy_.entries()) {
|
|
294
|
-
valueToKeyMap.set(mapValue, key);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
const originalToModifiedMap = /* @__PURE__ */ new Map();
|
|
298
|
-
if (ptarget instanceof Set) {
|
|
299
|
-
for (const setValue of changeTracker.copy_.values()) {
|
|
300
|
-
originalToModifiedMap.set(setValue, setValue);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return {
|
|
304
|
-
next() {
|
|
305
|
-
const nextResult = originalIterator.next();
|
|
306
|
-
if (!nextResult.done && nextResult.value && typeof nextResult.value === `object`) {
|
|
307
|
-
if (methodName === `entries` && Array.isArray(nextResult.value) && nextResult.value.length === 2) {
|
|
308
|
-
if (nextResult.value[1] && typeof nextResult.value[1] === `object`) {
|
|
309
|
-
const mapKey = nextResult.value[0];
|
|
310
|
-
const mapParent = {
|
|
311
|
-
tracker: changeTracker,
|
|
312
|
-
prop: mapKey,
|
|
313
|
-
updateMap: (newValue) => {
|
|
314
|
-
if (changeTracker.copy_ instanceof Map) {
|
|
315
|
-
changeTracker.copy_.set(mapKey, newValue);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
};
|
|
319
|
-
const { proxy: valueProxy } = memoizedCreateChangeProxy(
|
|
320
|
-
nextResult.value[1],
|
|
321
|
-
mapParent
|
|
322
|
-
);
|
|
323
|
-
nextResult.value[1] = valueProxy;
|
|
324
|
-
}
|
|
325
|
-
} else if (methodName === `values` || methodName === Symbol.iterator.toString() || prop === Symbol.iterator) {
|
|
326
|
-
if (typeof nextResult.value === `object` && nextResult.value !== null) {
|
|
327
|
-
if (methodName === `values` && ptarget instanceof Map) {
|
|
328
|
-
const mapKey = valueToKeyMap.get(nextResult.value);
|
|
329
|
-
if (mapKey !== void 0) {
|
|
330
|
-
const mapParent = {
|
|
331
|
-
tracker: changeTracker,
|
|
332
|
-
prop: mapKey,
|
|
333
|
-
updateMap: (newValue) => {
|
|
334
|
-
if (changeTracker.copy_ instanceof Map) {
|
|
335
|
-
changeTracker.copy_.set(mapKey, newValue);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
};
|
|
339
|
-
const { proxy: valueProxy } = memoizedCreateChangeProxy(
|
|
340
|
-
nextResult.value,
|
|
341
|
-
mapParent
|
|
342
|
-
);
|
|
343
|
-
nextResult.value = valueProxy;
|
|
344
|
-
}
|
|
345
|
-
} else if (ptarget instanceof Set) {
|
|
346
|
-
const setOriginalValue = nextResult.value;
|
|
347
|
-
const setParent = {
|
|
348
|
-
tracker: changeTracker,
|
|
349
|
-
prop: setOriginalValue,
|
|
350
|
-
// Use the original value as the prop
|
|
351
|
-
updateSet: (newValue) => {
|
|
352
|
-
if (changeTracker.copy_ instanceof Set) {
|
|
353
|
-
changeTracker.copy_.delete(setOriginalValue);
|
|
354
|
-
changeTracker.copy_.add(newValue);
|
|
355
|
-
originalToModifiedMap.set(
|
|
356
|
-
setOriginalValue,
|
|
357
|
-
newValue
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
const { proxy: valueProxy } = memoizedCreateChangeProxy(
|
|
363
|
-
nextResult.value,
|
|
364
|
-
setParent
|
|
365
|
-
);
|
|
366
|
-
nextResult.value = valueProxy;
|
|
367
|
-
} else {
|
|
368
|
-
const tempKey = Symbol(`iterator-value`);
|
|
369
|
-
const { proxy: valueProxy } = memoizedCreateChangeProxy(nextResult.value, {
|
|
370
|
-
tracker: changeTracker,
|
|
371
|
-
prop: tempKey
|
|
372
|
-
});
|
|
373
|
-
nextResult.value = valueProxy;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
return nextResult;
|
|
379
|
-
},
|
|
380
|
-
[Symbol.iterator]() {
|
|
381
|
-
return this;
|
|
382
|
-
}
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
return result;
|
|
386
|
-
};
|
|
503
|
+
const iteratorHandler = createMapSetIteratorHandler(
|
|
504
|
+
methodName,
|
|
505
|
+
prop,
|
|
506
|
+
value,
|
|
507
|
+
ptarget,
|
|
508
|
+
changeTracker,
|
|
509
|
+
memoizedCreateChangeProxy,
|
|
510
|
+
markChanged
|
|
511
|
+
);
|
|
512
|
+
if (iteratorHandler) {
|
|
513
|
+
return iteratorHandler;
|
|
387
514
|
}
|
|
388
515
|
}
|
|
389
516
|
return value.bind(ptarget);
|
|
390
517
|
}
|
|
391
|
-
if (
|
|
518
|
+
if (isProxiableObject(value)) {
|
|
392
519
|
const nestedParent = {
|
|
393
520
|
tracker: changeTracker,
|
|
394
521
|
prop: String(prop)
|