diff --git a/.gitignore b/.gitignore index cff47f7e..9f267e9b 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,9 @@ dist .tern-port benchmark.csv + +# IntelliJ +/.idea + +# MacOS Finder files +.DS_Store diff --git a/benchmark.jpg b/benchmark.jpg index d33eb8a0..4da51b29 100644 Binary files a/benchmark.jpg and b/benchmark.jpg differ diff --git a/global.d.ts b/global.d.ts index e05f81f7..29c10385 100644 --- a/global.d.ts +++ b/global.d.ts @@ -1,2 +1,13 @@ -// eslint-disable-next-line no-var -declare var __DEV__: boolean; + +export {} // "This does not work without at least one export (or import) keyword. This turns this file into an ES module, which is necessary for this to work. You can export any of the statements or add an empty export {}." + +declare global { + /** + * Allow __DEV__ to be used in the code, which is replaced via rollup-plugin-replace in the build process. + * WARNING: if directly executing scripts like the benchmarks via tsx or ts-node, this global variable needs to be set at the start. + * See https://stackoverflow.com/questions/59459312/using-globalthis-in-typescript. + */ + // eslint-disable-next-line no-var + // noinspection ES6ConvertVarToLetConst + var __DEV__: boolean; +} diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 00000000..6d751e64 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,27 @@ +import type {Config} from 'jest'; + +const config: Config = { + "preset": "ts-jest", + "coveragePathIgnorePatterns": [ + "/test/" + ], + "transform": { + "\\.[jt]sx?$": [ + "ts-jest", + { + "tsconfig": { + "noImplicitReturns": false, + "noFallthroughCasesInSwitch": false, + "noUnusedLocals": false, + "noUnusedParameters": false + } + } + ] + }, + "globals": { + "__DEV__": true + }, + prettierPath: require.resolve('prettier-2'), +}; + +export default config; diff --git a/package.json b/package.json index 113358b1..0f87ab3c 100644 --- a/package.json +++ b/package.json @@ -34,14 +34,17 @@ "benchmark:object": "NODE_ENV='production' ts-node test/performance/benchmark-object.ts", "benchmark:array": "NODE_ENV='production' ts-node test/performance/benchmark-array.ts", "benchmark:class": "NODE_ENV='production' ts-node test/performance/benchmark-class.ts", + "benchmark:map": "NODE_ENV='production' ts-node test/benchmark/map.ts", + "benchmark:map-batch": "NODE_ENV='production' ts-node test/benchmark/map-batch.ts", "performance:read-only": "yarn build && NODE_ENV='production' ts-node test/performance/read-draft/index.ts", "performance:immer": "cd test/__immer_performance_tests__ && NODE_ENV='production' ts-node add-data.ts && NODE_ENV='production' ts-node todo.ts && NODE_ENV='production' ts-node incremental.ts", "performance:basic": "cd test/performance && NODE_ENV='production' ts-node index.ts", "performance:set-map": "cd test/performance && NODE_ENV='production' ts-node set-map.ts", + "performance:mutative-set-map": "cd test/performance && NODE_ENV='production' ts-node mutative-set-map.ts", "performance:big-object": "cd test/performance && NODE_ENV='production' ts-node big-object.ts", "performance:sample": "cd test/performance && NODE_ENV='production' ts-node sample.ts", "performance:array-object": "cd test/performance && NODE_ENV='production' ts-node array-object.ts", - "performance": "yarn build && yarn performance:immer && yarn performance:basic && yarn performance:set-map && yarn performance:big-object && yarn performance:sample && yarn performance:array-object", + "performance": "yarn build && yarn performance:immer && yarn performance:basic && yarn performance:set-map && yarn performance:mutative-set-map && yarn performance:big-object && yarn performance:sample && yarn performance:array-object", "build": "yarn clean && rollup --config --bundleConfigAsCjs", "build:doc": "rimraf docs && typedoc --plugin typedoc-plugin-markdown --out docs src/index.ts --readme none", "commit": "yarn git-cz", @@ -85,7 +88,7 @@ "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-replace": "^5.0.5", + "@rollup/plugin-replace": "^6.0.2", "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^12.1.1", "@size-limit/esbuild": "^11.1.6", @@ -117,12 +120,15 @@ "lodash": "^4.17.21", "lodash.clonedeep": "^4.5.0", "prettier": "^3.3.3", + "prettier-2": "npm:prettier@^2", + "pretty-format": "^29.7.0", "quickchart-js": "^3.1.2", "redux": "^5.0.1", "rimraf": "^3.0.2", - "rollup": "^4.9.0", + "rollup": "^4.28.1", "seamless-immutable": "^7.1.4", "size-limit": "^11.1.6", + "superjson": "^2.2.2", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", "tslib": "^2.8.1", @@ -136,26 +142,5 @@ "path": "cz-conventional-changelog" } }, - "jest": { - "preset": "ts-jest", - "coveragePathIgnorePatterns": [ - "/test/" - ], - "transform": { - "\\.[jt]sx?$": [ - "ts-jest", - { - "tsconfig": { - "noImplicitReturns": false, - "noFallthroughCasesInSwitch": false, - "noUnusedLocals": false, - "noUnusedParameters": false - } - } - ] - }, - "globals": { - "__DEV__": true - } - } + "packageManager": "yarn@1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447" } diff --git a/rollup.config.js b/rollup.config.js index 454d9474..b797b551 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -35,7 +35,7 @@ export default [ declarationDir: 'dist', }), replace({ - __DEV__: 'false', + "__DEV__": 'false', preventAssignment: true, }), ], @@ -76,7 +76,7 @@ export default [ declarationDir: 'dist', }), replace({ - __DEV__: 'true', + "__DEV__": 'true', preventAssignment: true, }), { diff --git a/src/MutativeMap.ts b/src/MutativeMap.ts new file mode 100644 index 00000000..d5b62f17 --- /dev/null +++ b/src/MutativeMap.ts @@ -0,0 +1,299 @@ +const removedValueSymbol = Symbol('MutativeMap-removedValue'); + +/** + * More efficient version of Map for use with 'mutative' mutations, especially when a lot of entries exist and only a few are changed at a time. (it already is much faster at just a hundred entries though) + * WARNING: Iteration order may change between read-only calls and iteration order is not guaranteed to be insertion order like in a normal Map. + * TODO [bug] I think mutative already violates iteration-order contract anyhow? I think updated entries are treated as if they were inserted? or at least read objects are set with draft value and then set to final value, which might impact order. How does Immer behave? Write test for that + * TODO [MutativeMap] test/implement patch support + * TODO [MutativeMap] MutativeMap does not extend Map. Reasons were: + * 1. reduce the initial complexity/effort of having to implement and test all Map methods - which became obselete because I implemented everything (and additional utilities for better performance for common use-cases) anyhow; + * 2. prevent external business-logic written to handle Maps to do unsupported or weird stuff with MutativeMap - e.g. serialize MutativeMap to JSON as map, but at deserialization it would be a Map and no MutativeMap, reducing performance again on next mutation; + * 3. because MutativeMap does not adhere to the contract of insertion-order during iteration, it should not extend Map. + * But maybe all this does not matter and MutativeMap should extend Map. + * + * Background/Details: + * Mutative and especially Immer do not work well for scenarios where a lot of entries exist and only a fraction of the data is accessed/changed per mutation. It has to shallow-copy the whole Map on each mutation, which has a significant performance impact compared to directly mutating a Map. + * Compared to immer, mutative is already much faster/better for such scenarios, but using MutativeMap basically reduces the cost to 0. See performance test in test/performance/mutative-set-map.ts. + * This class enables mutative from needing to copy the entire map when a single value is changed. Instead, it stores the original entries separately and only copies the changed data during drafting. + * E.g. if there are 50k entries and only 1 is changed or was recently changed, this class will only copy the map with that 1 changed entry. + * With N=total_count, M=changed_count, this changes the complexity from O(N) to O(M) for drafting. + * Other operations will become slightly more expensive due to having to lookup two Maps in many scenarios, but they have the same asymptotic complexity as a regular Map. + */ +export class MutativeMap { + /** + * The original data that is never changed. + * This may be replaced by {@link compact}-ing the MutativeMap again. + * @private + */ + private immutableData: Map; + /** + * The difference to {@link immutableData}. + * If a key was removed, the value is {@link removedValueSymbol}. + * @private + */ + private patchData: Map; + private _size: number; + + get size() { + return this._size; + } + + constructor( + data?: Map | MutativeMap | Iterable + ) { + // TODO [MutativeMap] [unimportant] copy provided data map except in private constructor call to guarantee isolation? but probably unnecessary performance loss. Maybe freeze on auto-freeze mode, just to make sure. + if (data instanceof MutativeMap) { + this.immutableData = data.immutableData; + this.patchData = new Map(data.patchData); + this._size = data._size; + // TODO [MutativeMap] [performance] compact here if patchData size threshold reached? + } else if (data === undefined) { + this.immutableData = new Map(); + this.patchData = new Map(); + this._size = 0; + } else if (data instanceof Map) { + this.immutableData = data; + this.patchData = new Map(); + this._size = data.size; + } else { + this.immutableData = new Map(data); + this.patchData = new Map(); + this._size = this.immutableData.size; + } + } + + set(key: K, value: V) { + // const immutableMap = getImmutableData(this.mutableData.immutableDataKey); + // const isInImmutable = immutableMap.has(key); + // let isInPatch = this.patchData.has(key); + // if (isInPatch) { + // if (this.patchData.get(key) === removedValueSymbol) { + // isInPatch = false; + // } + // } + // if (!isInImmutable && !isInPatch) { + // this._size++; + // } + + // TODO [MutativeMap] [performance] [unimportant] could slightly optimize to minimize lookups / changes (i.e. not calling immutableData#has twice and not calling this.patchData.delete(key) if patchData#has is false) + if (!this.has(key)) { + this._size++; + } + if (this.immutableData.has(key) && this.immutableData.get(key) === value) { + // re-use immutableData value if it's the same as the new value to minimize size of patchData + // TODO [MutativeMap] [performance] could store drafts somewhere else to avoid setting drafts as values in patchData and then removing them again if they did not change? + this.patchData.delete(key); + } else { + this.patchData.set(key, value as any); + } + } + + delete(key: K) { + if (this.patchData.has(key)) { + if (this.patchData.get(key) !== removedValueSymbol) { + this.patchData.set(key, removedValueSymbol); + this._size--; + } + } else { + const immutableMap = this.immutableData; + if (immutableMap.has(key)) { + this.patchData.set(key, removedValueSymbol); + this._size--; + } + } + } + + get(key: K): V | undefined { + if (this.patchData.has(key)) { + const value = this.patchData.get(key); + if (value === removedValueSymbol) { + return undefined; + } + return value as V; + } + return this.immutableData.get(key); + } + + has(key: K) { + if (this.patchData.has(key)) { + return this.patchData.get(key) !== removedValueSymbol; + } + const immutableMap = this.immutableData; + return immutableMap.has(key); + } + + /** + * WARNING: order may change between calls during mutation (drafting influences entry order). + */ + forEach( + callbackfn: (value: V, key: K, map: MutativeMap) => void, + thisArg?: any + ): void { + for (const [key, value] of this.entries()) { + callbackfn.call(thisArg, value, key, this); + } + } + + /** + * WARNING: order may change between calls during mutation (drafting influences entry order). + */ + entriesArray(): [K, V][] { + // TODO [MutativeMap] [bug] if modified during iteration, new AND old entry for same key may be emitted + // TODO [MutativeMap] [bug] what about modification during iteration generally? + // NOTE: returning patchData values first to have better behavior in some cases when modifying during iteration (e.g. during drafting) + const entries: [K, V][] = Array.from({ length: this._size }); + let currentEntryIndex = 0; + for (const [key, value] of this.patchData.entries()) { + if (value !== removedValueSymbol) { + entries[currentEntryIndex++] = [key, value as V]; + } + } + for (const [key, value] of this.immutableData.entries()) { + if (!this.patchData.has(key)) { + entries[currentEntryIndex++] = [key, value]; + } + } + return entries; + } + + /** + * WARNING: order may change between calls during mutation (drafting influences entry order). + */ + *entries(): IterableIterator<[K, V]> { + // TODO [MutativeMap] [bug] if modified during iteration, new AND old entry for same key may be emitted + // NOTE: returning patchData values first to have better behavior in some cases when modifying during iteration (e.g. during drafting) + for (const [key, value] of this.patchData.entries()) { + if (value !== removedValueSymbol) { + yield [key, value as V]; + } + } + for (const [key, value] of this.immutableData.entries()) { + if (!this.patchData.has(key)) { + yield [key, value]; + } + } + } + + /** + * WARNING: order may change between calls during mutation (drafting influences entry order). + */ + valuesArray(): V[] { + // TODO [MutativeMap] [bug] if modified during iteration, old AND new value for same key may be emitted + // NOTE: returning patchData values first to have better behavior in some cases when modifying during iteration (e.g. during drafting) + const values: V[] = Array.from({ length: this._size }); + let currentValueIndex = 0; + for (const value of this.patchData.values()) { + if (value !== removedValueSymbol) { + values[currentValueIndex++] = value as V; + } + } + for (const [key, value] of this.immutableData.entries()) { + if (!this.patchData.has(key)) { + values[currentValueIndex++] = value; + } + } + return values; + } + + /** + * WARNING: order may change between calls during mutation (drafting influences entry order). + */ + *values(): IterableIterator { + // TODO [MutativeMap] [bug] if modified during iteration, old AND new value for same key may be emitted + // NOTE: returning patchData values first to have better behavior in some cases when modifying during iteration (e.g. during drafting) + for (const value of this.patchData.values()) { + if (value !== removedValueSymbol) { + yield value as V; + } + } + for (const [key, value] of this.immutableData.entries()) { + if (!this.patchData.has(key)) { + yield value; + } + } + } + + /** + * WARNING: order may change between calls during mutation (drafting influences entry order). + */ + keysArray(): K[] { + // TODO [MutativeMap] [bug] if modified during iteration, key may be emitted twice in some special cases like setting key to original value + // NOTE: returning patchData values first to have better behavior in some cases when modifying during iteration (e.g. during drafting) + const keys: K[] = Array.from({ length: this._size }); + let currentKeyIndex = 0; + for (const [key, value] of this.patchData.entries()) { + if (value !== removedValueSymbol) { + keys[currentKeyIndex++] = key; + } + } + for (const key of this.immutableData.keys()) { + if (!this.patchData.has(key)) { + keys[currentKeyIndex++] = key; + } + } + return keys; + } + + /** + * WARNING: order may change between calls during mutation (drafting influences entry order). + */ + *keys(): IterableIterator { + // TODO [MutativeMap] [bug] if modified during iteration, key may be emitted twice in some special cases like setting key to original value + // NOTE: returning patchData values first to have better behavior in some cases when modifying during iteration (e.g. during drafting) + for (const [key, value] of this.patchData.entries()) { + if (value !== removedValueSymbol) { + yield key; + } + } + for (const key of this.immutableData.keys()) { + if (!this.patchData.has(key)) { + yield key; + } + } + } + + clear() { + this.immutableData = new Map(); + this.patchData = new Map(); + this._size = 0; + } + + /** + * WARNING: order may change between calls during mutation (drafting influences entry order). + * @param fn + */ + mapValues(fn: (value: V, key: K) => ResultValue): ResultValue[] { + const result: ResultValue[] = Array.from({ length: this._size }); + let currentIndex = 0; + for (const [key, value] of this.entries()) { + result[currentIndex++] = fn(value, key); + } + return result; + } + + /** + * Call this to improve the performance after a series of set/delete operations to minimize the data needed to be copied when drafting. + * This is especially useful when a lot of entries were changed recently. + * @return The number of entries that were in the patch data. If this was high, compacting was very useful. + */ + compact(mutativeMap: MutativeMap): number { + let patchDataSize = mutativeMap.patchData.size; + if (patchDataSize === 0) { + return 0; + } + // TODO [MutativeMap] test compacting MutativeMap (especially during mutation. Should that even be allowed? May hurt performance by compacting before unchanged draft values were finalized, which will make patchData grow unnecessarily) + const mapWithAllData = new Map(mutativeMap.entries()); + const allData = new Map(mapWithAllData); + // for (const [key, value] of mutativeMap.patchData) { + // assertAlways(!isDraft(value), () => `Draft value for key=${key} found in patch data while compacting`); + // if (value === removedValueSymbol) { + // allData.delete(key); + // } else { + // allData.set(key, value as V); + // } + // } + mutativeMap.patchData = new Map(); + mutativeMap.immutableData = allData; + return patchDataSize; + } +} diff --git a/src/apply.ts b/src/apply.ts index b358c3f9..5c9bce22 100644 --- a/src/apply.ts +++ b/src/apply.ts @@ -81,6 +81,7 @@ export function apply( case Operation.Replace: switch (type) { case DraftType.Map: + case DraftType.MutativeMap: return base.set(key, value); case DraftType.Set: throw new Error(`Cannot apply replace patch to set.`); @@ -97,6 +98,7 @@ export function apply( ? base.push(value) : base.splice(key as number, 0, value); case DraftType.Map: + case DraftType.MutativeMap: return base.set(key, value); case DraftType.Set: return base.add(value); @@ -108,6 +110,7 @@ export function apply( case DraftType.Array: return base.splice(key as number, 1); case DraftType.Map: + case DraftType.MutativeMap: return base.delete(key); case DraftType.Set: return base.delete(patch.value); diff --git a/src/current.ts b/src/current.ts index bfc451d5..584c5460 100644 --- a/src/current.ts +++ b/src/current.ts @@ -8,10 +8,11 @@ import { isBaseSetInstance, isDraft, isDraftable, - isEqual, set, shallowCopy, } from './utils'; +import { objectIs } from './generic-utils/equality'; +import { MutativeMap } from './MutativeMap'; export function handleReturnValue(options: { rootDraft: ProxyDraft | undefined; @@ -21,6 +22,7 @@ export function handleReturnValue(options: { isRoot?: boolean; }) { const { rootDraft, value, useRawReturn = false, isRoot = true } = options; + // TODO doesn't this iterate over the proxy too? or does getOwnKeys only return the keys of the original object? forEach(value, (key, item, source) => { const proxyDraft = getProxyDraft(item); // just handle the draft which is created by the same rootDraft @@ -67,15 +69,18 @@ function getCurrent(target: any) { const type = getType(target); if (proxyDraft && !proxyDraft.operated) return proxyDraft.original; let currentValue: any; + function ensureShallowCopy() { currentValue = type === DraftType.Map ? !isBaseMapInstance(target) ? new (Object.getPrototypeOf(target).constructor)(target) : new Map(target) + : type === DraftType.MutativeMap + ? new MutativeMap(target) : type === DraftType.Set - ? Array.from(proxyDraft!.setMap!.values()!) - : shallowCopy(target, proxyDraft?.options); + ? Array.from(proxyDraft!.setMap!.values()!) + : shallowCopy(target, proxyDraft?.options); } if (proxyDraft) { @@ -93,7 +98,7 @@ function getCurrent(target: any) { } forEach(currentValue, (key, value) => { - if (proxyDraft && isEqual(get(proxyDraft.original, key), value)) return; + if (proxyDraft && objectIs(get(proxyDraft.original, key), value)) return; const newValue = getCurrent(value); if (newValue !== value) { if (currentValue === target) ensureShallowCopy(); diff --git a/src/draft.ts b/src/draft.ts index 53904482..80157db1 100644 --- a/src/draft.ts +++ b/src/draft.ts @@ -18,7 +18,6 @@ import { getType, getValue, has, - isEqual, isDraftable, latest, markChanged, @@ -32,15 +31,21 @@ import { } from './utils'; import { checkReadable } from './unsafe'; import { generatePatches } from './patch'; +import { objectIs } from './generic-utils/equality'; +import { + mutativeMapHandler, + mutativeMapHandlerKeys, +} from './mutativeMapHandlers'; +import { MutativeMap } from './MutativeMap'; const draftsCache = new WeakSet(); const proxyHandler: ProxyHandler = { get(target: ProxyDraft, key: string | number | symbol, receiver: any) { - const copy = target.copy?.[key]; + const copyValue = target.copy?.[key]; // Improve draft reading performance by caching the draft copy. - if (copy && draftsCache.has(copy)) { - return copy; + if (copyValue && draftsCache.has(copyValue)) { + return copyValue; } if (key === PROXY_DRAFT) return target; let markResult: any; @@ -49,7 +54,9 @@ const proxyHandler: ProxyHandler = { // or `Uncaught TypeError: Method get Set.prototype.size called on incompatible receiver #` const value = key === 'size' && - (target.original instanceof Map || target.original instanceof Set) + (target.original instanceof Map || + target.original instanceof MutativeMap || + target.original instanceof Set) ? Reflect.get(target.original, key) : Reflect.get(target.original, key, receiver); markResult = target.options.mark(value, dataTypes); @@ -64,6 +71,7 @@ const proxyHandler: ProxyHandler = { if (source instanceof Map && mapHandlerKeys.includes(key as any)) { if (key === 'size') { + // TODO [documentation] why this "fast-path" here? return Object.getOwnPropertyDescriptor(mapHandler, 'size')!.get!.call( target.proxy ); @@ -74,8 +82,27 @@ const proxyHandler: ProxyHandler = { } } + if ( + source instanceof MutativeMap && + mutativeMapHandlerKeys.includes(key as any) + ) { + if (key === 'size') { + // TODO [documentation] why this "fast-path" here? + return Object.getOwnPropertyDescriptor(mutativeMapHandler, 'size')!.get!.call( + target.proxy + ); + } + const handle = mutativeMapHandler[ + key as keyof typeof mutativeMapHandler + ] as Function; + if (handle) { + return handle.bind(target.proxy); + } + } + if (source instanceof Set && setHandlerKeys.includes(key as any)) { if (key === 'size') { + // TODO [documentation] why this "fast-path" here? return Object.getOwnPropertyDescriptor(setHandler, 'size')!.get!.call( target.proxy ); @@ -86,6 +113,7 @@ const proxyHandler: ProxyHandler = { } } + // TODO [bug] isn't this potentially wrong for map entries with string keys, because e.g. if mutativeMap['valuesArray'] is called and an entry with 'valuesArray' exists as key, it may not attempt to find a property 'valuesArray' on the MutativeMap object itself, which is not correct. Fixing it by adding mutativeMapHandler, but generally makes no sense here to consider map entries here. Will probably just make issues for custom map implementations (which seem to be supported based on some code I looked at). Test with '_size'. if (!has(source, key)) { const desc = getDescriptor(source, key); return desc @@ -152,7 +180,7 @@ const proxyHandler: ProxyHandler = { } const current = peek(latest(target), key); const currentProxyDraft = getProxyDraft(current); - if (currentProxyDraft && isEqual(currentProxyDraft.original, value)) { + if (currentProxyDraft && objectIs(currentProxyDraft.original, value)) { // !case: ignore the case of assigning the original draftable value to a draft target.copy![key] = value; target.assignedMap = target.assignedMap ?? new Map(); @@ -161,13 +189,13 @@ const proxyHandler: ProxyHandler = { } // !case: handle new props with value 'undefined' if ( - isEqual(value, current) && + objectIs(value, current) && (value !== undefined || has(target.original, key)) ) return true; ensureShallowCopy(target); markChanged(target); - if (has(target.original, key) && isEqual(value, target.original[key])) { + if (has(target.original, key) && objectIs(value, target.original[key])) { // !case: handle the case of assigning the original non-draftable value to a draft target.assignedMap!.delete(key); } else { @@ -243,6 +271,7 @@ export function createDraft(createDraftOptions: { options, // Mapping of draft Set items to their corresponding draft values. setMap: + // TODO [performance] so are Sets slower than Maps because of always creating a new Map with all the entries even though it is only read? type === DraftType.Set ? new Map((original as Set).entries()) : undefined, diff --git a/src/generic-utils/equality.ts b/src/generic-utils/equality.ts new file mode 100644 index 00000000..dc643f49 --- /dev/null +++ b/src/generic-utils/equality.ts @@ -0,0 +1,16 @@ +/** + * inlined Object.is polyfill to avoid requiring consumers ship their own + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is + * (copied from React) + */ +function is(x: any, y: any) { + return ( + (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare + ); +} + +/** + * Object.is but with implicit polyfill if necessary + * (copied from React) + */ +export const objectIs = typeof Object.is === 'function' ? Object.is : is; diff --git a/src/index.ts b/src/index.ts index e20d278a..c4e59b2d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ export { rawReturn } from './rawReturn'; export { isDraft } from './utils/draft'; export { isDraftable } from './utils/draft'; export { markSimpleObject } from './utils/marker'; +export { MutativeMap } from './MutativeMap'; export { castDraft, castImmutable, castMutable } from './utils/cast'; export type { @@ -17,4 +18,5 @@ export type { Patch, ExternalOptions as Options, PatchesOptions, + Mark, // TODO [review] allowed to expose this? I wanted it in my own project for proper typing and originally used patch-package. But I'll try to sneak it in with this feature if it's fine. } from './interface'; diff --git a/src/interface.ts b/src/interface.ts index 0fbabe30..3fcb265d 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -4,6 +4,7 @@ export const enum DraftType { Object, Array, Map, + MutativeMap, Set, } @@ -45,7 +46,13 @@ export interface ProxyDraft { options: Options & { updatedValues?: WeakMap }; parent?: ProxyDraft | null; key?: string | number | symbol; + /** + * Only for Set to store drafts. + */ setMap?: Map; + /** + * Marks which keys have been assigned new values (also during drafting properties). + */ assignedMap?: Map; callbacks?: ((patches?: Patches, inversePatches?: Patches) => void)[]; } diff --git a/src/makeCreator.ts b/src/makeCreator.ts index e4e93881..17e4e64a 100644 --- a/src/makeCreator.ts +++ b/src/makeCreator.ts @@ -8,15 +8,10 @@ import { Result, } from './interface'; import { draftify } from './draftify'; -import { - getProxyDraft, - isDraft, - isDraftable, - isEqual, - revokeProxy, -} from './utils'; +import { getProxyDraft, isDraft, isDraftable, revokeProxy } from './utils'; import { current, handleReturnValue } from './current'; import { RAW_RETURN_SYMBOL, dataTypes } from './constant'; +import { objectIs } from './generic-utils/equality'; type MakeCreator = < _F extends boolean = false, @@ -179,7 +174,7 @@ export const makeCreator: MakeCreator = (arg) => { if (!isDraft(value)) { if ( value !== undefined && - !isEqual(value, draft) && + !objectIs(value, draft) && proxyDraft?.operated ) { throw new Error( diff --git a/src/map.ts b/src/map.ts index 52dbc2df..dc3abb3d 100644 --- a/src/map.ts +++ b/src/map.ts @@ -6,11 +6,11 @@ import { ensureShallowCopy, getProxyDraft, isDraftable, - isEqual, latest, markChanged, markFinalization, } from './utils'; +import { objectIs } from './generic-utils/equality'; export const mapHandler = { get size() { @@ -23,7 +23,7 @@ export const mapHandler = { set(key: any, value: any) { const target = getProxyDraft(this)!; const source = latest(target); - if (!source.has(key) || !isEqual(source.get(key), value)) { + if (!source.has(key) || !objectIs(source.get(key), value)) { ensureShallowCopy(target); markChanged(target); target.assignedMap!.set(key, true); diff --git a/src/mutativeMapHandlers.ts b/src/mutativeMapHandlers.ts new file mode 100644 index 00000000..d20a24da --- /dev/null +++ b/src/mutativeMapHandlers.ts @@ -0,0 +1,187 @@ +import { dataTypes, iteratorSymbol } from './constant'; +import { internal } from './internal'; +import { generatePatches } from './patch'; +import { checkReadable } from './unsafe'; +import { + ensureShallowCopy, + getProxyDraft, + isDraftable, + latest, + markChanged, + markFinalization, +} from './utils'; +import { objectIs } from './generic-utils/equality'; +import { MutativeMap } from './MutativeMap'; + +// TODO [MutativeMap] [refactoring] ended up being basically an exact copy of mapHandlers, so code could be deduplicated. But additional methods to optimize performance could be added here. +export const mutativeMapHandler = { + get size() { + const current: MutativeMap = latest(getProxyDraft(this)!); + return current.size; + }, + has(key: any): boolean { + return latest(getProxyDraft(this)!).has(key); + }, + set(key: any, value: any) { + const target = getProxyDraft(this)!; + const source = latest(target); + if (!source.has(key) || !objectIs(source.get(key), value)) { + ensureShallowCopy(target); + markChanged(target); + target.assignedMap!.set(key, true); + target.copy.set(key, value); + markFinalization(target, key, value, generatePatches); + } + return this; + }, + delete(key: any): boolean { + if (!this.has(key)) { + return false; + } + const target = getProxyDraft(this)!; + ensureShallowCopy(target); + markChanged(target); + if (target.original.has(key)) { + target.assignedMap!.set(key, false); + } else { + target.assignedMap!.delete(key); + } + target.copy.delete(key); + return true; + }, + clear() { + const target = getProxyDraft(this)!; + if (!this.size) return; + ensureShallowCopy(target); + markChanged(target); + target.assignedMap = new Map(); + for (const [key] of target.original) { + target.assignedMap.set(key, false); + } + target.copy!.clear(); + }, + forEach(callback: (value: any, key: any, self: any) => void, thisArg?: any) { + const keys = this.keysArray(); // get as array to make sure that modifications due to creating drafts do not lead to the same key being iterated over multiple times + for (const key of keys) { + callback.call(thisArg, this.get(key), key, this); + } + }, + get(key: any): any { + const target = getProxyDraft(this)!; + const value = latest(target).get(key); + const mutable = + target.options.mark?.(value, dataTypes) === dataTypes.mutable; + if (target.options.strict) { + checkReadable(value, target.options, mutable); + } + if (mutable) { + return value; + } + if (target.finalized || !isDraftable(value, target.options)) { + return value; + } + // drafted or reassigned + if (value !== target.original.get(key)) { + return value; + } + const draft = internal.createDraft({ + original: value, + parentDraft: target, + key, + finalities: target.finalities, + options: target.options, + }); + ensureShallowCopy(target); + target.copy.set(key, draft); + return draft; + }, + keys(): IterableIterator { + return latest(getProxyDraft(this)!).keys(); + }, + keysArray(): any[] { + // TODO [MutativeMap] [bug] iteration order changes when calling .values() because of creating drafts. So if calling .values() again, the order is different but stays stable starting then. Fix by just returning patchData keys/entries first? + return latest(getProxyDraft(this)!).keysArray(); + }, + values(): IterableIterator { + const iterator = this.keys(); + return { + [iteratorSymbol]: () => this.values(), + next: () => { + const result = iterator.next(); + const value = this.get(result.value); + return { + done: result.done, + value, + }; + }, + } as any; + }, + // values(): IterableIterator { + // const keys = this.keysArray(); // get as array to make sure that modifications due to creating drafts do not lead to the same key being iterated over multiple times + // let nextIndex = 0; + // return { + // [iteratorSymbol]: () => this.values(), + // next: () => { + // if (nextIndex >= keys.length) { + // return { done: true }; + // } + // const key = keys[nextIndex++]; + // const value = this.get(key); + // return { + // done: false, + // value, + // }; + // }, + // } as any; + // }, + valuesArray(): any[] { + // TODO [MutativeMap] [performance] could probably be optimized just like non-draft fn + return Array.from(this.values()); + }, + entries(): IterableIterator<[any, any]> { + const iterator = this.keys(); + return { + [iteratorSymbol]: () => this.entries(), + next: () => { + const result = iterator.next(); + const value = this.get(result.value); + return { + done: result.done, + value: [result.value, value], + }; + }, + } as any; + }, + // entries(): IterableIterator<[any, any]> { + // const keys = this.keysArray(); // get as array to make sure that modifications due to creating drafts do not lead to the same key being iterated over multiple times + // let nextIndex = 0; + // return { + // [iteratorSymbol]: () => this.entries(), + // next: () => { + // if (nextIndex >= keys.length) { + // return { done: true }; + // } + // const key = keys[nextIndex++]; + // const value = this.get(key); + // return { + // done: false, + // value: [key, value], + // }; + // }, + // } as any; + // }, + entriesArray(): [any, any][] { + // TODO [MutativeMap] [performance] could probably be optimized just like non-draft fn + return Array.from(this.entries()); + }, + mapValues( + fn: (value: any, key: any) => ResultValue + ): ResultValue[] { + return Array.from(this.entries(), ([key, value]) => fn(value, key)); + }, + [iteratorSymbol]() { + return this.entries(); + }, +}; + +export const mutativeMapHandlerKeys = Reflect.ownKeys(mutativeMapHandler); diff --git a/src/patch.ts b/src/patch.ts index ebf6762d..9573606f 100644 --- a/src/patch.ts +++ b/src/patch.ts @@ -1,5 +1,6 @@ import { DraftType, Operation, Patches, ProxyDraft } from './interface'; -import { cloneIfNeeded, escapePath, get, has, isEqual } from './utils'; +import { cloneIfNeeded, escapePath, get, has } from './utils'; +import { objectIs } from './generic-utils/equality'; function generateArrayPatches( proxyState: ProxyDraft>, @@ -83,7 +84,7 @@ function generatePatchesFromAssigned( : has(original, key) ? Operation.Replace : Operation.Add; - if (isEqual(originalValue, value) && op === Operation.Replace) return; + if (objectIs(originalValue, value) && op === Operation.Replace) return; const _path = basePath.concat(key); const path = escapePath(_path, pathAsArray); patches.push(op === Operation.Remove ? { op, path } : { op, path, value }); diff --git a/src/utils/copy.ts b/src/utils/copy.ts index 567892e0..6a8fbebe 100644 --- a/src/utils/copy.ts +++ b/src/utils/copy.ts @@ -3,6 +3,8 @@ import { dataTypes } from '../constant'; import { getValue, isDraft, isDraftable } from './draft'; import { isBaseMapInstance, isBaseSetInstance } from './proto'; +import { MutativeMap } from '../MutativeMap'; + function strictCopy(target: any) { const copy = Object.create(Object.getPrototypeOf(target)); Reflect.ownKeys(target).forEach((key: any) => { @@ -48,6 +50,12 @@ export function shallowCopy(original: any, options?: Options) { return new SubClass(original); } return new Map(original); + } else if (original instanceof MutativeMap) { + if (!isBaseMutativeMapInstance(original)) { + const SubClass = Object.getPrototypeOf(original).constructor; + return new SubClass(original); + } + return new MutativeMap(original); } else if ( options?.mark && ((markResult = options.mark(original, dataTypes)), @@ -108,6 +116,13 @@ function deepClone(target: any) { } return new Map(iterable); } + if (target instanceof MutativeMap) { + const iterable = Array.from(target.entries()).map(([k, v]) => [ + k, + deepClone(v), + ]) as Iterable; + return new MutativeMap(iterable); + } if (target instanceof Set) { const iterable = Array.from(target).map(deepClone); if (!isBaseSetInstance(target)) { @@ -125,4 +140,8 @@ export function cloneIfNeeded(target: T): T { return isDraft(target) ? deepClone(target) : target; } +export function isBaseMutativeMapInstance(obj: any) { + return Object.getPrototypeOf(obj) === MutativeMap.prototype; +} + export { deepClone }; diff --git a/src/utils/deepFreeze.ts b/src/utils/deepFreeze.ts index ec7553ab..32235c25 100644 --- a/src/utils/deepFreeze.ts +++ b/src/utils/deepFreeze.ts @@ -1,6 +1,8 @@ import { DraftType } from '../interface'; import { getType, isDraft } from './draft'; +import { MutativeMap } from '../MutativeMap'; + function throwFrozenError() { throw new Error('Cannot modify frozen object'); } @@ -39,7 +41,9 @@ export function deepFreeze( const parent = stack![index]; if ( typeof key === 'object' && - (parent instanceof Map || parent instanceof Set) + (parent instanceof Map || + parent instanceof MutativeMap || + parent instanceof Set) ) return Array.from(parent.keys()).indexOf(key); return key; @@ -70,6 +74,14 @@ export function deepFreeze( } target.set = target.clear = target.delete = throwFrozenError; break; + case DraftType.MutativeMap: + for (const [key, value] of target) { + if (isFreezable(key)) deepFreeze(key, key, updatedValues, stack, keys); + if (isFreezable(value)) + deepFreeze(value, key, updatedValues, stack, keys); + } + target.set = target.clear = target.delete = throwFrozenError; + break; case DraftType.Set: for (const value of target) { if (isFreezable(value)) diff --git a/src/utils/draft.ts b/src/utils/draft.ts index 5549102c..87a061a3 100644 --- a/src/utils/draft.ts +++ b/src/utils/draft.ts @@ -2,6 +2,8 @@ import { DraftType, Mark, ProxyDraft } from '../interface'; import { dataTypes, PROXY_DRAFT } from '../constant'; import { has } from './proto'; +import { MutativeMap } from '../MutativeMap'; + export function latest(proxyDraft: ProxyDraft): T { return proxyDraft.copy ?? proxyDraft.original; } @@ -33,6 +35,7 @@ export function isDraftable(value: any, options?: { mark?: Mark }) { Object.getPrototypeOf(value) === Object.prototype || Array.isArray(value) || value instanceof Map || + value instanceof MutativeMap || value instanceof Set || (!!options?.mark && ((markResult = options.mark(value, dataTypes)) === dataTypes.immutable || @@ -79,17 +82,21 @@ export function getPath( export function getType(target: any) { if (Array.isArray(target)) return DraftType.Array; if (target instanceof Map) return DraftType.Map; + if (target instanceof MutativeMap) return DraftType.MutativeMap; if (target instanceof Set) return DraftType.Set; return DraftType.Object; } export function get(target: any, key: PropertyKey) { - return getType(target) === DraftType.Map ? target.get(key) : target[key]; + return getType(target) === DraftType.Map || + getType(target) === DraftType.MutativeMap + ? target.get(key) + : target[key]; } export function set(target: any, key: PropertyKey, value: any) { const type = getType(target); - if (type === DraftType.Map) { + if (type === DraftType.Map || type === DraftType.MutativeMap) { target.set(key, value); } else { target[key] = value; @@ -102,17 +109,10 @@ export function peek(target: any, key: PropertyKey) { return source[key]; } -export function isEqual(x: any, y: any) { - if (x === y) { - return x !== 0 || 1 / x === 1 / y; - } else { - return x !== x && y !== y; - } -} - export function revokeProxy(proxyDraft: ProxyDraft | null) { if (!proxyDraft) return; while (proxyDraft.finalities.revoke.length > 0) { + // TODO [performance] this can't be the fastest way to revoke all proxies. Shouldn't it just be iterated over and then cleared or just left alone and be garbage-collected anyhow? const revoke = proxyDraft.finalities.revoke.pop()!; revoke(); } diff --git a/src/utils/finalize.ts b/src/utils/finalize.ts index f545c62d..3f62d470 100644 --- a/src/utils/finalize.ts +++ b/src/utils/finalize.ts @@ -7,10 +7,10 @@ import { getValue, isDraft, isDraftable, - isEqual, set, } from './draft'; import { forEach } from './forEach'; +import { objectIs } from '../generic-utils/equality'; export function handleValue( target: any, @@ -121,7 +121,7 @@ export function markFinalization( } proxyDraft.callbacks.push((patches, inversePatches) => { const copy = target.type === DraftType.Set ? target.setMap : target.copy; - if (isEqual(get(copy, key), value)) { + if (objectIs(get(copy, key), value)) { let updatedValue = proxyDraft.original; if (proxyDraft.copy) { updatedValue = proxyDraft.copy; @@ -148,7 +148,7 @@ export function markFinalization( // !case: assign the non-draft value target.finalities.draft.push(() => { const copy = target.type === DraftType.Set ? target.setMap : target.copy; - if (isEqual(get(copy, key), value)) { + if (objectIs(get(copy, key), value)) { finalizeAssigned(target, key); } }); diff --git a/src/utils/forEach.ts b/src/utils/forEach.ts index 8d84c3bc..7525900c 100644 --- a/src/utils/forEach.ts +++ b/src/utils/forEach.ts @@ -1,5 +1,6 @@ import { DraftType } from '../interface'; import { getType } from './draft'; +import { MutativeMap } from '../MutativeMap'; export function forEach( target: T, @@ -17,8 +18,8 @@ export function forEach( index += 1; } } else { - (target as Map | Set).forEach((entry: any, index: any) => - iter(index, entry, target) + (target as Map | MutativeMap | Set).forEach( + (entry: any, index: any) => iter(index, entry, target) ); } } diff --git a/src/utils/proto.ts b/src/utils/proto.ts index f4d3b5cd..7fdaf76b 100644 --- a/src/utils/proto.ts +++ b/src/utils/proto.ts @@ -1,5 +1,7 @@ +import { MutativeMap } from '../MutativeMap'; + export function has(target: object, key: PropertyKey) { - return target instanceof Map + return target instanceof Map || target instanceof MutativeMap ? target.has(key) : Object.prototype.hasOwnProperty.call(target, key); } diff --git a/test/__immer_performance_tests__/add-data.ts b/test/__immer_performance_tests__/add-data.ts index b2c4e190..ee9dd549 100644 --- a/test/__immer_performance_tests__/add-data.ts +++ b/test/__immer_performance_tests__/add-data.ts @@ -1,5 +1,6 @@ // @ts-nocheck 'use strict'; +(globalThis as any).__DEV__ = false; import { produce, setAutoFreeze } from 'immer'; import cloneDeep from 'lodash.clonedeep'; diff --git a/test/__immer_performance_tests__/incremental.ts b/test/__immer_performance_tests__/incremental.ts index 5e08daf6..735dd871 100644 --- a/test/__immer_performance_tests__/incremental.ts +++ b/test/__immer_performance_tests__/incremental.ts @@ -1,5 +1,6 @@ // @ts-nocheck 'use strict'; +(globalThis as any).__DEV__ = false; import { produce, setAutoFreeze } from 'immer'; import cloneDeep from 'lodash.clonedeep'; import Immutable from 'immutable'; diff --git a/test/__immer_performance_tests__/large-obj.ts b/test/__immer_performance_tests__/large-obj.ts index 4aacff41..b1ea3945 100644 --- a/test/__immer_performance_tests__/large-obj.ts +++ b/test/__immer_performance_tests__/large-obj.ts @@ -1,4 +1,5 @@ // @ts-nocheck +(globalThis as any).__DEV__ = false; import { produce, setUseStrictShallowCopy } from 'immer'; import { measure } from './measure'; diff --git a/test/__immer_performance_tests__/measure.ts b/test/__immer_performance_tests__/measure.ts index dd15b103..e72247c7 100644 --- a/test/__immer_performance_tests__/measure.ts +++ b/test/__immer_performance_tests__/measure.ts @@ -1,5 +1,6 @@ // @ts-nocheck 'use strict'; +(globalThis as any).__DEV__ = false; function measureTime(setup, fn) { if (!fn) { diff --git a/test/__immer_performance_tests__/todo.ts b/test/__immer_performance_tests__/todo.ts index 2249c46a..ae0a8f18 100644 --- a/test/__immer_performance_tests__/todo.ts +++ b/test/__immer_performance_tests__/todo.ts @@ -2,6 +2,7 @@ /* eslint-disable @typescript-eslint/no-empty-function */ // @ts-nocheck 'use strict'; +(globalThis as any).__DEV__ = false; import { enablePatches, produce, setAutoFreeze } from 'immer'; import cloneDeep from 'lodash.clonedeep'; diff --git a/test/benchmark/array-batch-getter.ts b/test/benchmark/array-batch-getter.ts index 41534c82..f429a9ee 100644 --- a/test/benchmark/array-batch-getter.ts +++ b/test/benchmark/array-batch-getter.ts @@ -2,6 +2,7 @@ /* eslint-disable import/no-relative-packages */ /* eslint-disable prefer-template */ // @ts-nocheck +(globalThis as any).__DEV__ = false; import fs from 'fs'; import path from 'path'; import https from 'https'; diff --git a/test/benchmark/array-batch.ts b/test/benchmark/array-batch.ts index ba2f99d5..e5ecde42 100644 --- a/test/benchmark/array-batch.ts +++ b/test/benchmark/array-batch.ts @@ -2,6 +2,7 @@ /* eslint-disable import/no-relative-packages */ /* eslint-disable prefer-template */ // @ts-nocheck +(globalThis as any).__DEV__ = false; import fs from 'fs'; import path from 'path'; import https from 'https'; diff --git a/test/benchmark/array-single-push.ts b/test/benchmark/array-single-push.ts index cdba14b4..2a2bb738 100644 --- a/test/benchmark/array-single-push.ts +++ b/test/benchmark/array-single-push.ts @@ -1,6 +1,7 @@ /* eslint-disable import/no-relative-packages */ /* eslint-disable prefer-template */ // @ts-nocheck +(globalThis as any).__DEV__ = false; import fs from 'fs'; import path from 'path'; import https from 'https'; diff --git a/test/benchmark/array.ts b/test/benchmark/array.ts index 9734f8f1..e144e651 100644 --- a/test/benchmark/array.ts +++ b/test/benchmark/array.ts @@ -1,6 +1,7 @@ /* eslint-disable import/no-relative-packages */ /* eslint-disable prefer-template */ // @ts-nocheck +(globalThis as any).__DEV__ = false; import fs from 'fs'; import path from 'path'; import https from 'https'; diff --git a/test/benchmark/class.ts b/test/benchmark/class.ts index eac5f50b..1aaf6174 100644 --- a/test/benchmark/class.ts +++ b/test/benchmark/class.ts @@ -1,6 +1,7 @@ /* eslint-disable import/no-relative-packages */ /* eslint-disable prefer-template */ // @ts-nocheck +(globalThis as any).__DEV__ = false; import fs from 'fs'; import path from 'path'; import https from 'https'; diff --git a/test/benchmark/index.ts b/test/benchmark/index.ts index e7fbb068..f56aef32 100644 --- a/test/benchmark/index.ts +++ b/test/benchmark/index.ts @@ -1,4 +1,5 @@ /* eslint-disable no-loop-func */ +(globalThis as any).__DEV__ = false; import fs from 'fs'; import { spawn } from 'child_process'; import path from 'path'; diff --git a/test/benchmark/map-batch.ts b/test/benchmark/map-batch.ts index 8dcf2596..35cc5948 100644 --- a/test/benchmark/map-batch.ts +++ b/test/benchmark/map-batch.ts @@ -1,6 +1,7 @@ /* eslint-disable import/no-relative-packages */ /* eslint-disable prefer-template */ // @ts-nocheck +(globalThis as any).__DEV__ = false; import fs from 'fs'; import path from 'path'; import https from 'https'; @@ -21,7 +22,7 @@ import { // produceWithPatches, // setAutoFreeze, // } from '../../../temp/immer/dist'; -import { create } from '../..'; +import { create, MutativeMap } from '../..'; enableMapSet(); @@ -37,6 +38,13 @@ const config: Parameters[0] = { data: [], fill: false, }, + { + label: 'MutativeMap', + backgroundColor: 'rgba(235,54,220,0.5)', + borderColor: 'rgba(235,54,220,0.5)', + data: [], + fill: false, + }, // { // label: 'Naive handcrafted reducer', // backgroundColor: 'rgba(255, 0, 0, 0.5)', @@ -93,7 +101,9 @@ const run = (size: number) => { const suite = new Suite(); let i: number; - let baseState: Map; + let baseState: + | Map + | MutativeMap; let MODIFY_FACTOR = 0.1; suite @@ -113,6 +123,22 @@ const run = (size: number) => { }, } ) + .add( + 'MutativeMap', + () => { + const state = create(baseState, (draft) => { + for (let index = 0; index < size * MODIFY_FACTOR; index++) { + draft.get(index).value = i; + } + }); + }, + { + onStart: () => { + i = Math.random(); + baseState = new MutativeMap(getData(size)); + }, + } + ) // .add( // 'Naive handcrafted reducer', // () => { diff --git a/test/benchmark/map.ts b/test/benchmark/map.ts index 077bf1c5..59a1c6e2 100644 --- a/test/benchmark/map.ts +++ b/test/benchmark/map.ts @@ -1,6 +1,8 @@ /* eslint-disable import/no-relative-packages */ /* eslint-disable prefer-template */ // @ts-nocheck + +(globalThis as any).__DEV__ = false; import fs from 'fs'; import path from 'path'; import https from 'https'; @@ -21,7 +23,7 @@ import { // produceWithPatches, // setAutoFreeze, // } from '../../../temp/immer/dist'; -import { create } from '../..'; +import { create, MutativeMap } from '../..'; enableMapSet(); @@ -37,6 +39,13 @@ const config: Parameters[0] = { data: [], fill: false, }, + { + label: 'MutativeMap', + backgroundColor: 'rgba(235,54,220,0.5)', + borderColor: 'rgba(235,54,220,0.5)', + data: [], + fill: false, + }, // { // label: 'Naive handcrafted reducer', // backgroundColor: 'rgba(255, 0, 0, 0.5)', @@ -93,7 +102,9 @@ const run = (size: number) => { const suite = new Suite(); let i: number; - let baseState: Map; + let baseState: + | Map + | MutativeMap; suite .add( @@ -110,6 +121,20 @@ const run = (size: number) => { }, } ) + .add( + 'MutativeMap', + () => { + const state = create(baseState, (draft) => { + draft.get(0).value = i; + }); + }, + { + onStart: () => { + i = Math.random(); + baseState = new MutativeMap(getData(size)); + }, + } + ) // .add( // 'Naive handcrafted reducer', // () => { @@ -153,9 +178,15 @@ const run = (size: number) => { }; [ + // 100 - 900 + // ...Array(9) + // .fill(1) + // .map((_, i) => (i + 1) * 10 ** 2), + // 1000 - 9000 ...Array(9) .fill(1) .map((_, i) => (i + 1) * 10 ** 3), + // 10000 - 90000 ...Array(9) .fill(1) .map((_, i) => (i + 1) * 10 ** 4), diff --git a/test/benchmark/object-batch-getter.ts b/test/benchmark/object-batch-getter.ts index adc554ef..e0150e73 100644 --- a/test/benchmark/object-batch-getter.ts +++ b/test/benchmark/object-batch-getter.ts @@ -1,6 +1,7 @@ /* eslint-disable import/no-relative-packages */ /* eslint-disable prefer-template */ // @ts-nocheck +(globalThis as any).__DEV__ = false; import fs from 'fs'; import path from 'path'; import https from 'https'; diff --git a/test/benchmark/object-batch.ts b/test/benchmark/object-batch.ts index f631e846..cb47f135 100644 --- a/test/benchmark/object-batch.ts +++ b/test/benchmark/object-batch.ts @@ -1,6 +1,7 @@ /* eslint-disable import/no-relative-packages */ /* eslint-disable prefer-template */ // @ts-nocheck +(globalThis as any).__DEV__ = false; import fs from 'fs'; import path from 'path'; import https from 'https'; diff --git a/test/benchmark/object.ts b/test/benchmark/object.ts index 2a22fcbd..3c38b39a 100644 --- a/test/benchmark/object.ts +++ b/test/benchmark/object.ts @@ -1,6 +1,7 @@ /* eslint-disable import/no-relative-packages */ /* eslint-disable prefer-template */ // @ts-nocheck +(globalThis as any).__DEV__ = false; import fs from 'fs'; import path from 'path'; import https from 'https'; diff --git a/test/benchmark/results/map-batch.jpg b/test/benchmark/results/map-batch.jpg index 480835de..0a11e2bb 100644 Binary files a/test/benchmark/results/map-batch.jpg and b/test/benchmark/results/map-batch.jpg differ diff --git a/test/benchmark/results/map.jpg b/test/benchmark/results/map.jpg index c9ac16a7..884df5d3 100644 Binary files a/test/benchmark/results/map.jpg and b/test/benchmark/results/map.jpg differ diff --git a/test/benchmark/results/result.json b/test/benchmark/results/result.json index 1aa295c4..d2d6b5dc 100644 --- a/test/benchmark/results/result.json +++ b/test/benchmark/results/result.json @@ -21,11 +21,11 @@ }, "map-batch": { "name": "map-batch", - "avg": 3.4827841500897168 + "avg": 0.6617105542656838 }, "map": { "name": "map", - "avg": 5.7553703684454725 + "avg": 0.027595652427846274 }, "object-batch-getter": { "name": "object-batch-getter", diff --git a/test/benchmark/set-batch.ts b/test/benchmark/set-batch.ts index 9b7d46e9..0379cec8 100644 --- a/test/benchmark/set-batch.ts +++ b/test/benchmark/set-batch.ts @@ -1,6 +1,7 @@ /* eslint-disable import/no-relative-packages */ /* eslint-disable prefer-template */ // @ts-nocheck +(globalThis as any).__DEV__ = false; import fs from 'fs'; import path from 'path'; import https from 'https'; diff --git a/test/benchmark/set.ts b/test/benchmark/set.ts index 485a328f..2a71000e 100644 --- a/test/benchmark/set.ts +++ b/test/benchmark/set.ts @@ -1,6 +1,7 @@ /* eslint-disable import/no-relative-packages */ /* eslint-disable prefer-template */ // @ts-nocheck +(globalThis as any).__DEV__ = false; import fs from 'fs'; import path from 'path'; import https from 'https'; diff --git a/test/create.test.ts b/test/create.test.ts index 4e701b6f..1e8e0880 100644 --- a/test/create.test.ts +++ b/test/create.test.ts @@ -5,7 +5,9 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable no-param-reassign */ /* eslint-disable no-plusplus */ -import { create, isDraft } from '../src'; +import { create, isDraft, MutativeMap } from '../src'; +import { makeAssertDidNotChange } from './utils/identityChecking'; +import { assertHasNoDrafts } from './utils/assertHasNoDrafts'; describe('base', () => { test('object', () => { @@ -433,6 +435,291 @@ describe('base', () => { expect(state.map).not.toBe(data.map); }); + test('MutativeMap:set new', () => { + const initialState = { + bar: {}, + map: new MutativeMap([ + ['initial1', { a: 1 }], + ['initial2', { a: 2 }], + ]), + }; + const assertInitialStateDidNotChange = makeAssertDidNotChange(initialState); + + const newState = create(initialState, (draft) => { + draft.map.set('new1', { a: 3 }); + }); + assertHasNoDrafts(newState); + expect(newState).toMatchInlineSnapshot(` + { + "bar": {}, + "map": MutativeMap { + "_size": 3, + "immutableData": Map { + "initial1" => { + "a": 1, + }, + "initial2" => { + "a": 2, + }, + }, + "patchData": Map { + "new1" => { + "a": 3, + }, + }, + }, + } + `); + expect(newState).not.toBe(initialState); + expect(newState.bar).toBe(initialState.bar); + expect(newState.map).not.toBe(initialState.map); + expect(newState.map.get('initial1')).toBe(initialState.map.get('initial1')); + expect(newState.map.get('initial2')).toBe(initialState.map.get('initial2')); + expect(newState.map.get('new1')).not.toBe(initialState.map.get('new1')); + assertInitialStateDidNotChange(); + }); + test('MutativeMap update existing value (without calling set)', () => { + const initialState = { + bar: {}, + map: new MutativeMap([ + ['initial1', { a: 1 }], + ['initial2', { a: 2 }], + ]), + }; + const assertInitialStateDidNotChange = makeAssertDidNotChange(initialState); + + const newState = create(initialState, (draft) => { + draft.map.get('initial1')!.a = 3; + }); + assertHasNoDrafts(newState); + expect(newState).toMatchInlineSnapshot(` + { + "bar": {}, + "map": MutativeMap { + "_size": 2, + "immutableData": Map { + "initial1" => { + "a": 1, + }, + "initial2" => { + "a": 2, + }, + }, + "patchData": Map { + "initial1" => { + "a": 3, + }, + }, + }, + } + `); + expect(newState).not.toBe(initialState); + expect(newState.bar).toBe(initialState.bar); + expect(newState.map).not.toBe(initialState.map); + expect(newState.map.get('initial1')).not.toBe( + initialState.map.get('initial1') + ); + expect(newState.map.get('initial1')).toStrictEqual({ a: 3 }); + expect(newState.map.get('initial2')).toBe(initialState.map.get('initial2')); + assertInitialStateDidNotChange(); + }); + test('MutativeMap update existing value (without calling set) and read other value without changing it', () => { + const initialState = { + bar: {}, + map: new MutativeMap([ + ['initial1', { a: 1 }], + ['initial2', { a: 2 }], + ]), + }; + const assertInitialStateDidNotChange = makeAssertDidNotChange(initialState); + + const newState = create(initialState, (draft) => { + draft.map.get('initial1')!.a = draft.map.get('initial2')!.a + 3; + }); + assertHasNoDrafts(newState); + expect(newState).toMatchInlineSnapshot(` + { + "bar": {}, + "map": MutativeMap { + "_size": 2, + "immutableData": Map { + "initial1" => { + "a": 1, + }, + "initial2" => { + "a": 2, + }, + }, + "patchData": Map { + "initial1" => { + "a": 5, + }, + }, + }, + } + `); + expect(newState).not.toBe(initialState); + expect(newState.bar).toBe(initialState.bar); + expect(newState.map).not.toBe(initialState.map); + expect(newState.map.get('initial1')).not.toBe( + initialState.map.get('initial1') + ); + expect(newState.map.get('initial1')).toStrictEqual({ a: 5 }); + expect(newState.map.get('initial2')).toBe(initialState.map.get('initial2')); + assertInitialStateDidNotChange(); + }); + test('MutativeMap value is only read', () => { + const initialState = { + bar: {}, + map: new MutativeMap([ + ['initial1', { a: 1 }], + ['initial2', { a: 2 }], + ]), + }; + const assertInitialStateDidNotChange = makeAssertDidNotChange(initialState); + + const newState = create(initialState, (draft) => { + draft.bar = draft.map.get('initial1')!; + }); + assertHasNoDrafts(newState); + expect(newState).toMatchInlineSnapshot(` + { + "bar": { + "a": 1, + }, + "map": MutativeMap { + "_size": 2, + "immutableData": Map { + "initial1" => { + "a": 1, + }, + "initial2" => { + "a": 2, + }, + }, + "patchData": Map {}, + }, + } + `); + expect(newState).not.toBe(initialState); + expect(newState.bar).not.toBe(initialState.bar); + expect(newState.bar).toBe(initialState.map.get('initial1')); + expect(newState.map).toBe(initialState.map); + assertInitialStateDidNotChange(); + }); + test('MutativeMap additional utilities within and outside draft', () => { + const initialState = { + bar: {}, + map: new MutativeMap([ + ['initial1', { a: 1 }], + ['initial2', { a: 2 }], + ['initial3', { a: 3 }], + ]), + }; + const assertInitialStateDidNotChange = makeAssertDidNotChange(initialState); + + const newState = create(initialState, (draft) => { + expect(draft.map.keysArray()).toStrictEqual([ + 'initial1', + 'initial2', + 'initial3', + ]); + draft.map.get('initial2')!.a = 10; + + expect(draft.map.keysArray()).toStrictEqual([ + 'initial2', + 'initial1', + 'initial3', + ]); + const values = draft.map.valuesArray(); + expect(values).toStrictEqual([{ a: 10 }, { a: 1 }, { a: 3 }]); + // NOTE: the order is weird and changes due to drafting + expect(draft.map.valuesArray()).toStrictEqual([ + { a: 10 }, + { a: 1 }, + { a: 3 }, + ]); + expect(draft.map.valuesArray()).toStrictEqual([ + { a: 10 }, + { a: 1 }, + { a: 3 }, + ]); + // expect(JSON.stringify(values)).toStrictEqual('[{"a":3},{"a":2}]'); + const keys = draft.map.keysArray(); + // NOTE: the order of keys is weird due to drafting + // expect(keys).toStrictEqual(['initial2', 'initial1']); + expect(keys).toStrictEqual(['initial2', 'initial1', 'initial3']); + const entries = draft.map.entriesArray(); + expect(entries).toStrictEqual([ + ['initial2', { a: 10 }], + ['initial1', { a: 1 }], + ['initial3', { a: 3 }], + ]); + expect(Array.from(draft.map.entries())).toStrictEqual(entries); + expect(Array.from(draft.map.keys())).toStrictEqual(keys); + expect(draft.map.mapValues((value) => value.a)).toStrictEqual([10, 1, 3]); + }); + assertHasNoDrafts(newState); + expect(newState).toMatchInlineSnapshot(` + { + "bar": {}, + "map": MutativeMap { + "_size": 3, + "immutableData": Map { + "initial1" => { + "a": 1, + }, + "initial2" => { + "a": 2, + }, + "initial3" => { + "a": 3, + }, + }, + "patchData": Map { + "initial2" => { + "a": 10, + }, + }, + }, + } + `); + expect(newState.map.entriesArray()).toStrictEqual([ + ['initial2', { a: 10 }], + ['initial1', { a: 1 }], + ['initial3', { a: 3 }], + ]); + expect(newState.map.keysArray()).toStrictEqual([ + 'initial2', + 'initial1', + 'initial3', + ]); + expect(newState.map.valuesArray()).toStrictEqual([ + { a: 10 }, + { a: 1 }, + { a: 3 }, + ]); + expect(Array.from(newState.map.entries())).toStrictEqual([ + ['initial2', { a: 10 }], + ['initial1', { a: 1 }], + ['initial3', { a: 3 }], + ]); + expect(Array.from(newState.map.keys())).toStrictEqual([ + 'initial2', + 'initial1', + 'initial3', + ]); + expect(Array.from(newState.map.values())).toStrictEqual([ + { a: 10 }, + { a: 1 }, + { a: 3 }, + ]); + expect(newState.map.mapValues((value) => value.a)).toStrictEqual([ + 10, 1, 3, + ]); + assertInitialStateDidNotChange(); + }); + test('map clear', () => { const data = { bar: {}, diff --git a/test/generic-utils/assertAlways.ts b/test/generic-utils/assertAlways.ts new file mode 100644 index 00000000..79fcdd34 --- /dev/null +++ b/test/generic-utils/assertAlways.ts @@ -0,0 +1,16 @@ +export function assertAlways( + condition: unknown, + message?: string | (() => string) +): asserts condition { + if (!condition) { + if (message !== undefined) { + if (typeof message === 'function') { + throw new Error(message()); + } else { + throw new Error(message); + } + } else { + throw new Error(`assertion error`); + } + } +} diff --git a/test/generic-utils/recordUtil.ts b/test/generic-utils/recordUtil.ts new file mode 100644 index 00000000..36e81ca6 --- /dev/null +++ b/test/generic-utils/recordUtil.ts @@ -0,0 +1,206 @@ +import { assertAlways } from './assertAlways'; + +import { MutativeMap } from '../..'; + +/** + * + * @param value + * @param filter returns false to stop iteration within the current object + */ +export function deepIterateObjectNodes( + value: unknown, + filter?: (path: (string | number)[], value: unknown) => boolean +): Generator<[path: (string | number)[], value: unknown], void> { + return deepIterateObjectNodes2(value, [], filter); +} + +function* deepIterateObjectNodes2( + value: unknown, + path: (string | number)[], + filter?: (path: (string | number)[], value: unknown) => boolean +): Generator<[path: (string | number)[], value: unknown], void> { + yield [path, value]; + if (value == null) { + return; + } + const shouldContinue = filter?.(path, value); + if (shouldContinue === false) { + return; + } + if (Array.isArray(value)) { + for (let i = 0; i < value.length; i++) { + yield* deepIterateObjectNodes2(value[i], [...path, i], filter); + } + } else if (value instanceof Map || value instanceof MutativeMap) { + for (const [key, subValue] of value.entries()) { + yield* deepIterateObjectNodes2(subValue, [...path, key], filter); + } + } else if (value instanceof Set) { + // hack to iterate over set values + let i = 0; + for (const subValue of value.values()) { + yield* deepIterateObjectNodes2(subValue, [...path, i], filter); + i++; + } + } else if (typeof value === 'object') { + for (const [key, subValue] of Object.entries(value)) { + yield* deepIterateObjectNodes2(subValue, [...path, key], filter); + } + } +} + +/** + * similar to radash crush, but has arrays as paths and supports Maps and Sets + * TODO support symbols and other values in paths + fix typing for map keys that are not strings + * @param data + */ +export function getAllChildIntermediateAndLeafNodePaths( + data: unknown +): (string | number)[][] { + if (typeof data !== 'object' || data === null) { + return []; + } + if (data instanceof Map || data instanceof MutativeMap) { + const keys = [...data.keys()]; + return keys.flatMap((key) => { + return [ + [key], + ...getAllChildIntermediateAndLeafNodePaths(data.get(key)).map( + (path) => { + return [key, ...path]; + } + ), + ]; + }); + } + if (data instanceof Set) { + return getAllChildIntermediateAndLeafNodePaths([...data.values()]); + } + if (Array.isArray(data)) { + return data.flatMap((v, index) => { + return [ + [index], + ...getAllChildIntermediateAndLeafNodePaths(v).map((path) => [ + index, + ...path, + ]), + ]; + }); + } + const keys = Object.keys(data); + return keys.flatMap((key) => { + return [ + [key], + ...getAllChildIntermediateAndLeafNodePaths((data as any)[key]).map( + (path) => { + return [key, ...path]; + } + ), + ]; + }); +} + +export function getDeepValueByPath( + data: unknown, + path: (string | number)[] +): unknown { + let current = data; + for (const key of path) { + if (typeof current !== 'object' || current === null) { + throw new Error(`cannot get value by path '${path.join('.')}'`); + } + if (current instanceof Map || current instanceof MutativeMap) { + current = (current as Map | MutativeMap).get(key); + } else if (current instanceof Set) { + assertAlways( + typeof key === 'number', + () => 'key must be a index (number) for Set values but was ' + key + ); + current = [...current.values()][key as number]; + } else { + current = (current as any)[key]; + } + } + return current; +} + +export function tryGetDeepValueByPath( + data: unknown, + path: (string | number)[] +): undefined | { value: unknown } { + let current = data; + for (const key of path) { + if (typeof current !== 'object' || current === null) { + return undefined; + } + if (current instanceof Map || current instanceof MutativeMap) { + current = (current as Map | MutativeMap).get(key); + } else if (current instanceof Set) { + if (typeof key !== 'number') { + return undefined; + } + current = [...current.values()][key]; + } else { + current = (current as any)[key]; + } + } + return { value: current }; +} + +export function setDeepValueByPath( + data: unknown, + path: (string | number)[], + value: unknown +): void { + const parent = getDeepValueByPath(data, path.slice(0, -1)); + const key = path[path.length - 1]; + if (parent instanceof Map || parent instanceof MutativeMap) { + (parent as Map | MutativeMap).set(key, value); + } else if (parent instanceof Set) { + // TODO [unimportant] does this make any sense? + parent.add(value); + } else if (Array.isArray(parent)) { + assertAlways( + typeof key === 'number', + () => 'key must be a index (number) for array values but was ' + key + ); + if (parent.length === key) { + parent.push(value); + } else if (parent.length < key) { + throw new Error( + `cannot set value by path '${path.join('.')}' in array with length ${ + parent.length + }` + ); + } else { + parent[key] = value; + } + } else { + (parent as any)[key] = value; + } +} + +export function deleteDeepValueByPath( + data: unknown, + path: (string | number)[] +): void { + const parent = getDeepValueByPath(data, path.slice(0, -1)); + const key = path[path.length - 1]; + if (parent instanceof Map || parent instanceof MutativeMap) { + (parent as Map | MutativeMap).delete(key); + } else if (parent instanceof Set) { + assertAlways( + typeof key === 'number', + () => 'key must be a index (number) for Set values but was ' + key + ); + parent.delete([...parent.values()][key]); + } else if (Array.isArray(parent)) { + assertAlways( + typeof key === 'number', + () => 'key must be a index (number) for array values but was ' + key + ); + parent.splice(key, 1); + } else { + delete (parent as any)[key]; + } +} diff --git a/test/immer/__tests__/curry.ts b/test/immer/__tests__/curry.ts index f4041995..f3d9d68e 100644 --- a/test/immer/__tests__/curry.ts +++ b/test/immer/__tests__/curry.ts @@ -1,9 +1,5 @@ // @ts-nocheck -import { - produce, - produceWithPatches, - enablePatches, -} from '../src/immer'; +import { produce, produceWithPatches, enablePatches } from '../src/immer'; enablePatches(); diff --git a/test/immer/__tests__/frozen.ts b/test/immer/__tests__/frozen.ts index 941043b1..ed292a84 100644 --- a/test/immer/__tests__/frozen.ts +++ b/test/immer/__tests__/frozen.ts @@ -1,10 +1,5 @@ // @ts-nocheck -import { - produce, - setAutoFreeze, - freeze, - enableMapSet, -} from '../src/immer'; +import { produce, setAutoFreeze, freeze, enableMapSet } from '../src/immer'; enableMapSet(); diff --git a/test/immer/__tests__/manual.ts b/test/immer/__tests__/manual.ts index 82c8e0f8..6f3d77d8 100644 --- a/test/immer/__tests__/manual.ts +++ b/test/immer/__tests__/manual.ts @@ -52,7 +52,7 @@ function runTests(name) { it('cannot finishDraft twice', () => { const state = { a: 1 }; - const [draft, finishDraft]= createDraft(state); + const [draft, finishDraft] = createDraft(state); draft.a = 2; expect(finishDraft(draft)).toEqual({ a: 2 }); expect(() => { diff --git a/test/immer/__tests__/map-set.ts b/test/immer/__tests__/map-set.ts index a4fba0a6..160da992 100644 --- a/test/immer/__tests__/map-set.ts +++ b/test/immer/__tests__/map-set.ts @@ -17,7 +17,7 @@ jest.setTimeout(1000); runBaseTest('proxy (no freeze)', true, false); runBaseTest('proxy (autofreeze)', true, true); -runBaseTest('proxy (autofreeze)(patch listener)', true, true, true); +runBaseTest('proxy (autofreeze)(patch listener)', true, true, true); // TODO unused 4th arg? function runBaseTest(name, autoFreeze, useListener) { const listener = useListener ? function () {} : undefined; diff --git a/test/immer/src/immer.ts b/test/immer/src/immer.ts index e7b443cf..b39ce927 100644 --- a/test/immer/src/immer.ts +++ b/test/immer/src/immer.ts @@ -81,8 +81,7 @@ export { castDraft, } from '../../../src'; -export const applyPatches = (base: any, patches: any) => - apply(base, patches); +export const applyPatches = (base: any, patches: any) => apply(base, patches); export const enablePatches = () => {}; @@ -131,7 +130,7 @@ export const produceWithPatches = (base: any, recipe: any) => { export function freeze(obj: any, deep: boolean = false): T { if (isFrozen(obj) || isDraft(obj) || !isDraftable(obj)) return obj; - if (getArchtype(obj) > 1 /* Map or Set */) { + if (getArchtype(obj) > 1 /* Map or MutativeMap or Set */) { obj.set = obj.add = obj.clear = diff --git a/test/performance/array-object-first-time.ts b/test/performance/array-object-first-time.ts index ab37c86f..26d35963 100644 --- a/test/performance/array-object-first-time.ts +++ b/test/performance/array-object-first-time.ts @@ -1,5 +1,6 @@ // @ts-nocheck 'use strict'; +(globalThis as any).__DEV__ = false; import { produce, setAutoFreeze, enableMapSet } from 'immer'; import { create } from '../..'; diff --git a/test/performance/array-object.ts b/test/performance/array-object.ts index 71b3f4c1..eb0e4593 100644 --- a/test/performance/array-object.ts +++ b/test/performance/array-object.ts @@ -1,5 +1,6 @@ // @ts-nocheck 'use strict'; +(globalThis as any).__DEV__ = false; import { produce, setAutoFreeze, enableMapSet } from 'immer'; import { create } from '../..'; diff --git a/test/performance/benchmark-array.ts b/test/performance/benchmark-array.ts index 5189d30f..f4986c4d 100644 --- a/test/performance/benchmark-array.ts +++ b/test/performance/benchmark-array.ts @@ -1,6 +1,7 @@ /* eslint-disable import/no-relative-packages */ /* eslint-disable prefer-template */ // @ts-nocheck +(globalThis as any).__DEV__ = false; import fs from 'fs'; import https from 'https'; import { Suite } from 'benchmark'; diff --git a/test/performance/benchmark-class.ts b/test/performance/benchmark-class.ts index 77593b50..17091635 100644 --- a/test/performance/benchmark-class.ts +++ b/test/performance/benchmark-class.ts @@ -1,6 +1,7 @@ /* eslint-disable import/no-relative-packages */ /* eslint-disable prefer-template */ // @ts-nocheck +(globalThis as any).__DEV__ = false; import fs from 'fs'; import https from 'https'; import { Suite } from 'benchmark'; diff --git a/test/performance/benchmark-object.ts b/test/performance/benchmark-object.ts index 0d1139c0..7d247e8d 100644 --- a/test/performance/benchmark-object.ts +++ b/test/performance/benchmark-object.ts @@ -1,6 +1,7 @@ /* eslint-disable import/no-relative-packages */ /* eslint-disable prefer-template */ // @ts-nocheck +(globalThis as any).__DEV__ = false; import fs from 'fs'; import https from 'https'; import { Suite } from 'benchmark'; diff --git a/test/performance/benchmark.ts b/test/performance/benchmark.ts index 80b2ad03..dcf1c1b2 100644 --- a/test/performance/benchmark.ts +++ b/test/performance/benchmark.ts @@ -1,6 +1,7 @@ /* eslint-disable import/no-relative-packages */ /* eslint-disable prefer-template */ // @ts-nocheck +(globalThis as any).__DEV__ = false; import fs from 'fs'; import https from 'https'; import { Suite } from 'benchmark'; diff --git a/test/performance/benchmarks/forEach.ts b/test/performance/benchmarks/forEach.ts index 20f6da71..1a612a29 100644 --- a/test/performance/benchmarks/forEach.ts +++ b/test/performance/benchmarks/forEach.ts @@ -1,6 +1,7 @@ /* eslint-disable prefer-template */ /* eslint-disable @typescript-eslint/no-unused-vars */ // @ts-nocheck +(globalThis as any).__DEV__ = false; import fs from 'fs'; import { Suite } from 'benchmark'; import { parse } from 'json2csv'; diff --git a/test/performance/benchmarks/rawReturn.ts b/test/performance/benchmarks/rawReturn.ts index 54ab8ad6..4d5bed64 100644 --- a/test/performance/benchmarks/rawReturn.ts +++ b/test/performance/benchmarks/rawReturn.ts @@ -2,6 +2,7 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable no-unused-expressions */ // @ts-nocheck +(globalThis as any).__DEV__ = false; import fs from 'fs'; import { Suite } from 'benchmark'; import { parse } from 'json2csv'; diff --git a/test/performance/benchmarks/returnWithDraft.ts b/test/performance/benchmarks/returnWithDraft.ts index b2510115..26820e50 100644 --- a/test/performance/benchmarks/returnWithDraft.ts +++ b/test/performance/benchmarks/returnWithDraft.ts @@ -1,6 +1,7 @@ /* eslint-disable prefer-template */ /* eslint-disable @typescript-eslint/no-unused-vars */ // @ts-nocheck +(globalThis as any).__DEV__ = false; import fs from 'fs'; import { Suite } from 'benchmark'; import { parse } from 'json2csv'; diff --git a/test/performance/big-object.ts b/test/performance/big-object.ts index f0cf65f9..9fd8e256 100644 --- a/test/performance/big-object.ts +++ b/test/performance/big-object.ts @@ -1,5 +1,6 @@ // @ts-nocheck 'use strict'; +(globalThis as any).__DEV__ = false; import { produce, diff --git a/test/performance/index.ts b/test/performance/index.ts index 14716ab5..fa1d04f6 100644 --- a/test/performance/index.ts +++ b/test/performance/index.ts @@ -1,5 +1,6 @@ // @ts-nocheck 'use strict'; +(globalThis as any).__DEV__ = false; import { produce, diff --git a/test/performance/mutative-set-map.ts b/test/performance/mutative-set-map.ts new file mode 100644 index 00000000..16c87812 --- /dev/null +++ b/test/performance/mutative-set-map.ts @@ -0,0 +1,96 @@ +// @ts-nocheck +'use strict'; +(globalThis as any).__DEV__ = false; +import { create, MutativeMap } from '../..'; +import { measure } from '../__immer_performance_tests__/measure'; + +// TODO [performance] use minified version for benchmarks and performance tests. Apparently as it worked sometime without __DEV__ being defined and indicated by setting NODE_ENV=production, this probably already happened sometime but was broken since? + +const MAX = 100; + +const getData = (mapClass: any) => { + const baseState = { + // set: new Set( + // Array(10 ** 4 * 5) + // .fill('') + // .map((_, i) => ({ [i]: i })) + // ), + map: new mapClass( + Array(10 ** 4 * 5) + .fill('') + .map((_, i) => [i, { [i]: i }]) + ), + }; + return baseState; +}; + +interface BaseState { + // set: Set<{ + // [x: number]: number; + // }>; + map: + | Map< + number, + { + [x: number]: number; + } + > + | MutativeMap< + number, + { + [x: number]: number; + } + >; +} + +measure( + 'mutative - normal Map', + () => getData(Map), + (baseState: BaseState) => { + for (let i = 0; i < MAX; i++) { + const state = create(baseState, (draft) => { + // draft.set.add({ [i]: i }); + draft.map.get(i)[i] = i.toString(); + }); + } + } +); + +measure( + 'mutative - MutativeMap', + () => getData(MutativeMap), + (baseState: BaseState) => { + for (let i = 0; i < MAX; i++) { + const state = create(baseState, (draft) => { + // draft.set.add({ [i]: i }); + draft.map.get(i)[i] = i.toString(); + }); + } + } +); + +// measure( +// 'tmpRoot - mutative - normal Map', +// () => getData(Map).map, +// (baseState: BaseState) => { +// for (let i = 0; i < MAX; i++) { +// const state = create(baseState, (draft) => { +// // draft.set.add({ [i]: i }); +// draft.get(i)[i] = i.toString(); +// }); +// } +// } +// ); +// +// measure( +// 'tmpRoot - mutative - MutativeMap', +// () => getData(MutativeMap).map, +// (baseState: BaseState) => { +// for (let i = 0; i < MAX; i++) { +// const state = create(baseState, (draft) => { +// // draft.set.add({ [i]: i }); +// draft.get(i)[i] = i.toString(); +// }); +// } +// } +// ); diff --git a/test/performance/new-set-api.ts b/test/performance/new-set-api.ts index 1356fd45..06ed9a54 100644 --- a/test/performance/new-set-api.ts +++ b/test/performance/new-set-api.ts @@ -124,7 +124,7 @@ const run = (size: number) => { .add( 'Set - shallow copy with difference()', () => { - const state = Set.prototype.difference.call(baseState, new Set()) + const state = Set.prototype.difference.call(baseState, new Set()); }, { onStart: () => { diff --git a/test/performance/read-draft/index.ts b/test/performance/read-draft/index.ts index c039be15..75aeadb5 100644 --- a/test/performance/read-draft/index.ts +++ b/test/performance/read-draft/index.ts @@ -1,5 +1,6 @@ /* eslint-disable prefer-template */ // @ts-nocheck +(globalThis as any).__DEV__ = false; import { produce } from 'immer'; import { create } from '../../..'; import { createTable, updateTable } from './mockPhysics'; diff --git a/test/performance/sample.ts b/test/performance/sample.ts index 9fb0da37..8c0fa6c5 100644 --- a/test/performance/sample.ts +++ b/test/performance/sample.ts @@ -1,4 +1,5 @@ // @ts-nocheck +(globalThis as any).__DEV__ = false; import { produce } from 'immer'; import { create } from '../..'; diff --git a/test/performance/set-map.ts b/test/performance/set-map.ts index 60c541b0..64058454 100644 --- a/test/performance/set-map.ts +++ b/test/performance/set-map.ts @@ -1,5 +1,6 @@ // @ts-nocheck 'use strict'; +(globalThis as any).__DEV__ = false; import { produce, @@ -10,6 +11,7 @@ import { } from 'immer'; import { create } from '../..'; import { measure } from '../__immer_performance_tests__/measure'; +// TODO [performance] use minified version for benchmarks and performance tests. Apparently as it worked sometime without __DEV__ being defined and indicated by setting NODE_ENV=production, this probably already happened sometime but was broken since? const MAX = 10; diff --git a/test/utils/assertHasNoDrafts.ts b/test/utils/assertHasNoDrafts.ts new file mode 100644 index 00000000..1d782ec9 --- /dev/null +++ b/test/utils/assertHasNoDrafts.ts @@ -0,0 +1,30 @@ +import { deepIterateObjectNodes } from '../generic-utils/recordUtil'; +import { isDraft } from '../../src'; + +export function assertHasNoDrafts(obj: any) { + for (const [path, value] of deepIterateObjectNodes(obj)) { + let isDraftResult: boolean; + try { + isDraftResult = isDraft(value); + } catch (e) { + if ( + e instanceof Error && + e.message.endsWith('on a proxy that has been revoked') + ) { + // throw new Error(`Draft found at path ${path.join('.')}, but the proxy has been revoked`, { + // cause: e, + // }); + // TODO separate tsconfig for tests to upgrade tsconfig target? + throw new Error( + `Draft found at path ${path.join( + '.' + )}, but the proxy has been revoked` + ); + } + throw e; + } + if (isDraftResult) { + throw new Error(`Draft found at path ${path.join('.')}`); + } + } +} diff --git a/test/utils/identityChecking.ts b/test/utils/identityChecking.ts new file mode 100644 index 00000000..fa7a7803 --- /dev/null +++ b/test/utils/identityChecking.ts @@ -0,0 +1,27 @@ +import { + getAllChildIntermediateAndLeafNodePaths, + getDeepValueByPath, +} from '../generic-utils/recordUtil'; +import { format as prettyFormat } from 'pretty-format'; + +/** + * returns a function that asserts that the value did not change in any way (deep equality and identity checks) + * @param value + */ +export function makeAssertDidNotChange(value: any) { + const strRepr = prettyFormat(value); + const allPaths = getAllChildIntermediateAndLeafNodePaths(value); + const valuePerPath = allPaths.map((path) => { + return getDeepValueByPath(value, path); + }); + + return function assertDidNotChange() { + // console.debug('checking that the value did not change, strRepr:', strRepr); + expect(prettyFormat(value)).toStrictEqual(strRepr); + const newAllPaths = getAllChildIntermediateAndLeafNodePaths(value); + expect(newAllPaths).toStrictEqual(allPaths); + allPaths.forEach((path, index) => { + expect(getDeepValueByPath(value, path)).toBe(valuePerPath[index]); + }); + }; +} diff --git a/yarn.lock b/yarn.lock index 61d849ca..dc1c5c1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -396,121 +396,241 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/natural-compare/-/natural-compare-1.1.0.tgz#75f0642ad64701ffa9d42f1d7ada3b83f4e67cf3" integrity sha512-yuctPJs5lRXoI8LkpVZGAV6n+DKOuEsfpfcIDQ8ZjWHwazqk1QjBc4jMlof0UlZHyUqv4dwsOTooMiAmtzvwXA== +"@esbuild/aix-ppc64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz#51299374de171dbd80bb7d838e1cfce9af36f353" + integrity sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ== + "@esbuild/aix-ppc64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz#b57697945b50e99007b4c2521507dc613d4a648c" integrity sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw== +"@esbuild/android-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz#58565291a1fe548638adb9c584237449e5e14018" + integrity sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw== + "@esbuild/android-arm64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz#1add7e0af67acefd556e407f8497e81fddad79c0" integrity sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w== +"@esbuild/android-arm@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.23.1.tgz#5eb8c652d4c82a2421e3395b808e6d9c42c862ee" + integrity sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ== + "@esbuild/android-arm@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.24.0.tgz#ab7263045fa8e090833a8e3c393b60d59a789810" integrity sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew== +"@esbuild/android-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.23.1.tgz#ae19d665d2f06f0f48a6ac9a224b3f672e65d517" + integrity sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg== + "@esbuild/android-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.24.0.tgz#e8f8b196cfdfdd5aeaebbdb0110983460440e705" integrity sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ== +"@esbuild/darwin-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz#05b17f91a87e557b468a9c75e9d85ab10c121b16" + integrity sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q== + "@esbuild/darwin-arm64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz#2d0d9414f2acbffd2d86e98253914fca603a53dd" integrity sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw== +"@esbuild/darwin-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz#c58353b982f4e04f0d022284b8ba2733f5ff0931" + integrity sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw== + "@esbuild/darwin-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz#33087aab31a1eb64c89daf3d2cf8ce1775656107" integrity sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA== +"@esbuild/freebsd-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz#f9220dc65f80f03635e1ef96cfad5da1f446f3bc" + integrity sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA== + "@esbuild/freebsd-arm64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz#bb76e5ea9e97fa3c753472f19421075d3a33e8a7" integrity sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA== +"@esbuild/freebsd-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz#69bd8511fa013b59f0226d1609ac43f7ce489730" + integrity sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g== + "@esbuild/freebsd-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz#e0e2ce9249fdf6ee29e5dc3d420c7007fa579b93" integrity sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ== +"@esbuild/linux-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz#8050af6d51ddb388c75653ef9871f5ccd8f12383" + integrity sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g== + "@esbuild/linux-arm64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz#d1b2aa58085f73ecf45533c07c82d81235388e75" integrity sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g== +"@esbuild/linux-arm@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz#ecaabd1c23b701070484990db9a82f382f99e771" + integrity sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ== + "@esbuild/linux-arm@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz#8e4915df8ea3e12b690a057e77a47b1d5935ef6d" integrity sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw== +"@esbuild/linux-ia32@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz#3ed2273214178109741c09bd0687098a0243b333" + integrity sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ== + "@esbuild/linux-ia32@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz#8200b1110666c39ab316572324b7af63d82013fb" integrity sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA== +"@esbuild/linux-loong64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz#a0fdf440b5485c81b0fbb316b08933d217f5d3ac" + integrity sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw== + "@esbuild/linux-loong64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz#6ff0c99cf647504df321d0640f0d32e557da745c" integrity sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g== +"@esbuild/linux-mips64el@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz#e11a2806346db8375b18f5e104c5a9d4e81807f6" + integrity sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q== + "@esbuild/linux-mips64el@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz#3f720ccd4d59bfeb4c2ce276a46b77ad380fa1f3" integrity sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA== +"@esbuild/linux-ppc64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz#06a2744c5eaf562b1a90937855b4d6cf7c75ec96" + integrity sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw== + "@esbuild/linux-ppc64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz#9d6b188b15c25afd2e213474bf5f31e42e3aa09e" integrity sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ== +"@esbuild/linux-riscv64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz#65b46a2892fc0d1af4ba342af3fe0fa4a8fe08e7" + integrity sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA== + "@esbuild/linux-riscv64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz#f989fdc9752dfda286c9cd87c46248e4dfecbc25" integrity sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw== +"@esbuild/linux-s390x@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz#e71ea18c70c3f604e241d16e4e5ab193a9785d6f" + integrity sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw== + "@esbuild/linux-s390x@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz#29ebf87e4132ea659c1489fce63cd8509d1c7319" integrity sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g== +"@esbuild/linux-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz#d47f97391e80690d4dfe811a2e7d6927ad9eed24" + integrity sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ== + "@esbuild/linux-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz#4af48c5c0479569b1f359ffbce22d15f261c0cef" integrity sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA== +"@esbuild/netbsd-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz#44e743c9778d57a8ace4b72f3c6b839a3b74a653" + integrity sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA== + "@esbuild/netbsd-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz#1ae73d23cc044a0ebd4f198334416fb26c31366c" integrity sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg== +"@esbuild/openbsd-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz#05c5a1faf67b9881834758c69f3e51b7dee015d7" + integrity sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q== + "@esbuild/openbsd-arm64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz#5d904a4f5158c89859fd902c427f96d6a9e632e2" integrity sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg== +"@esbuild/openbsd-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz#2e58ae511bacf67d19f9f2dcd9e8c5a93f00c273" + integrity sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA== + "@esbuild/openbsd-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz#4c8aa88c49187c601bae2971e71c6dc5e0ad1cdf" integrity sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q== +"@esbuild/sunos-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz#adb022b959d18d3389ac70769cef5a03d3abd403" + integrity sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA== + "@esbuild/sunos-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz#8ddc35a0ea38575fa44eda30a5ee01ae2fa54dd4" integrity sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA== +"@esbuild/win32-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz#84906f50c212b72ec360f48461d43202f4c8b9a2" + integrity sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A== + "@esbuild/win32-arm64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz#6e79c8543f282c4539db684a207ae0e174a9007b" integrity sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA== +"@esbuild/win32-ia32@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz#5e3eacc515820ff729e90d0cb463183128e82fac" + integrity sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ== + "@esbuild/win32-ia32@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz#057af345da256b7192d18b676a02e95d0fa39103" integrity sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw== +"@esbuild/win32-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz#81fd50d11e2c32b2d6241470e3185b70c7b30699" + integrity sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg== + "@esbuild/win32-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz#168ab1c7e1c318b922637fad8f339d48b01e1244" @@ -955,10 +1075,10 @@ is-module "^1.0.0" resolve "^1.22.1" -"@rollup/plugin-replace@^5.0.5": - version "5.0.5" - resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-5.0.5.tgz#33d5653dce6d03cb24ef98bef7f6d25b57faefdf" - integrity sha512-rYO4fOi8lMaTg/z5Jb+hKnrHHVn8j2lwkqwyS4kTRhKyWOLf2wST2sWXr4WzWiTcoHTp2sTjqUbqIj2E39slKQ== +"@rollup/plugin-replace@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-6.0.2.tgz#2f565d312d681e4570ff376c55c5c08eb6f1908d" + integrity sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ== dependencies: "@rollup/pluginutils" "^5.0.1" magic-string "^0.30.3" @@ -998,85 +1118,100 @@ estree-walker "^2.0.2" picomatch "^4.0.2" -"@rollup/rollup-android-arm-eabi@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz#8b613b9725e8f9479d142970b106b6ae878610d5" - integrity sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w== - -"@rollup/rollup-android-arm64@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz#654ca1049189132ff602bfcf8df14c18da1f15fb" - integrity sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA== - -"@rollup/rollup-darwin-arm64@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz#6d241d099d1518ef0c2205d96b3fa52e0fe1954b" - integrity sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q== - -"@rollup/rollup-darwin-x64@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz#42bd19d292a57ee11734c980c4650de26b457791" - integrity sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw== - -"@rollup/rollup-linux-arm-gnueabihf@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz#f23555ee3d8fe941c5c5fd458cd22b65eb1c2232" - integrity sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ== - -"@rollup/rollup-linux-arm-musleabihf@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz#f3bbd1ae2420f5539d40ac1fde2b38da67779baa" - integrity sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg== - -"@rollup/rollup-linux-arm64-gnu@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz#7abe900120113e08a1f90afb84c7c28774054d15" - integrity sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw== - -"@rollup/rollup-linux-arm64-musl@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz#9e655285c8175cd44f57d6a1e8e5dedfbba1d820" - integrity sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA== - -"@rollup/rollup-linux-powerpc64le-gnu@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz#9a79ae6c9e9d8fe83d49e2712ecf4302db5bef5e" - integrity sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg== - -"@rollup/rollup-linux-riscv64-gnu@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz#67ac70eca4ace8e2942fabca95164e8874ab8128" - integrity sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA== - -"@rollup/rollup-linux-s390x-gnu@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz#9f883a7440f51a22ed7f99e1d070bd84ea5005fc" - integrity sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q== - -"@rollup/rollup-linux-x64-gnu@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz#70116ae6c577fe367f58559e2cffb5641a1dd9d0" - integrity sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg== - -"@rollup/rollup-linux-x64-musl@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz#f473f88219feb07b0b98b53a7923be716d1d182f" - integrity sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g== - -"@rollup/rollup-win32-arm64-msvc@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz#4349482d17f5d1c58604d1c8900540d676f420e0" - integrity sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw== - -"@rollup/rollup-win32-ia32-msvc@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz#a6fc39a15db618040ec3c2a24c1e26cb5f4d7422" - integrity sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g== - -"@rollup/rollup-win32-x64-msvc@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz#3dd5d53e900df2a40841882c02e56f866c04d202" - integrity sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q== +"@rollup/rollup-android-arm-eabi@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz#7f4c4d8cd5ccab6e95d6750dbe00321c1f30791e" + integrity sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ== + +"@rollup/rollup-android-arm64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz#17ea71695fb1518c2c324badbe431a0bd1879f2d" + integrity sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA== + +"@rollup/rollup-darwin-arm64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz#dac0f0d0cfa73e7d5225ae6d303c13c8979e7999" + integrity sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ== + +"@rollup/rollup-darwin-x64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz#8f63baa1d31784904a380d2e293fa1ddf53dd4a2" + integrity sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ== + +"@rollup/rollup-freebsd-arm64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz#30ed247e0df6e8858cdc6ae4090e12dbeb8ce946" + integrity sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA== + +"@rollup/rollup-freebsd-x64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz#57846f382fddbb508412ae07855b8a04c8f56282" + integrity sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ== + +"@rollup/rollup-linux-arm-gnueabihf@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz#378ca666c9dae5e6f94d1d351e7497c176e9b6df" + integrity sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA== + +"@rollup/rollup-linux-arm-musleabihf@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz#a692eff3bab330d5c33a5d5813a090c15374cddb" + integrity sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg== + +"@rollup/rollup-linux-arm64-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz#6b1719b76088da5ac1ae1feccf48c5926b9e3db9" + integrity sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA== + +"@rollup/rollup-linux-arm64-musl@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz#865baf5b6f5ff67acb32e5a359508828e8dc5788" + integrity sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A== + +"@rollup/rollup-linux-loongarch64-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz#23c6609ba0f7fa7a7f2038b6b6a08555a5055a87" + integrity sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA== + +"@rollup/rollup-linux-powerpc64le-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz#652ef0d9334a9f25b9daf85731242801cb0fc41c" + integrity sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A== + +"@rollup/rollup-linux-riscv64-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz#1eb6651839ee6ebca64d6cc64febbd299e95e6bd" + integrity sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA== + +"@rollup/rollup-linux-s390x-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz#015c52293afb3ff2a293cf0936b1d43975c1e9cd" + integrity sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg== + +"@rollup/rollup-linux-x64-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz#b83001b5abed2bcb5e2dbeec6a7e69b194235c1e" + integrity sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw== + +"@rollup/rollup-linux-x64-musl@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz#6cc7c84cd4563737f8593e66f33b57d8e228805b" + integrity sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g== + +"@rollup/rollup-win32-arm64-msvc@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz#631ffeee094d71279fcd1fe8072bdcf25311bc11" + integrity sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A== + +"@rollup/rollup-win32-ia32-msvc@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz#06d1d60d5b9f718e8a6c4a43f82e3f9e3254587f" + integrity sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA== + +"@rollup/rollup-win32-x64-msvc@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz#4dff5c4259ebe6c5b4a8f2c5bc3829b7a8447ff0" + integrity sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA== "@rtsao/scc@^1.1.0": version "1.1.0" @@ -1393,16 +1528,11 @@ dependencies: "@babel/types" "^7.20.7" -"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.5", "@types/estree@^1.0.6": +"@types/estree@*", "@types/estree@1.0.6", "@types/estree@^1.0.0", "@types/estree@^1.0.5", "@types/estree@^1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== -"@types/estree@1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" - integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== - "@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" @@ -2491,6 +2621,13 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +copy-anything@^3.0.2: + version "3.0.5" + resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-3.0.5.tgz#2d92dce8c498f790fa7ad16b01a1ae5a45b020a0" + integrity sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w== + dependencies: + is-what "^4.1.8" + core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -3075,6 +3212,36 @@ esbuild@^0.24.0: "@esbuild/win32-ia32" "0.24.0" "@esbuild/win32-x64" "0.24.0" +esbuild@~0.23.0: + version "0.23.1" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.23.1.tgz#40fdc3f9265ec0beae6f59824ade1bd3d3d2dab8" + integrity sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg== + optionalDependencies: + "@esbuild/aix-ppc64" "0.23.1" + "@esbuild/android-arm" "0.23.1" + "@esbuild/android-arm64" "0.23.1" + "@esbuild/android-x64" "0.23.1" + "@esbuild/darwin-arm64" "0.23.1" + "@esbuild/darwin-x64" "0.23.1" + "@esbuild/freebsd-arm64" "0.23.1" + "@esbuild/freebsd-x64" "0.23.1" + "@esbuild/linux-arm" "0.23.1" + "@esbuild/linux-arm64" "0.23.1" + "@esbuild/linux-ia32" "0.23.1" + "@esbuild/linux-loong64" "0.23.1" + "@esbuild/linux-mips64el" "0.23.1" + "@esbuild/linux-ppc64" "0.23.1" + "@esbuild/linux-riscv64" "0.23.1" + "@esbuild/linux-s390x" "0.23.1" + "@esbuild/linux-x64" "0.23.1" + "@esbuild/netbsd-x64" "0.23.1" + "@esbuild/openbsd-arm64" "0.23.1" + "@esbuild/openbsd-x64" "0.23.1" + "@esbuild/sunos-x64" "0.23.1" + "@esbuild/win32-arm64" "0.23.1" + "@esbuild/win32-ia32" "0.23.1" + "@esbuild/win32-x64" "0.23.1" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -3594,7 +3761,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.3.2: +fsevents@^2.3.2, fsevents@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== @@ -3696,6 +3863,13 @@ get-symbol-description@^1.0.2: es-errors "^1.3.0" get-intrinsic "^1.2.4" +get-tsconfig@^4.7.5: + version "4.8.1" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.8.1.tgz#8995eb391ae6e1638d251118c7b56de7eb425471" + integrity sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg== + dependencies: + resolve-pkg-maps "^1.0.0" + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -4379,6 +4553,11 @@ is-weakset@^2.0.3: call-bind "^1.0.7" get-intrinsic "^1.2.4" +is-what@^4.1.8: + version "4.1.16" + resolved "https://registry.yarnpkg.com/is-what/-/is-what-4.1.16.tgz#1ad860a19da8b4895ad5495da3182ce2acdd7a6f" + integrity sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A== + is-windows@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -5795,6 +5974,11 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +"prettier-2@npm:prettier@^2": + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + prettier-linter-helpers@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" @@ -6026,6 +6210,11 @@ resolve-global@^1.0.0: dependencies: global-dirs "^0.1.1" +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + resolve.exports@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" @@ -6078,29 +6267,32 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -rollup@^4.9.0: - version "4.22.4" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.22.4.tgz#4135a6446671cd2a2453e1ad42a45d5973ec3a0f" - integrity sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A== +rollup@^4.28.1: + version "4.28.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.28.1.tgz#7718ba34d62b449dfc49adbfd2f312b4fe0df4de" + integrity sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg== dependencies: - "@types/estree" "1.0.5" + "@types/estree" "1.0.6" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.22.4" - "@rollup/rollup-android-arm64" "4.22.4" - "@rollup/rollup-darwin-arm64" "4.22.4" - "@rollup/rollup-darwin-x64" "4.22.4" - "@rollup/rollup-linux-arm-gnueabihf" "4.22.4" - "@rollup/rollup-linux-arm-musleabihf" "4.22.4" - "@rollup/rollup-linux-arm64-gnu" "4.22.4" - "@rollup/rollup-linux-arm64-musl" "4.22.4" - "@rollup/rollup-linux-powerpc64le-gnu" "4.22.4" - "@rollup/rollup-linux-riscv64-gnu" "4.22.4" - "@rollup/rollup-linux-s390x-gnu" "4.22.4" - "@rollup/rollup-linux-x64-gnu" "4.22.4" - "@rollup/rollup-linux-x64-musl" "4.22.4" - "@rollup/rollup-win32-arm64-msvc" "4.22.4" - "@rollup/rollup-win32-ia32-msvc" "4.22.4" - "@rollup/rollup-win32-x64-msvc" "4.22.4" + "@rollup/rollup-android-arm-eabi" "4.28.1" + "@rollup/rollup-android-arm64" "4.28.1" + "@rollup/rollup-darwin-arm64" "4.28.1" + "@rollup/rollup-darwin-x64" "4.28.1" + "@rollup/rollup-freebsd-arm64" "4.28.1" + "@rollup/rollup-freebsd-x64" "4.28.1" + "@rollup/rollup-linux-arm-gnueabihf" "4.28.1" + "@rollup/rollup-linux-arm-musleabihf" "4.28.1" + "@rollup/rollup-linux-arm64-gnu" "4.28.1" + "@rollup/rollup-linux-arm64-musl" "4.28.1" + "@rollup/rollup-linux-loongarch64-gnu" "4.28.1" + "@rollup/rollup-linux-powerpc64le-gnu" "4.28.1" + "@rollup/rollup-linux-riscv64-gnu" "4.28.1" + "@rollup/rollup-linux-s390x-gnu" "4.28.1" + "@rollup/rollup-linux-x64-gnu" "4.28.1" + "@rollup/rollup-linux-x64-musl" "4.28.1" + "@rollup/rollup-win32-arm64-msvc" "4.28.1" + "@rollup/rollup-win32-ia32-msvc" "4.28.1" + "@rollup/rollup-win32-x64-msvc" "4.28.1" fsevents "~2.3.2" run-async@^2.4.0: @@ -6523,6 +6715,13 @@ strip-json-comments@3.1.1, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +superjson@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/superjson/-/superjson-2.2.2.tgz#9d52bf0bf6b5751a3c3472f1292e714782ba3173" + integrity sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q== + dependencies: + copy-anything "^3.0.2" + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -6745,6 +6944,16 @@ tslib@^2.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== +tsx@^4.19.2: + version "4.19.2" + resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.19.2.tgz#2d7814783440e0ae42354d0417d9c2989a2ae92c" + integrity sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g== + dependencies: + esbuild "~0.23.0" + get-tsconfig "^4.7.5" + optionalDependencies: + fsevents "~2.3.3" + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"