@tldraw/state 3.16.0-next.f9f54ec051f3 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +268 -5
- package/dist-cjs/index.d.ts +7 -6
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/Atom.js.map +1 -1
- package/dist-cjs/lib/Computed.js.map +1 -1
- package/dist-cjs/lib/EffectScheduler.js +7 -0
- package/dist-cjs/lib/EffectScheduler.js.map +2 -2
- package/dist-esm/index.d.mts +7 -6
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/Atom.mjs.map +1 -1
- package/dist-esm/lib/Computed.mjs.map +1 -1
- package/dist-esm/lib/EffectScheduler.mjs +7 -0
- package/dist-esm/lib/EffectScheduler.mjs.map +2 -2
- package/package.json +9 -16
- package/src/lib/Atom.ts +1 -1
- package/src/lib/Computed.ts +3 -3
- package/src/lib/EffectScheduler.ts +11 -2
- package/src/lib/__tests__/computed.test.ts +9 -8
- package/src/lib/__tests__/errors.test.ts +8 -8
- package/src/lib/__tests__/reactor.test.ts +8 -7
- package/src/lib/__tests__/transactions.test.ts +7 -6
package/dist-esm/index.d.mts
CHANGED
|
@@ -87,7 +87,7 @@ export declare interface AtomOptions<Value, Diff> {
|
|
|
87
87
|
* By default, values are compared using first using strict equality (`===`), then `Object.is`, and finally any `.equals` method present in the object's prototype chain.
|
|
88
88
|
* @param a - The old value
|
|
89
89
|
* @param b - The new value
|
|
90
|
-
* @returns
|
|
90
|
+
* @returns True if the values are equal, false otherwise.
|
|
91
91
|
*/
|
|
92
92
|
isEqual?(a: any, b: any): boolean;
|
|
93
93
|
}
|
|
@@ -201,15 +201,15 @@ export declare interface ComputedOptions<Value, Diff> {
|
|
|
201
201
|
*/
|
|
202
202
|
historyLength?: number;
|
|
203
203
|
/**
|
|
204
|
-
* A method used to compute a diff between the
|
|
204
|
+
* A method used to compute a diff between the computed's old and new values. If provided, it will not be used unless you also specify {@link ComputedOptions.historyLength}.
|
|
205
205
|
*/
|
|
206
206
|
computeDiff?: ComputeDiff<Value, Diff>;
|
|
207
207
|
/**
|
|
208
|
-
* If provided, this will be used to compare the old and new values of the
|
|
208
|
+
* If provided, this will be used to compare the old and new values of the computed to determine if the value has changed.
|
|
209
209
|
* By default, values are compared using first using strict equality (`===`), then `Object.is`, and finally any `.equals` method present in the object's prototype chain.
|
|
210
210
|
* @param a - The old value
|
|
211
211
|
* @param b - The new value
|
|
212
|
-
* @returns
|
|
212
|
+
* @returns True if the values are equal, false otherwise.
|
|
213
213
|
*/
|
|
214
214
|
isEqual?(a: any, b: any): boolean;
|
|
215
215
|
}
|
|
@@ -237,7 +237,6 @@ export declare const EffectScheduler: new <Result>(name: string, runEffect: (las
|
|
|
237
237
|
|
|
238
238
|
/** @public */
|
|
239
239
|
export declare interface EffectScheduler<Result> {
|
|
240
|
-
/** @internal */
|
|
241
240
|
/**
|
|
242
241
|
* Whether this scheduler is attached and actively listening to its parents.
|
|
243
242
|
* @public
|
|
@@ -268,11 +267,13 @@ export declare interface EffectScheduler<Result> {
|
|
|
268
267
|
/**
|
|
269
268
|
* Makes this scheduler stop 'actively listening' to its parents.
|
|
270
269
|
* It will no longer be eligible to receive 'maybeScheduleEffect' calls until `EffectScheduler.attach` is called again.
|
|
270
|
+
* @public
|
|
271
271
|
*/
|
|
272
272
|
detach(): void;
|
|
273
273
|
/**
|
|
274
274
|
* Executes the effect immediately and returns the result.
|
|
275
275
|
* @returns The result of the effect.
|
|
276
|
+
* @public
|
|
276
277
|
*/
|
|
277
278
|
execute(): Result;
|
|
278
279
|
}
|
|
@@ -306,7 +307,7 @@ export declare interface EffectSchedulerOptions {
|
|
|
306
307
|
* ```
|
|
307
308
|
*
|
|
308
309
|
* @param execute - A function that will execute the effect.
|
|
309
|
-
* @returns
|
|
310
|
+
* @returns void
|
|
310
311
|
*/
|
|
311
312
|
scheduleEffect?: (execute: () => void) => void;
|
|
312
313
|
}
|
package/dist-esm/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/Atom.ts"],
|
|
4
|
-
"sourcesContent": ["import { ArraySet } from './ArraySet'\nimport { HistoryBuffer } from './HistoryBuffer'\nimport { maybeCaptureParent } from './capture'\nimport { EMPTY_ARRAY, equals, singleton } from './helpers'\nimport { advanceGlobalEpoch, atomDidChange, getGlobalEpoch } from './transactions'\nimport { Child, ComputeDiff, RESET_VALUE, Signal } from './types'\n\n/**\n * The options to configure an atom, passed into the {@link atom} function.\n * @public\n */\nexport interface AtomOptions<Value, Diff> {\n\t/**\n\t * The maximum number of diffs to keep in the history buffer.\n\t *\n\t * If you don't need to compute diffs, or if you will supply diffs manually via {@link Atom.set}, you can leave this as `undefined` and no history buffer will be created.\n\t *\n\t * If you expect the value to be part of an active effect subscription all the time, and to not change multiple times inside of a single transaction, you can set this to a relatively low number (e.g. 10).\n\t *\n\t * Otherwise, set this to a higher number based on your usage pattern and memory constraints.\n\t *\n\t */\n\thistoryLength?: number\n\t/**\n\t * A method used to compute a diff between the atom's old and new values. If provided, it will not be used unless you also specify {@link AtomOptions.historyLength}.\n\t */\n\tcomputeDiff?: ComputeDiff<Value, Diff>\n\t/**\n\t * If provided, this will be used to compare the old and new values of the atom to determine if the value has changed.\n\t * By default, values are compared using first using strict equality (`===`), then `Object.is`, and finally any `.equals` method present in the object's prototype chain.\n\t * @param a - The old value\n\t * @param b - The new value\n\t * @returns
|
|
4
|
+
"sourcesContent": ["import { ArraySet } from './ArraySet'\nimport { HistoryBuffer } from './HistoryBuffer'\nimport { maybeCaptureParent } from './capture'\nimport { EMPTY_ARRAY, equals, singleton } from './helpers'\nimport { advanceGlobalEpoch, atomDidChange, getGlobalEpoch } from './transactions'\nimport { Child, ComputeDiff, RESET_VALUE, Signal } from './types'\n\n/**\n * The options to configure an atom, passed into the {@link atom} function.\n * @public\n */\nexport interface AtomOptions<Value, Diff> {\n\t/**\n\t * The maximum number of diffs to keep in the history buffer.\n\t *\n\t * If you don't need to compute diffs, or if you will supply diffs manually via {@link Atom.set}, you can leave this as `undefined` and no history buffer will be created.\n\t *\n\t * If you expect the value to be part of an active effect subscription all the time, and to not change multiple times inside of a single transaction, you can set this to a relatively low number (e.g. 10).\n\t *\n\t * Otherwise, set this to a higher number based on your usage pattern and memory constraints.\n\t *\n\t */\n\thistoryLength?: number\n\t/**\n\t * A method used to compute a diff between the atom's old and new values. If provided, it will not be used unless you also specify {@link AtomOptions.historyLength}.\n\t */\n\tcomputeDiff?: ComputeDiff<Value, Diff>\n\t/**\n\t * If provided, this will be used to compare the old and new values of the atom to determine if the value has changed.\n\t * By default, values are compared using first using strict equality (`===`), then `Object.is`, and finally any `.equals` method present in the object's prototype chain.\n\t * @param a - The old value\n\t * @param b - The new value\n\t * @returns True if the values are equal, false otherwise.\n\t */\n\tisEqual?(a: any, b: any): boolean\n}\n\n/**\n * An Atom is a signal that can be updated directly by calling {@link Atom.set} or {@link Atom.update}.\n *\n * Atoms are created using the {@link atom} function.\n *\n * @example\n * ```ts\n * const name = atom('name', 'John')\n *\n * print(name.get()) // 'John'\n * ```\n *\n * @public\n */\nexport interface Atom<Value, Diff = unknown> extends Signal<Value, Diff> {\n\t/**\n\t * Sets the value of this atom to the given value. If the value is the same as the current value, this is a no-op.\n\t *\n\t * @param value - The new value to set.\n\t * @param diff - The diff to use for the update. If not provided, the diff will be computed using {@link AtomOptions.computeDiff}.\n\t */\n\tset(value: Value, diff?: Diff): Value\n\t/**\n\t * Updates the value of this atom using the given updater function. If the returned value is the same as the current value, this is a no-op.\n\t *\n\t * @param updater - A function that takes the current value and returns the new value.\n\t */\n\tupdate(updater: (value: Value) => Value): Value\n}\n\n/**\n * @internal\n */\nclass __Atom__<Value, Diff = unknown> implements Atom<Value, Diff> {\n\tconstructor(\n\t\tpublic readonly name: string,\n\t\tprivate current: Value,\n\t\toptions?: AtomOptions<Value, Diff>\n\t) {\n\t\tthis.isEqual = options?.isEqual ?? null\n\n\t\tif (!options) return\n\n\t\tif (options.historyLength) {\n\t\t\tthis.historyBuffer = new HistoryBuffer(options.historyLength)\n\t\t}\n\n\t\tthis.computeDiff = options.computeDiff\n\t}\n\n\treadonly isEqual: null | ((a: any, b: any) => boolean)\n\n\tcomputeDiff?: ComputeDiff<Value, Diff>\n\n\tlastChangedEpoch = getGlobalEpoch()\n\n\tchildren = new ArraySet<Child>()\n\n\thistoryBuffer?: HistoryBuffer<Diff>\n\n\t__unsafe__getWithoutCapture(_ignoreErrors?: boolean): Value {\n\t\treturn this.current\n\t}\n\n\tget() {\n\t\tmaybeCaptureParent(this)\n\t\treturn this.current\n\t}\n\n\tset(value: Value, diff?: Diff): Value {\n\t\t// If the value has not changed, do nothing.\n\t\tif (this.isEqual?.(this.current, value) ?? equals(this.current, value)) {\n\t\t\treturn this.current\n\t\t}\n\n\t\t// Tick forward the global epoch\n\t\tadvanceGlobalEpoch()\n\n\t\t// Add the diff to the history buffer.\n\t\tif (this.historyBuffer) {\n\t\t\tthis.historyBuffer.pushEntry(\n\t\t\t\tthis.lastChangedEpoch,\n\t\t\t\tgetGlobalEpoch(),\n\t\t\t\tdiff ??\n\t\t\t\t\tthis.computeDiff?.(this.current, value, this.lastChangedEpoch, getGlobalEpoch()) ??\n\t\t\t\t\tRESET_VALUE\n\t\t\t)\n\t\t}\n\n\t\t// Update the atom's record of the epoch when last changed.\n\t\tthis.lastChangedEpoch = getGlobalEpoch()\n\n\t\tconst oldValue = this.current\n\t\tthis.current = value\n\n\t\t// Notify all children that this atom has changed.\n\t\tatomDidChange(this as any, oldValue)\n\n\t\treturn value\n\t}\n\n\tupdate(updater: (value: Value) => Value): Value {\n\t\treturn this.set(updater(this.current))\n\t}\n\n\tgetDiffSince(epoch: number): RESET_VALUE | Diff[] {\n\t\tmaybeCaptureParent(this)\n\n\t\t// If no changes have occurred since the given epoch, return an empty array.\n\t\tif (epoch >= this.lastChangedEpoch) {\n\t\t\treturn EMPTY_ARRAY\n\t\t}\n\n\t\treturn this.historyBuffer?.getChangesSince(epoch) ?? RESET_VALUE\n\t}\n}\n\nexport const _Atom = singleton('Atom', () => __Atom__)\nexport type _Atom = InstanceType<typeof _Atom>\n\n/**\n * Creates a new {@link Atom}.\n *\n * An Atom is a signal that can be updated directly by calling {@link Atom.set} or {@link Atom.update}.\n *\n * @example\n * ```ts\n * const name = atom('name', 'John')\n *\n * name.get() // 'John'\n *\n * name.set('Jane')\n *\n * name.get() // 'Jane'\n * ```\n *\n * @public\n */\nexport function atom<Value, Diff = unknown>(\n\t/**\n\t * A name for the signal. This is used for debugging and profiling purposes, it does not need to be unique.\n\t */\n\tname: string,\n\t/**\n\t * The initial value of the signal.\n\t */\n\tinitialValue: Value,\n\t/**\n\t * The options to configure the atom. See {@link AtomOptions}.\n\t */\n\toptions?: AtomOptions<Value, Diff>\n): Atom<Value, Diff> {\n\treturn new _Atom(name, initialValue, options)\n}\n\n/**\n * Returns true if the given value is an {@link Atom}.\n * @public\n */\nexport function isAtom(value: unknown): value is Atom<unknown> {\n\treturn value instanceof _Atom\n}\n"],
|
|
5
5
|
"mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,qBAAqB;AAC9B,SAAS,0BAA0B;AACnC,SAAS,aAAa,QAAQ,iBAAiB;AAC/C,SAAS,oBAAoB,eAAe,sBAAsB;AAClE,SAA6B,mBAA2B;AAiExD,MAAM,SAA6D;AAAA,EAClE,YACiB,MACR,SACR,SACC;AAHe;AACR;AAGR,SAAK,UAAU,SAAS,WAAW;AAEnC,QAAI,CAAC,QAAS;AAEd,QAAI,QAAQ,eAAe;AAC1B,WAAK,gBAAgB,IAAI,cAAc,QAAQ,aAAa;AAAA,IAC7D;AAEA,SAAK,cAAc,QAAQ;AAAA,EAC5B;AAAA,EAES;AAAA,EAET;AAAA,EAEA,mBAAmB,eAAe;AAAA,EAElC,WAAW,IAAI,SAAgB;AAAA,EAE/B;AAAA,EAEA,4BAA4B,eAAgC;AAC3D,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,MAAM;AACL,uBAAmB,IAAI;AACvB,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,IAAI,OAAc,MAAoB;AAErC,QAAI,KAAK,UAAU,KAAK,SAAS,KAAK,KAAK,OAAO,KAAK,SAAS,KAAK,GAAG;AACvE,aAAO,KAAK;AAAA,IACb;AAGA,uBAAmB;AAGnB,QAAI,KAAK,eAAe;AACvB,WAAK,cAAc;AAAA,QAClB,KAAK;AAAA,QACL,eAAe;AAAA,QACf,QACC,KAAK,cAAc,KAAK,SAAS,OAAO,KAAK,kBAAkB,eAAe,CAAC,KAC/E;AAAA,MACF;AAAA,IACD;AAGA,SAAK,mBAAmB,eAAe;AAEvC,UAAM,WAAW,KAAK;AACtB,SAAK,UAAU;AAGf,kBAAc,MAAa,QAAQ;AAEnC,WAAO;AAAA,EACR;AAAA,EAEA,OAAO,SAAyC;AAC/C,WAAO,KAAK,IAAI,QAAQ,KAAK,OAAO,CAAC;AAAA,EACtC;AAAA,EAEA,aAAa,OAAqC;AACjD,uBAAmB,IAAI;AAGvB,QAAI,SAAS,KAAK,kBAAkB;AACnC,aAAO;AAAA,IACR;AAEA,WAAO,KAAK,eAAe,gBAAgB,KAAK,KAAK;AAAA,EACtD;AACD;AAEO,MAAM,QAAQ,UAAU,QAAQ,MAAM,QAAQ;AAqB9C,SAAS,KAIf,MAIA,cAIA,SACoB;AACpB,SAAO,IAAI,MAAM,MAAM,cAAc,OAAO;AAC7C;AAMO,SAAS,OAAO,OAAwC;AAC9D,SAAO,iBAAiB;AACzB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/Computed.ts"],
|
|
4
|
-
"sourcesContent": ["/* eslint-disable prefer-rest-params */\nimport { assert } from '@tldraw/utils'\nimport { ArraySet } from './ArraySet'\nimport { HistoryBuffer } from './HistoryBuffer'\nimport { maybeCaptureParent, startCapturingParents, stopCapturingParents } from './capture'\nimport { GLOBAL_START_EPOCH } from './constants'\nimport { EMPTY_ARRAY, equals, haveParentsChanged, singleton } from './helpers'\nimport { getGlobalEpoch, getIsReacting, getReactionEpoch } from './transactions'\nimport { Child, ComputeDiff, RESET_VALUE, Signal } from './types'\nimport { logComputedGetterWarning } from './warnings'\n\n/**\n * @public\n */\nexport const UNINITIALIZED = Symbol.for('com.tldraw.state/UNINITIALIZED')\n/**\n * The type of the first value passed to a computed signal function as the 'prevValue' parameter.\n *\n * @see {@link isUninitialized}.\n * @public\n */\nexport type UNINITIALIZED = typeof UNINITIALIZED\n\n/**\n * Call this inside a computed signal function to determine whether it is the first time the function is being called.\n *\n * Mainly useful for incremental signal computation.\n *\n * @example\n * ```ts\n * const count = atom('count', 0)\n * const double = computed('double', (prevValue) => {\n * if (isUninitialized(prevValue)) {\n * print('First time!')\n * }\n * return count.get() * 2\n * })\n * ```\n *\n * @param value - The value to check.\n * @public\n */\nexport function isUninitialized(value: any): value is UNINITIALIZED {\n\treturn value === UNINITIALIZED\n}\n\n/** @public */\nexport const WithDiff = singleton(\n\t'WithDiff',\n\t() =>\n\t\tclass WithDiff<Value, Diff> {\n\t\t\tconstructor(\n\t\t\t\tpublic value: Value,\n\t\t\t\tpublic diff: Diff\n\t\t\t) {}\n\t\t}\n)\n\n/** @public */\nexport interface WithDiff<Value, Diff> {\n\tvalue: Value\n\tdiff: Diff\n}\n\n/**\n * When writing incrementally-computed signals it is convenient (and usually more performant) to incrementally compute the diff too.\n *\n * You can use this function to wrap the return value of a computed signal function to indicate that the diff should be used instead of calculating a new one with {@link AtomOptions.computeDiff}.\n *\n * @example\n * ```ts\n * const count = atom('count', 0)\n * const double = computed('double', (prevValue) => {\n * const nextValue = count.get() * 2\n * if (isUninitialized(prevValue)) {\n * return nextValue\n * }\n * return withDiff(nextValue, nextValue - prevValue)\n * }, { historyLength: 10 })\n * ```\n *\n *\n * @param value - The value.\n * @param diff - The diff.\n * @public\n */\nexport function withDiff<Value, Diff>(value: Value, diff: Diff): WithDiff<Value, Diff> {\n\treturn new WithDiff(value, diff)\n}\n\n/**\n * Options for creating computed signals. Used when calling `computed`.\n * @public\n */\nexport interface ComputedOptions<Value, Diff> {\n\t/**\n\t * The maximum number of diffs to keep in the history buffer.\n\t *\n\t * If you don't need to compute diffs, or if you will supply diffs manually via {@link Atom.set}, you can leave this as `undefined` and no history buffer will be created.\n\t *\n\t * If you expect the value to be part of an active effect subscription all the time, and to not change multiple times inside of a single transaction, you can set this to a relatively low number (e.g. 10).\n\t *\n\t * Otherwise, set this to a higher number based on your usage pattern and memory constraints.\n\t *\n\t */\n\thistoryLength?: number\n\t/**\n\t * A method used to compute a diff between the atom's old and new values. If provided, it will not be used unless you also specify {@link AtomOptions.historyLength}.\n\t */\n\tcomputeDiff?: ComputeDiff<Value, Diff>\n\t/**\n\t * If provided, this will be used to compare the old and new values of the atom to determine if the value has changed.\n\t * By default, values are compared using first using strict equality (`===`), then `Object.is`, and finally any `.equals` method present in the object's prototype chain.\n\t * @param a - The old value\n\t * @param b - The new value\n\t * @returns\n\t */\n\tisEqual?(a: any, b: any): boolean\n}\n\n/**\n * A computed signal created via `computed`.\n *\n * @public\n */\nexport interface Computed<Value, Diff = unknown> extends Signal<Value, Diff> {\n\t/**\n\t * Whether this computed child is involved in an actively-running effect graph.\n\t * @public\n\t */\n\treadonly isActivelyListening: boolean\n\n\t/** @internal */\n\treadonly parentSet: ArraySet<Signal<any, any>>\n\t/** @internal */\n\treadonly parents: Signal<any, any>[]\n\t/** @internal */\n\treadonly parentEpochs: number[]\n}\n\n/**\n * @internal\n */\nclass __UNSAFE__Computed<Value, Diff = unknown> implements Computed<Value, Diff> {\n\tlastChangedEpoch = GLOBAL_START_EPOCH\n\tlastTraversedEpoch = GLOBAL_START_EPOCH\n\n\t__debug_ancestor_epochs__: Map<Signal<any, any>, number> | null = null\n\n\t/**\n\t * The epoch when the reactor was last checked.\n\t */\n\tprivate lastCheckedEpoch = GLOBAL_START_EPOCH\n\n\tparentSet = new ArraySet<Signal<any, any>>()\n\tparents: Signal<any, any>[] = []\n\tparentEpochs: number[] = []\n\n\tchildren = new ArraySet<Child>()\n\n\t// eslint-disable-next-line no-restricted-syntax\n\tget isActivelyListening(): boolean {\n\t\treturn !this.children.isEmpty\n\t}\n\n\thistoryBuffer?: HistoryBuffer<Diff>\n\n\t// The last-computed value of this signal.\n\tprivate state: Value = UNINITIALIZED as unknown as Value\n\t// If the signal throws an error we stash it so we can rethrow it on the next get()\n\tprivate error: null | { thrownValue: any } = null\n\n\tprivate computeDiff?: ComputeDiff<Value, Diff>\n\n\tprivate readonly isEqual: (a: any, b: any) => boolean\n\n\tconstructor(\n\t\t/**\n\t\t * The name of the signal. This is used for debugging and performance profiling purposes. It does not need to be globally unique.\n\t\t */\n\t\tpublic readonly name: string,\n\t\t/**\n\t\t * The function that computes the value of the signal.\n\t\t */\n\t\tprivate readonly derive: (\n\t\t\tpreviousValue: Value | UNINITIALIZED,\n\t\t\tlastComputedEpoch: number\n\t\t) => Value | WithDiff<Value, Diff>,\n\t\toptions?: ComputedOptions<Value, Diff>\n\t) {\n\t\tif (options?.historyLength) {\n\t\t\tthis.historyBuffer = new HistoryBuffer(options.historyLength)\n\t\t}\n\t\tthis.computeDiff = options?.computeDiff\n\t\tthis.isEqual = options?.isEqual ?? equals\n\t}\n\n\t__unsafe__getWithoutCapture(ignoreErrors?: boolean): Value {\n\t\tconst isNew = this.lastChangedEpoch === GLOBAL_START_EPOCH\n\n\t\tconst globalEpoch = getGlobalEpoch()\n\n\t\tif (\n\t\t\t!isNew &&\n\t\t\t(this.lastCheckedEpoch === globalEpoch ||\n\t\t\t\t(this.isActivelyListening &&\n\t\t\t\t\tgetIsReacting() &&\n\t\t\t\t\tthis.lastTraversedEpoch < getReactionEpoch()) ||\n\t\t\t\t!haveParentsChanged(this))\n\t\t) {\n\t\t\tthis.lastCheckedEpoch = globalEpoch\n\t\t\tif (this.error) {\n\t\t\t\tif (!ignoreErrors) {\n\t\t\t\t\tthrow this.error.thrownValue\n\t\t\t\t} else {\n\t\t\t\t\treturn this.state // will be UNINITIALIZED\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn this.state\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tstartCapturingParents(this)\n\t\t\tconst result = this.derive(this.state, this.lastCheckedEpoch)\n\t\t\tconst newState = result instanceof WithDiff ? result.value : result\n\t\t\tconst isUninitialized = this.state === UNINITIALIZED\n\t\t\tif (isUninitialized || !this.isEqual(newState, this.state)) {\n\t\t\t\tif (this.historyBuffer && !isUninitialized) {\n\t\t\t\t\tconst diff = result instanceof WithDiff ? result.diff : undefined\n\t\t\t\t\tthis.historyBuffer.pushEntry(\n\t\t\t\t\t\tthis.lastChangedEpoch,\n\t\t\t\t\t\tgetGlobalEpoch(),\n\t\t\t\t\t\tdiff ??\n\t\t\t\t\t\t\tthis.computeDiff?.(this.state, newState, this.lastCheckedEpoch, getGlobalEpoch()) ??\n\t\t\t\t\t\t\tRESET_VALUE\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tthis.lastChangedEpoch = getGlobalEpoch()\n\t\t\t\tthis.state = newState\n\t\t\t}\n\t\t\tthis.error = null\n\t\t\tthis.lastCheckedEpoch = getGlobalEpoch()\n\n\t\t\treturn this.state\n\t\t} catch (e) {\n\t\t\t// if a derived value throws an error, we reset the state to UNINITIALIZED\n\t\t\tif (this.state !== UNINITIALIZED) {\n\t\t\t\tthis.state = UNINITIALIZED as unknown as Value\n\t\t\t\tthis.lastChangedEpoch = getGlobalEpoch()\n\t\t\t}\n\t\t\tthis.lastCheckedEpoch = getGlobalEpoch()\n\t\t\t// we also clear the history buffer if an error was thrown\n\t\t\tif (this.historyBuffer) {\n\t\t\t\tthis.historyBuffer.clear()\n\t\t\t}\n\t\t\tthis.error = { thrownValue: e }\n\t\t\t// we don't wish to propagate errors when derefed via haveParentsChanged()\n\t\t\tif (!ignoreErrors) throw e\n\t\t\treturn this.state\n\t\t} finally {\n\t\t\tstopCapturingParents()\n\t\t}\n\t}\n\n\tget(): Value {\n\t\ttry {\n\t\t\treturn this.__unsafe__getWithoutCapture()\n\t\t} finally {\n\t\t\t// if the deriver throws an error we still need to capture\n\t\t\tmaybeCaptureParent(this)\n\t\t}\n\t}\n\n\tgetDiffSince(epoch: number): RESET_VALUE | Diff[] {\n\t\t// we can ignore any errors thrown during derive\n\t\tthis.__unsafe__getWithoutCapture(true)\n\t\t// and we still need to capture this signal as a parent\n\t\tmaybeCaptureParent(this)\n\n\t\tif (epoch >= this.lastChangedEpoch) {\n\t\t\treturn EMPTY_ARRAY\n\t\t}\n\n\t\treturn this.historyBuffer?.getChangesSince(epoch) ?? RESET_VALUE\n\t}\n}\n\nexport const _Computed = singleton('Computed', () => __UNSAFE__Computed)\nexport type _Computed = InstanceType<typeof __UNSAFE__Computed>\n\nfunction computedMethodLegacyDecorator(\n\toptions: ComputedOptions<any, any> = {},\n\t_target: any,\n\tkey: string,\n\tdescriptor: PropertyDescriptor\n) {\n\tconst originalMethod = descriptor.value\n\tconst derivationKey = Symbol.for('__@tldraw/state__computed__' + key)\n\n\tdescriptor.value = function (this: any) {\n\t\tlet d = this[derivationKey] as Computed<any> | undefined\n\n\t\tif (!d) {\n\t\t\td = new _Computed(key, originalMethod!.bind(this) as any, options)\n\t\t\tObject.defineProperty(this, derivationKey, {\n\t\t\t\tenumerable: false,\n\t\t\t\tconfigurable: false,\n\t\t\t\twritable: false,\n\t\t\t\tvalue: d,\n\t\t\t})\n\t\t}\n\t\treturn d.get()\n\t}\n\tdescriptor.value[isComputedMethodKey] = true\n\n\treturn descriptor\n}\n\nfunction computedGetterLegacyDecorator(\n\toptions: ComputedOptions<any, any> = {},\n\t_target: any,\n\tkey: string,\n\tdescriptor: PropertyDescriptor\n) {\n\tconst originalMethod = descriptor.get\n\tconst derivationKey = Symbol.for('__@tldraw/state__computed__' + key)\n\n\tdescriptor.get = function (this: any) {\n\t\tlet d = this[derivationKey] as Computed<any> | undefined\n\n\t\tif (!d) {\n\t\t\td = new _Computed(key, originalMethod!.bind(this) as any, options)\n\t\t\tObject.defineProperty(this, derivationKey, {\n\t\t\t\tenumerable: false,\n\t\t\t\tconfigurable: false,\n\t\t\t\twritable: false,\n\t\t\t\tvalue: d,\n\t\t\t})\n\t\t}\n\t\treturn d.get()\n\t}\n\n\treturn descriptor\n}\n\nfunction computedMethodTc39Decorator<This extends object, Value>(\n\toptions: ComputedOptions<Value, any>,\n\tcompute: () => Value,\n\tcontext: ClassMethodDecoratorContext<This, () => Value>\n) {\n\tassert(context.kind === 'method', '@computed can only be used on methods')\n\tconst derivationKey = Symbol.for('__@tldraw/state__computed__' + String(context.name))\n\n\tconst fn = function (this: any) {\n\t\tlet d = this[derivationKey] as Computed<any> | undefined\n\n\t\tif (!d) {\n\t\t\td = new _Computed(String(context.name), compute.bind(this) as any, options)\n\t\t\tObject.defineProperty(this, derivationKey, {\n\t\t\t\tenumerable: false,\n\t\t\t\tconfigurable: false,\n\t\t\t\twritable: false,\n\t\t\t\tvalue: d,\n\t\t\t})\n\t\t}\n\t\treturn d.get()\n\t}\n\tfn[isComputedMethodKey] = true\n\treturn fn\n}\n\nfunction computedDecorator(\n\toptions: ComputedOptions<any, any> = {},\n\targs:\n\t\t| [target: any, key: string, descriptor: PropertyDescriptor]\n\t\t| [originalMethod: () => any, context: ClassMethodDecoratorContext]\n) {\n\tif (args.length === 2) {\n\t\tconst [originalMethod, context] = args\n\t\treturn computedMethodTc39Decorator(options, originalMethod, context)\n\t} else {\n\t\tconst [_target, key, descriptor] = args\n\t\tif (descriptor.get) {\n\t\t\tlogComputedGetterWarning()\n\t\t\treturn computedGetterLegacyDecorator(options, _target, key, descriptor)\n\t\t} else {\n\t\t\treturn computedMethodLegacyDecorator(options, _target, key, descriptor)\n\t\t}\n\t}\n}\n\nconst isComputedMethodKey = '@@__isComputedMethod__@@'\n\n/**\n * Retrieves the underlying computed instance for a given property created with the `computed`\n * decorator.\n *\n * @example\n * ```ts\n * class Counter {\n * max = 100\n * count = atom(0)\n *\n * @computed getRemaining() {\n * return this.max - this.count.get()\n * }\n * }\n *\n * const c = new Counter()\n * const remaining = getComputedInstance(c, 'getRemaining')\n * remaining.get() === 100 // true\n * c.count.set(13)\n * remaining.get() === 87 // true\n * ```\n *\n * @param obj - The object\n * @param propertyName - The property name\n * @public\n */\nexport function getComputedInstance<Obj extends object, Prop extends keyof Obj>(\n\tobj: Obj,\n\tpropertyName: Prop\n): Computed<Obj[Prop]> {\n\tconst key = Symbol.for('__@tldraw/state__computed__' + propertyName.toString())\n\tlet inst = obj[key as keyof typeof obj] as Computed<Obj[Prop]> | undefined\n\tif (!inst) {\n\t\t// deref to make sure it exists first\n\t\tconst val = obj[propertyName]\n\t\tif (typeof val === 'function' && (val as any)[isComputedMethodKey]) {\n\t\t\tval.call(obj)\n\t\t}\n\n\t\tinst = obj[key as keyof typeof obj] as Computed<Obj[Prop]> | undefined\n\t}\n\treturn inst as any\n}\n\n/**\n * Creates a computed signal.\n *\n * @example\n * ```ts\n * const name = atom('name', 'John')\n * const greeting = computed('greeting', () => `Hello ${name.get()}!`)\n * console.log(greeting.get()) // 'Hello John!'\n * ```\n *\n * `computed` may also be used as a decorator for creating computed getter methods.\n *\n * @example\n * ```ts\n * class Counter {\n * max = 100\n * count = atom<number>(0)\n *\n * @computed getRemaining() {\n * return this.max - this.count.get()\n * }\n * }\n * ```\n *\n * You may optionally pass in a {@link ComputedOptions} when used as a decorator:\n *\n * @example\n * ```ts\n * class Counter {\n * max = 100\n * count = atom<number>(0)\n *\n * @computed({isEqual: (a, b) => a === b})\n * getRemaining() {\n * return this.max - this.count.get()\n * }\n * }\n * ```\n *\n * @param name - The name of the signal.\n * @param compute - The function that computes the value of the signal.\n * @param options - Options for the signal.\n *\n * @public\n */\nexport function computed<Value, Diff = unknown>(\n\tname: string,\n\tcompute: (\n\t\tpreviousValue: Value | typeof UNINITIALIZED,\n\t\tlastComputedEpoch: number\n\t) => Value | WithDiff<Value, Diff>,\n\toptions?: ComputedOptions<Value, Diff>\n): Computed<Value, Diff>\n/**\n * `@computed` decorator (TC39 decorators).\n * @public\n */\nexport function computed<This extends object, Value>(\n\tcompute: () => Value,\n\tcontext: ClassMethodDecoratorContext<This, () => Value>\n): () => Value\n/**\n * `@computed` decorator (legacy typescript decorator syntax).\n *\n * @public */\nexport function computed(\n\ttarget: any,\n\tkey: string,\n\tdescriptor: PropertyDescriptor\n): PropertyDescriptor\n/**\n * `@computed` decorator with options.\n * @public\n */\nexport function computed<Value, Diff = unknown>(\n\toptions?: ComputedOptions<Value, Diff>\n): ((target: any, key: string, descriptor: PropertyDescriptor) => PropertyDescriptor) &\n\t(<This>(\n\t\tcompute: () => Value,\n\t\tcontext: ClassMethodDecoratorContext<This, () => Value>\n\t) => () => Value)\n\n/** @public */\nexport function computed() {\n\tif (arguments.length === 1) {\n\t\tconst options = arguments[0]\n\t\treturn (...args: any) => computedDecorator(options, args)\n\t} else if (typeof arguments[0] === 'string') {\n\t\treturn new _Computed(arguments[0], arguments[1], arguments[2])\n\t} else {\n\t\treturn computedDecorator(undefined, arguments as any)\n\t}\n}\n\n/**\n * Returns true if the given value is a computed signal.\n *\n * @param value\n * @returns {value is Computed<any>}\n * @public\n */\nexport function isComputed(value: any): value is Computed<any> {\n\treturn value && value instanceof _Computed\n}\n"],
|
|
4
|
+
"sourcesContent": ["/* eslint-disable prefer-rest-params */\nimport { assert } from '@tldraw/utils'\nimport { ArraySet } from './ArraySet'\nimport { HistoryBuffer } from './HistoryBuffer'\nimport { maybeCaptureParent, startCapturingParents, stopCapturingParents } from './capture'\nimport { GLOBAL_START_EPOCH } from './constants'\nimport { EMPTY_ARRAY, equals, haveParentsChanged, singleton } from './helpers'\nimport { getGlobalEpoch, getIsReacting, getReactionEpoch } from './transactions'\nimport { Child, ComputeDiff, RESET_VALUE, Signal } from './types'\nimport { logComputedGetterWarning } from './warnings'\n\n/**\n * @public\n */\nexport const UNINITIALIZED = Symbol.for('com.tldraw.state/UNINITIALIZED')\n/**\n * The type of the first value passed to a computed signal function as the 'prevValue' parameter.\n *\n * @see {@link isUninitialized}.\n * @public\n */\nexport type UNINITIALIZED = typeof UNINITIALIZED\n\n/**\n * Call this inside a computed signal function to determine whether it is the first time the function is being called.\n *\n * Mainly useful for incremental signal computation.\n *\n * @example\n * ```ts\n * const count = atom('count', 0)\n * const double = computed('double', (prevValue) => {\n * if (isUninitialized(prevValue)) {\n * print('First time!')\n * }\n * return count.get() * 2\n * })\n * ```\n *\n * @param value - The value to check.\n * @public\n */\nexport function isUninitialized(value: any): value is UNINITIALIZED {\n\treturn value === UNINITIALIZED\n}\n\n/** @public */\nexport const WithDiff = singleton(\n\t'WithDiff',\n\t() =>\n\t\tclass WithDiff<Value, Diff> {\n\t\t\tconstructor(\n\t\t\t\tpublic value: Value,\n\t\t\t\tpublic diff: Diff\n\t\t\t) {}\n\t\t}\n)\n\n/** @public */\nexport interface WithDiff<Value, Diff> {\n\tvalue: Value\n\tdiff: Diff\n}\n\n/**\n * When writing incrementally-computed signals it is convenient (and usually more performant) to incrementally compute the diff too.\n *\n * You can use this function to wrap the return value of a computed signal function to indicate that the diff should be used instead of calculating a new one with {@link AtomOptions.computeDiff}.\n *\n * @example\n * ```ts\n * const count = atom('count', 0)\n * const double = computed('double', (prevValue) => {\n * const nextValue = count.get() * 2\n * if (isUninitialized(prevValue)) {\n * return nextValue\n * }\n * return withDiff(nextValue, nextValue - prevValue)\n * }, { historyLength: 10 })\n * ```\n *\n *\n * @param value - The value.\n * @param diff - The diff.\n * @public\n */\nexport function withDiff<Value, Diff>(value: Value, diff: Diff): WithDiff<Value, Diff> {\n\treturn new WithDiff(value, diff)\n}\n\n/**\n * Options for creating computed signals. Used when calling `computed`.\n * @public\n */\nexport interface ComputedOptions<Value, Diff> {\n\t/**\n\t * The maximum number of diffs to keep in the history buffer.\n\t *\n\t * If you don't need to compute diffs, or if you will supply diffs manually via {@link Atom.set}, you can leave this as `undefined` and no history buffer will be created.\n\t *\n\t * If you expect the value to be part of an active effect subscription all the time, and to not change multiple times inside of a single transaction, you can set this to a relatively low number (e.g. 10).\n\t *\n\t * Otherwise, set this to a higher number based on your usage pattern and memory constraints.\n\t *\n\t */\n\thistoryLength?: number\n\t/**\n\t * A method used to compute a diff between the computed's old and new values. If provided, it will not be used unless you also specify {@link ComputedOptions.historyLength}.\n\t */\n\tcomputeDiff?: ComputeDiff<Value, Diff>\n\t/**\n\t * If provided, this will be used to compare the old and new values of the computed to determine if the value has changed.\n\t * By default, values are compared using first using strict equality (`===`), then `Object.is`, and finally any `.equals` method present in the object's prototype chain.\n\t * @param a - The old value\n\t * @param b - The new value\n\t * @returns True if the values are equal, false otherwise.\n\t */\n\tisEqual?(a: any, b: any): boolean\n}\n\n/**\n * A computed signal created via `computed`.\n *\n * @public\n */\nexport interface Computed<Value, Diff = unknown> extends Signal<Value, Diff> {\n\t/**\n\t * Whether this computed child is involved in an actively-running effect graph.\n\t * @public\n\t */\n\treadonly isActivelyListening: boolean\n\n\t/** @internal */\n\treadonly parentSet: ArraySet<Signal<any, any>>\n\t/** @internal */\n\treadonly parents: Signal<any, any>[]\n\t/** @internal */\n\treadonly parentEpochs: number[]\n}\n\n/**\n * @internal\n */\nclass __UNSAFE__Computed<Value, Diff = unknown> implements Computed<Value, Diff> {\n\tlastChangedEpoch = GLOBAL_START_EPOCH\n\tlastTraversedEpoch = GLOBAL_START_EPOCH\n\n\t__debug_ancestor_epochs__: Map<Signal<any, any>, number> | null = null\n\n\t/**\n\t * The epoch when the reactor was last checked.\n\t */\n\tprivate lastCheckedEpoch = GLOBAL_START_EPOCH\n\n\tparentSet = new ArraySet<Signal<any, any>>()\n\tparents: Signal<any, any>[] = []\n\tparentEpochs: number[] = []\n\n\tchildren = new ArraySet<Child>()\n\n\t// eslint-disable-next-line no-restricted-syntax\n\tget isActivelyListening(): boolean {\n\t\treturn !this.children.isEmpty\n\t}\n\n\thistoryBuffer?: HistoryBuffer<Diff>\n\n\t// The last-computed value of this signal.\n\tprivate state: Value = UNINITIALIZED as unknown as Value\n\t// If the signal throws an error we stash it so we can rethrow it on the next get()\n\tprivate error: null | { thrownValue: any } = null\n\n\tprivate computeDiff?: ComputeDiff<Value, Diff>\n\n\tprivate readonly isEqual: (a: any, b: any) => boolean\n\n\tconstructor(\n\t\t/**\n\t\t * The name of the signal. This is used for debugging and performance profiling purposes. It does not need to be globally unique.\n\t\t */\n\t\tpublic readonly name: string,\n\t\t/**\n\t\t * The function that computes the value of the signal.\n\t\t */\n\t\tprivate readonly derive: (\n\t\t\tpreviousValue: Value | UNINITIALIZED,\n\t\t\tlastComputedEpoch: number\n\t\t) => Value | WithDiff<Value, Diff>,\n\t\toptions?: ComputedOptions<Value, Diff>\n\t) {\n\t\tif (options?.historyLength) {\n\t\t\tthis.historyBuffer = new HistoryBuffer(options.historyLength)\n\t\t}\n\t\tthis.computeDiff = options?.computeDiff\n\t\tthis.isEqual = options?.isEqual ?? equals\n\t}\n\n\t__unsafe__getWithoutCapture(ignoreErrors?: boolean): Value {\n\t\tconst isNew = this.lastChangedEpoch === GLOBAL_START_EPOCH\n\n\t\tconst globalEpoch = getGlobalEpoch()\n\n\t\tif (\n\t\t\t!isNew &&\n\t\t\t(this.lastCheckedEpoch === globalEpoch ||\n\t\t\t\t(this.isActivelyListening &&\n\t\t\t\t\tgetIsReacting() &&\n\t\t\t\t\tthis.lastTraversedEpoch < getReactionEpoch()) ||\n\t\t\t\t!haveParentsChanged(this))\n\t\t) {\n\t\t\tthis.lastCheckedEpoch = globalEpoch\n\t\t\tif (this.error) {\n\t\t\t\tif (!ignoreErrors) {\n\t\t\t\t\tthrow this.error.thrownValue\n\t\t\t\t} else {\n\t\t\t\t\treturn this.state // will be UNINITIALIZED\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn this.state\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tstartCapturingParents(this)\n\t\t\tconst result = this.derive(this.state, this.lastCheckedEpoch)\n\t\t\tconst newState = result instanceof WithDiff ? result.value : result\n\t\t\tconst isUninitialized = this.state === UNINITIALIZED\n\t\t\tif (isUninitialized || !this.isEqual(newState, this.state)) {\n\t\t\t\tif (this.historyBuffer && !isUninitialized) {\n\t\t\t\t\tconst diff = result instanceof WithDiff ? result.diff : undefined\n\t\t\t\t\tthis.historyBuffer.pushEntry(\n\t\t\t\t\t\tthis.lastChangedEpoch,\n\t\t\t\t\t\tgetGlobalEpoch(),\n\t\t\t\t\t\tdiff ??\n\t\t\t\t\t\t\tthis.computeDiff?.(this.state, newState, this.lastCheckedEpoch, getGlobalEpoch()) ??\n\t\t\t\t\t\t\tRESET_VALUE\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tthis.lastChangedEpoch = getGlobalEpoch()\n\t\t\t\tthis.state = newState\n\t\t\t}\n\t\t\tthis.error = null\n\t\t\tthis.lastCheckedEpoch = getGlobalEpoch()\n\n\t\t\treturn this.state\n\t\t} catch (e) {\n\t\t\t// if a derived value throws an error, we reset the state to UNINITIALIZED\n\t\t\tif (this.state !== UNINITIALIZED) {\n\t\t\t\tthis.state = UNINITIALIZED as unknown as Value\n\t\t\t\tthis.lastChangedEpoch = getGlobalEpoch()\n\t\t\t}\n\t\t\tthis.lastCheckedEpoch = getGlobalEpoch()\n\t\t\t// we also clear the history buffer if an error was thrown\n\t\t\tif (this.historyBuffer) {\n\t\t\t\tthis.historyBuffer.clear()\n\t\t\t}\n\t\t\tthis.error = { thrownValue: e }\n\t\t\t// we don't wish to propagate errors when derefed via haveParentsChanged()\n\t\t\tif (!ignoreErrors) throw e\n\t\t\treturn this.state\n\t\t} finally {\n\t\t\tstopCapturingParents()\n\t\t}\n\t}\n\n\tget(): Value {\n\t\ttry {\n\t\t\treturn this.__unsafe__getWithoutCapture()\n\t\t} finally {\n\t\t\t// if the deriver throws an error we still need to capture\n\t\t\tmaybeCaptureParent(this)\n\t\t}\n\t}\n\n\tgetDiffSince(epoch: number): RESET_VALUE | Diff[] {\n\t\t// we can ignore any errors thrown during derive\n\t\tthis.__unsafe__getWithoutCapture(true)\n\t\t// and we still need to capture this signal as a parent\n\t\tmaybeCaptureParent(this)\n\n\t\tif (epoch >= this.lastChangedEpoch) {\n\t\t\treturn EMPTY_ARRAY\n\t\t}\n\n\t\treturn this.historyBuffer?.getChangesSince(epoch) ?? RESET_VALUE\n\t}\n}\n\nexport const _Computed = singleton('Computed', () => __UNSAFE__Computed)\nexport type _Computed = InstanceType<typeof __UNSAFE__Computed>\n\nfunction computedMethodLegacyDecorator(\n\toptions: ComputedOptions<any, any> = {},\n\t_target: any,\n\tkey: string,\n\tdescriptor: PropertyDescriptor\n) {\n\tconst originalMethod = descriptor.value\n\tconst derivationKey = Symbol.for('__@tldraw/state__computed__' + key)\n\n\tdescriptor.value = function (this: any) {\n\t\tlet d = this[derivationKey] as Computed<any> | undefined\n\n\t\tif (!d) {\n\t\t\td = new _Computed(key, originalMethod!.bind(this) as any, options)\n\t\t\tObject.defineProperty(this, derivationKey, {\n\t\t\t\tenumerable: false,\n\t\t\t\tconfigurable: false,\n\t\t\t\twritable: false,\n\t\t\t\tvalue: d,\n\t\t\t})\n\t\t}\n\t\treturn d.get()\n\t}\n\tdescriptor.value[isComputedMethodKey] = true\n\n\treturn descriptor\n}\n\nfunction computedGetterLegacyDecorator(\n\toptions: ComputedOptions<any, any> = {},\n\t_target: any,\n\tkey: string,\n\tdescriptor: PropertyDescriptor\n) {\n\tconst originalMethod = descriptor.get\n\tconst derivationKey = Symbol.for('__@tldraw/state__computed__' + key)\n\n\tdescriptor.get = function (this: any) {\n\t\tlet d = this[derivationKey] as Computed<any> | undefined\n\n\t\tif (!d) {\n\t\t\td = new _Computed(key, originalMethod!.bind(this) as any, options)\n\t\t\tObject.defineProperty(this, derivationKey, {\n\t\t\t\tenumerable: false,\n\t\t\t\tconfigurable: false,\n\t\t\t\twritable: false,\n\t\t\t\tvalue: d,\n\t\t\t})\n\t\t}\n\t\treturn d.get()\n\t}\n\n\treturn descriptor\n}\n\nfunction computedMethodTc39Decorator<This extends object, Value>(\n\toptions: ComputedOptions<Value, any>,\n\tcompute: () => Value,\n\tcontext: ClassMethodDecoratorContext<This, () => Value>\n) {\n\tassert(context.kind === 'method', '@computed can only be used on methods')\n\tconst derivationKey = Symbol.for('__@tldraw/state__computed__' + String(context.name))\n\n\tconst fn = function (this: any) {\n\t\tlet d = this[derivationKey] as Computed<any> | undefined\n\n\t\tif (!d) {\n\t\t\td = new _Computed(String(context.name), compute.bind(this) as any, options)\n\t\t\tObject.defineProperty(this, derivationKey, {\n\t\t\t\tenumerable: false,\n\t\t\t\tconfigurable: false,\n\t\t\t\twritable: false,\n\t\t\t\tvalue: d,\n\t\t\t})\n\t\t}\n\t\treturn d.get()\n\t}\n\tfn[isComputedMethodKey] = true\n\treturn fn\n}\n\nfunction computedDecorator(\n\toptions: ComputedOptions<any, any> = {},\n\targs:\n\t\t| [target: any, key: string, descriptor: PropertyDescriptor]\n\t\t| [originalMethod: () => any, context: ClassMethodDecoratorContext]\n) {\n\tif (args.length === 2) {\n\t\tconst [originalMethod, context] = args\n\t\treturn computedMethodTc39Decorator(options, originalMethod, context)\n\t} else {\n\t\tconst [_target, key, descriptor] = args\n\t\tif (descriptor.get) {\n\t\t\tlogComputedGetterWarning()\n\t\t\treturn computedGetterLegacyDecorator(options, _target, key, descriptor)\n\t\t} else {\n\t\t\treturn computedMethodLegacyDecorator(options, _target, key, descriptor)\n\t\t}\n\t}\n}\n\nconst isComputedMethodKey = '@@__isComputedMethod__@@'\n\n/**\n * Retrieves the underlying computed instance for a given property created with the `computed`\n * decorator.\n *\n * @example\n * ```ts\n * class Counter {\n * max = 100\n * count = atom(0)\n *\n * @computed getRemaining() {\n * return this.max - this.count.get()\n * }\n * }\n *\n * const c = new Counter()\n * const remaining = getComputedInstance(c, 'getRemaining')\n * remaining.get() === 100 // true\n * c.count.set(13)\n * remaining.get() === 87 // true\n * ```\n *\n * @param obj - The object\n * @param propertyName - The property name\n * @public\n */\nexport function getComputedInstance<Obj extends object, Prop extends keyof Obj>(\n\tobj: Obj,\n\tpropertyName: Prop\n): Computed<Obj[Prop]> {\n\tconst key = Symbol.for('__@tldraw/state__computed__' + propertyName.toString())\n\tlet inst = obj[key as keyof typeof obj] as Computed<Obj[Prop]> | undefined\n\tif (!inst) {\n\t\t// deref to make sure it exists first\n\t\tconst val = obj[propertyName]\n\t\tif (typeof val === 'function' && (val as any)[isComputedMethodKey]) {\n\t\t\tval.call(obj)\n\t\t}\n\n\t\tinst = obj[key as keyof typeof obj] as Computed<Obj[Prop]> | undefined\n\t}\n\treturn inst as any\n}\n\n/**\n * Creates a computed signal.\n *\n * @example\n * ```ts\n * const name = atom('name', 'John')\n * const greeting = computed('greeting', () => `Hello ${name.get()}!`)\n * console.log(greeting.get()) // 'Hello John!'\n * ```\n *\n * `computed` may also be used as a decorator for creating computed getter methods.\n *\n * @example\n * ```ts\n * class Counter {\n * max = 100\n * count = atom<number>(0)\n *\n * @computed getRemaining() {\n * return this.max - this.count.get()\n * }\n * }\n * ```\n *\n * You may optionally pass in a {@link ComputedOptions} when used as a decorator:\n *\n * @example\n * ```ts\n * class Counter {\n * max = 100\n * count = atom<number>(0)\n *\n * @computed({isEqual: (a, b) => a === b})\n * getRemaining() {\n * return this.max - this.count.get()\n * }\n * }\n * ```\n *\n * @param name - The name of the signal.\n * @param compute - The function that computes the value of the signal.\n * @param options - Options for the signal.\n *\n * @public\n */\nexport function computed<Value, Diff = unknown>(\n\tname: string,\n\tcompute: (\n\t\tpreviousValue: Value | typeof UNINITIALIZED,\n\t\tlastComputedEpoch: number\n\t) => Value | WithDiff<Value, Diff>,\n\toptions?: ComputedOptions<Value, Diff>\n): Computed<Value, Diff>\n/**\n * `@computed` decorator (TC39 decorators).\n * @public\n */\nexport function computed<This extends object, Value>(\n\tcompute: () => Value,\n\tcontext: ClassMethodDecoratorContext<This, () => Value>\n): () => Value\n/**\n * `@computed` decorator (legacy typescript decorator syntax).\n *\n * @public */\nexport function computed(\n\ttarget: any,\n\tkey: string,\n\tdescriptor: PropertyDescriptor\n): PropertyDescriptor\n/**\n * `@computed` decorator with options.\n * @public\n */\nexport function computed<Value, Diff = unknown>(\n\toptions?: ComputedOptions<Value, Diff>\n): ((target: any, key: string, descriptor: PropertyDescriptor) => PropertyDescriptor) &\n\t(<This>(\n\t\tcompute: () => Value,\n\t\tcontext: ClassMethodDecoratorContext<This, () => Value>\n\t) => () => Value)\n\n/** @public */\nexport function computed() {\n\tif (arguments.length === 1) {\n\t\tconst options = arguments[0]\n\t\treturn (...args: any) => computedDecorator(options, args)\n\t} else if (typeof arguments[0] === 'string') {\n\t\treturn new _Computed(arguments[0], arguments[1], arguments[2])\n\t} else {\n\t\treturn computedDecorator(undefined, arguments as any)\n\t}\n}\n\n/**\n * Returns true if the given value is a computed signal.\n *\n * @param value\n * @returns {value is Computed<any>}\n * @public\n */\nexport function isComputed(value: any): value is Computed<any> {\n\treturn value && value instanceof _Computed\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["isUninitialized"]
|
|
7
7
|
}
|
|
@@ -9,6 +9,7 @@ class __EffectScheduler__ {
|
|
|
9
9
|
this.runEffect = runEffect;
|
|
10
10
|
this._scheduleEffect = options?.scheduleEffect;
|
|
11
11
|
}
|
|
12
|
+
/** @internal */
|
|
12
13
|
_isActivelyListening = false;
|
|
13
14
|
/**
|
|
14
15
|
* Whether this scheduler is attached and actively listening to its parents.
|
|
@@ -20,8 +21,11 @@ class __EffectScheduler__ {
|
|
|
20
21
|
}
|
|
21
22
|
/** @internal */
|
|
22
23
|
lastTraversedEpoch = GLOBAL_START_EPOCH;
|
|
24
|
+
/** @internal */
|
|
23
25
|
lastReactedEpoch = GLOBAL_START_EPOCH;
|
|
26
|
+
/** @internal */
|
|
24
27
|
_scheduleCount = 0;
|
|
28
|
+
/** @internal */
|
|
25
29
|
__debug_ancestor_epochs__ = null;
|
|
26
30
|
/**
|
|
27
31
|
* The number of times this effect has been scheduled.
|
|
@@ -37,6 +41,7 @@ class __EffectScheduler__ {
|
|
|
37
41
|
parentEpochs = [];
|
|
38
42
|
/** @internal */
|
|
39
43
|
parents = [];
|
|
44
|
+
/** @internal */
|
|
40
45
|
_scheduleEffect;
|
|
41
46
|
/** @internal */
|
|
42
47
|
maybeScheduleEffect() {
|
|
@@ -78,6 +83,7 @@ class __EffectScheduler__ {
|
|
|
78
83
|
/**
|
|
79
84
|
* Makes this scheduler stop 'actively listening' to its parents.
|
|
80
85
|
* It will no longer be eligible to receive 'maybeScheduleEffect' calls until `EffectScheduler.attach` is called again.
|
|
86
|
+
* @public
|
|
81
87
|
*/
|
|
82
88
|
detach() {
|
|
83
89
|
this._isActivelyListening = false;
|
|
@@ -88,6 +94,7 @@ class __EffectScheduler__ {
|
|
|
88
94
|
/**
|
|
89
95
|
* Executes the effect immediately and returns the result.
|
|
90
96
|
* @returns The result of the effect.
|
|
97
|
+
* @public
|
|
91
98
|
*/
|
|
92
99
|
execute() {
|
|
93
100
|
try {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/EffectScheduler.ts"],
|
|
4
|
-
"sourcesContent": ["import { ArraySet } from './ArraySet'\nimport { startCapturingParents, stopCapturingParents } from './capture'\nimport { GLOBAL_START_EPOCH } from './constants'\nimport { attach, detach, haveParentsChanged, singleton } from './helpers'\nimport { getGlobalEpoch } from './transactions'\nimport { Signal } from './types'\n\n/** @public */\nexport interface EffectSchedulerOptions {\n\t/**\n\t * scheduleEffect is a function that will be called when the effect is scheduled.\n\t *\n\t * It can be used to defer running effects until a later time, for example to batch them together with requestAnimationFrame.\n\t *\n\t *\n\t * @example\n\t * ```ts\n\t * let isRafScheduled = false\n\t * const scheduledEffects: Array<() => void> = []\n\t * const scheduleEffect = (runEffect: () => void) => {\n\t * \tscheduledEffects.push(runEffect)\n\t * \tif (!isRafScheduled) {\n\t * \t\tisRafScheduled = true\n\t * \t\trequestAnimationFrame(() => {\n\t * \t\t\tisRafScheduled = false\n\t * \t\t\tscheduledEffects.forEach((runEffect) => runEffect())\n\t * \t\t\tscheduledEffects.length = 0\n\t * \t\t})\n\t * \t}\n\t * }\n\t * const stop = react('set page title', () => {\n\t * \tdocument.title = doc.title,\n\t * }, scheduleEffect)\n\t * ```\n\t *\n\t * @param execute - A function that will execute the effect.\n\t * @returns\n\t */\n\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\tscheduleEffect?: (execute: () => void) => void\n}\n\nclass __EffectScheduler__<Result> implements EffectScheduler<Result> {\n\tprivate _isActivelyListening = false\n\t/**\n\t * Whether this scheduler is attached and actively listening to its parents.\n\t * @public\n\t */\n\t// eslint-disable-next-line no-restricted-syntax\n\tget isActivelyListening() {\n\t\treturn this._isActivelyListening\n\t}\n\t/** @internal */\n\tlastTraversedEpoch = GLOBAL_START_EPOCH\n\n\tprivate lastReactedEpoch = GLOBAL_START_EPOCH\n\tprivate _scheduleCount = 0\n\t__debug_ancestor_epochs__: Map<Signal<any, any>, number> | null = null\n\n\t/**\n\t * The number of times this effect has been scheduled.\n\t * @public\n\t */\n\t// eslint-disable-next-line no-restricted-syntax\n\tget scheduleCount() {\n\t\treturn this._scheduleCount\n\t}\n\n\t/** @internal */\n\treadonly parentSet = new ArraySet<Signal<any, any>>()\n\t/** @internal */\n\treadonly parentEpochs: number[] = []\n\t/** @internal */\n\treadonly parents: Signal<any, any>[] = []\n\tprivate readonly _scheduleEffect?: (execute: () => void) => void\n\tconstructor(\n\t\tpublic readonly name: string,\n\t\tprivate readonly runEffect: (lastReactedEpoch: number) => Result,\n\t\toptions?: EffectSchedulerOptions\n\t) {\n\t\tthis._scheduleEffect = options?.scheduleEffect\n\t}\n\n\t/** @internal */\n\tmaybeScheduleEffect() {\n\t\t// bail out if we have been cancelled by another effect\n\t\tif (!this._isActivelyListening) return\n\t\t// bail out if no atoms have changed since the last time we ran this effect\n\t\tif (this.lastReactedEpoch === getGlobalEpoch()) return\n\n\t\t// bail out if we have parents and they have not changed since last time\n\t\tif (this.parents.length && !haveParentsChanged(this)) {\n\t\t\tthis.lastReactedEpoch = getGlobalEpoch()\n\t\t\treturn\n\t\t}\n\t\t// if we don't have parents it's probably the first time this is running.\n\t\tthis.scheduleEffect()\n\t}\n\n\t/** @internal */\n\tscheduleEffect() {\n\t\tthis._scheduleCount++\n\t\tif (this._scheduleEffect) {\n\t\t\t// if the effect should be deferred (e.g. until a react render), do so\n\t\t\tthis._scheduleEffect(this.maybeExecute)\n\t\t} else {\n\t\t\t// otherwise execute right now!\n\t\t\tthis.execute()\n\t\t}\n\t}\n\n\t/** @internal */\n\t// eslint-disable-next-line local/prefer-class-methods\n\treadonly maybeExecute = () => {\n\t\t// bail out if we have been detached before this runs\n\t\tif (!this._isActivelyListening) return\n\t\tthis.execute()\n\t}\n\n\t/**\n\t * Makes this scheduler become 'actively listening' to its parents.\n\t * If it has been executed before it will immediately become eligible to receive 'maybeScheduleEffect' calls.\n\t * If it has not executed before it will need to be manually executed once to become eligible for scheduling, i.e. by calling `EffectScheduler.execute`.\n\t * @public\n\t */\n\tattach() {\n\t\tthis._isActivelyListening = true\n\t\tfor (let i = 0, n = this.parents.length; i < n; i++) {\n\t\t\tattach(this.parents[i], this)\n\t\t}\n\t}\n\n\t/**\n\t * Makes this scheduler stop 'actively listening' to its parents.\n\t * It will no longer be eligible to receive 'maybeScheduleEffect' calls until `EffectScheduler.attach` is called again.\n\t */\n\tdetach() {\n\t\tthis._isActivelyListening = false\n\t\tfor (let i = 0, n = this.parents.length; i < n; i++) {\n\t\t\tdetach(this.parents[i], this)\n\t\t}\n\t}\n\n\t/**\n\t * Executes the effect immediately and returns the result.\n\t * @returns The result of the effect.\n\t */\n\texecute(): Result {\n\t\ttry {\n\t\t\tstartCapturingParents(this)\n\t\t\t// Important! We have to make a note of the current epoch before running the effect.\n\t\t\t// We allow atoms to be updated during effects, which increments the global epoch,\n\t\t\t// so if we were to wait until after the effect runs, the this.lastReactedEpoch value might get ahead of itself.\n\t\t\tconst currentEpoch = getGlobalEpoch()\n\t\t\tconst result = this.runEffect(this.lastReactedEpoch)\n\t\t\tthis.lastReactedEpoch = currentEpoch\n\t\t\treturn result\n\t\t} finally {\n\t\t\tstopCapturingParents()\n\t\t}\n\t}\n}\n\n/**\n * An EffectScheduler is responsible for executing side effects in response to changes in state.\n *\n * You probably don't need to use this directly unless you're integrating this library with a framework of some kind.\n *\n * Instead, use the {@link react} and {@link reactor} functions.\n *\n * @example\n * ```ts\n * const render = new EffectScheduler('render', drawToCanvas)\n *\n * render.attach()\n * render.execute()\n * ```\n *\n * @public\n */\nexport const EffectScheduler = singleton(\n\t'EffectScheduler',\n\t(): {\n\t\tnew <Result>(\n\t\t\tname: string,\n\t\t\trunEffect: (lastReactedEpoch: number) => Result,\n\t\t\toptions?: EffectSchedulerOptions\n\t\t): EffectScheduler<Result>\n\t} => __EffectScheduler__\n)\n/** @public */\nexport interface EffectScheduler<Result> {\n\t/** @internal */\n\t/**\n\t * Whether this scheduler is attached and actively listening to its parents.\n\t * @public\n\t */\n\treadonly isActivelyListening: boolean\n\n\t/** @internal */\n\treadonly lastTraversedEpoch: number\n\n\t/** @public */\n\treadonly name: string\n\n\t/** @internal */\n\t__debug_ancestor_epochs__: Map<Signal<any, any>, number> | null\n\n\t/**\n\t * The number of times this effect has been scheduled.\n\t * @public\n\t */\n\treadonly scheduleCount: number\n\n\t/** @internal */\n\treadonly parentSet: ArraySet<Signal<any, any>>\n\n\t/** @internal */\n\treadonly parentEpochs: number[]\n\n\t/** @internal */\n\treadonly parents: Signal<any, any>[]\n\n\t/** @internal */\n\tmaybeScheduleEffect(): void\n\n\t/** @internal */\n\tscheduleEffect(): void\n\n\t/** @internal */\n\tmaybeExecute(): void\n\n\t/**\n\t * Makes this scheduler become 'actively listening' to its parents.\n\t * If it has been executed before it will immediately become eligible to receive 'maybeScheduleEffect' calls.\n\t * If it has not executed before it will need to be manually executed once to become eligible for scheduling, i.e. by calling `EffectScheduler.execute`.\n\t * @public\n\t */\n\tattach(): void\n\n\t/**\n\t * Makes this scheduler stop 'actively listening' to its parents.\n\t * It will no longer be eligible to receive 'maybeScheduleEffect' calls until `EffectScheduler.attach` is called again.\n\t */\n\tdetach(): void\n\n\t/**\n\t * Executes the effect immediately and returns the result.\n\t * @returns The result of the effect.\n\t */\n\texecute(): Result\n}\n\n/**\n * Starts a new effect scheduler, scheduling the effect immediately.\n *\n * Returns a function that can be called to stop the scheduler.\n *\n * @example\n * ```ts\n * const color = atom('color', 'red')\n * const stop = react('set style', () => {\n * divElem.style.color = color.get()\n * })\n * color.set('blue')\n * // divElem.style.color === 'blue'\n * stop()\n * color.set('green')\n * // divElem.style.color === 'blue'\n * ```\n *\n *\n * Also useful in React applications for running effects outside of the render cycle.\n *\n * @example\n * ```ts\n * useEffect(() => react('set style', () => {\n * divRef.current.style.color = color.get()\n * }), [])\n * ```\n *\n * @public\n */\nexport function react(\n\tname: string,\n\tfn: (lastReactedEpoch: number) => any,\n\toptions?: EffectSchedulerOptions\n) {\n\tconst scheduler = new EffectScheduler(name, fn, options)\n\tscheduler.attach()\n\tscheduler.scheduleEffect()\n\treturn () => {\n\t\tscheduler.detach()\n\t}\n}\n\n/**\n * The reactor is a user-friendly interface for starting and stopping an `EffectScheduler`.\n *\n * Calling `.start()` will attach the scheduler and execute the effect immediately the first time it is called.\n *\n * If the reactor is stopped, calling `.start()` will re-attach the scheduler but will only execute the effect if any of its parents have changed since it was stopped.\n *\n * You can create a reactor with {@link reactor}.\n * @public\n */\nexport interface Reactor<T = unknown> {\n\t/**\n\t * The underlying effect scheduler.\n\t * @public\n\t */\n\tscheduler: EffectScheduler<T>\n\t/**\n\t * Start the scheduler. The first time this is called the effect will be scheduled immediately.\n\t *\n\t * If the reactor is stopped, calling this will start the scheduler again but will only execute the effect if any of its parents have changed since it was stopped.\n\t *\n\t * If you need to force re-execution of the effect, pass `{ force: true }`.\n\t * @public\n\t */\n\tstart(options?: { force?: boolean }): void\n\t/**\n\t * Stop the scheduler.\n\t * @public\n\t */\n\tstop(): void\n}\n\n/**\n * Creates a {@link Reactor}, which is a thin wrapper around an `EffectScheduler`.\n *\n * @public\n */\nexport function reactor<Result>(\n\tname: string,\n\tfn: (lastReactedEpoch: number) => Result,\n\toptions?: EffectSchedulerOptions\n): Reactor<Result> {\n\tconst scheduler = new EffectScheduler<Result>(name, fn, options)\n\treturn {\n\t\tscheduler,\n\t\tstart: (options?: { force?: boolean }) => {\n\t\t\tconst force = options?.force ?? false\n\t\t\tscheduler.attach()\n\t\t\tif (force) {\n\t\t\t\tscheduler.scheduleEffect()\n\t\t\t} else {\n\t\t\t\tscheduler.maybeScheduleEffect()\n\t\t\t}\n\t\t},\n\t\tstop: () => {\n\t\t\tscheduler.detach()\n\t\t},\n\t}\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,uBAAuB,4BAA4B;AAC5D,SAAS,0BAA0B;AACnC,SAAS,QAAQ,QAAQ,oBAAoB,iBAAiB;AAC9D,SAAS,sBAAsB;AAsC/B,MAAM,oBAA+D;AAAA,
|
|
4
|
+
"sourcesContent": ["import { ArraySet } from './ArraySet'\nimport { startCapturingParents, stopCapturingParents } from './capture'\nimport { GLOBAL_START_EPOCH } from './constants'\nimport { attach, detach, haveParentsChanged, singleton } from './helpers'\nimport { getGlobalEpoch } from './transactions'\nimport { Signal } from './types'\n\n/** @public */\nexport interface EffectSchedulerOptions {\n\t/**\n\t * scheduleEffect is a function that will be called when the effect is scheduled.\n\t *\n\t * It can be used to defer running effects until a later time, for example to batch them together with requestAnimationFrame.\n\t *\n\t *\n\t * @example\n\t * ```ts\n\t * let isRafScheduled = false\n\t * const scheduledEffects: Array<() => void> = []\n\t * const scheduleEffect = (runEffect: () => void) => {\n\t * \tscheduledEffects.push(runEffect)\n\t * \tif (!isRafScheduled) {\n\t * \t\tisRafScheduled = true\n\t * \t\trequestAnimationFrame(() => {\n\t * \t\t\tisRafScheduled = false\n\t * \t\t\tscheduledEffects.forEach((runEffect) => runEffect())\n\t * \t\t\tscheduledEffects.length = 0\n\t * \t\t})\n\t * \t}\n\t * }\n\t * const stop = react('set page title', () => {\n\t * \tdocument.title = doc.title,\n\t * }, scheduleEffect)\n\t * ```\n\t *\n\t * @param execute - A function that will execute the effect.\n\t * @returns void\n\t */\n\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\tscheduleEffect?: (execute: () => void) => void\n}\n\nclass __EffectScheduler__<Result> implements EffectScheduler<Result> {\n\t/** @internal */\n\tprivate _isActivelyListening = false\n\t/**\n\t * Whether this scheduler is attached and actively listening to its parents.\n\t * @public\n\t */\n\t// eslint-disable-next-line no-restricted-syntax\n\tget isActivelyListening() {\n\t\treturn this._isActivelyListening\n\t}\n\t/** @internal */\n\tlastTraversedEpoch = GLOBAL_START_EPOCH\n\n\t/** @internal */\n\tprivate lastReactedEpoch = GLOBAL_START_EPOCH\n\n\t/** @internal */\n\tprivate _scheduleCount = 0\n\t/** @internal */\n\t__debug_ancestor_epochs__: Map<Signal<any, any>, number> | null = null\n\n\t/**\n\t * The number of times this effect has been scheduled.\n\t * @public\n\t */\n\t// eslint-disable-next-line no-restricted-syntax\n\tget scheduleCount() {\n\t\treturn this._scheduleCount\n\t}\n\n\t/** @internal */\n\treadonly parentSet = new ArraySet<Signal<any, any>>()\n\t/** @internal */\n\treadonly parentEpochs: number[] = []\n\t/** @internal */\n\treadonly parents: Signal<any, any>[] = []\n\t/** @internal */\n\tprivate readonly _scheduleEffect?: (execute: () => void) => void\n\tconstructor(\n\t\tpublic readonly name: string,\n\t\tprivate readonly runEffect: (lastReactedEpoch: number) => Result,\n\t\toptions?: EffectSchedulerOptions\n\t) {\n\t\tthis._scheduleEffect = options?.scheduleEffect\n\t}\n\n\t/** @internal */\n\tmaybeScheduleEffect() {\n\t\t// bail out if we have been cancelled by another effect\n\t\tif (!this._isActivelyListening) return\n\t\t// bail out if no atoms have changed since the last time we ran this effect\n\t\tif (this.lastReactedEpoch === getGlobalEpoch()) return\n\n\t\t// bail out if we have parents and they have not changed since last time\n\t\tif (this.parents.length && !haveParentsChanged(this)) {\n\t\t\tthis.lastReactedEpoch = getGlobalEpoch()\n\t\t\treturn\n\t\t}\n\t\t// if we don't have parents it's probably the first time this is running.\n\t\tthis.scheduleEffect()\n\t}\n\n\t/** @internal */\n\tscheduleEffect() {\n\t\tthis._scheduleCount++\n\t\tif (this._scheduleEffect) {\n\t\t\t// if the effect should be deferred (e.g. until a react render), do so\n\t\t\tthis._scheduleEffect(this.maybeExecute)\n\t\t} else {\n\t\t\t// otherwise execute right now!\n\t\t\tthis.execute()\n\t\t}\n\t}\n\n\t/** @internal */\n\t// eslint-disable-next-line local/prefer-class-methods\n\treadonly maybeExecute = () => {\n\t\t// bail out if we have been detached before this runs\n\t\tif (!this._isActivelyListening) return\n\t\tthis.execute()\n\t}\n\n\t/**\n\t * Makes this scheduler become 'actively listening' to its parents.\n\t * If it has been executed before it will immediately become eligible to receive 'maybeScheduleEffect' calls.\n\t * If it has not executed before it will need to be manually executed once to become eligible for scheduling, i.e. by calling `EffectScheduler.execute`.\n\t * @public\n\t */\n\tattach() {\n\t\tthis._isActivelyListening = true\n\t\tfor (let i = 0, n = this.parents.length; i < n; i++) {\n\t\t\tattach(this.parents[i], this)\n\t\t}\n\t}\n\n\t/**\n\t * Makes this scheduler stop 'actively listening' to its parents.\n\t * It will no longer be eligible to receive 'maybeScheduleEffect' calls until `EffectScheduler.attach` is called again.\n\t * @public\n\t */\n\tdetach() {\n\t\tthis._isActivelyListening = false\n\t\tfor (let i = 0, n = this.parents.length; i < n; i++) {\n\t\t\tdetach(this.parents[i], this)\n\t\t}\n\t}\n\n\t/**\n\t * Executes the effect immediately and returns the result.\n\t * @returns The result of the effect.\n\t * @public\n\t */\n\texecute(): Result {\n\t\ttry {\n\t\t\tstartCapturingParents(this)\n\t\t\t// Important! We have to make a note of the current epoch before running the effect.\n\t\t\t// We allow atoms to be updated during effects, which increments the global epoch,\n\t\t\t// so if we were to wait until after the effect runs, the this.lastReactedEpoch value might get ahead of itself.\n\t\t\tconst currentEpoch = getGlobalEpoch()\n\t\t\tconst result = this.runEffect(this.lastReactedEpoch)\n\t\t\tthis.lastReactedEpoch = currentEpoch\n\t\t\treturn result\n\t\t} finally {\n\t\t\tstopCapturingParents()\n\t\t}\n\t}\n}\n\n/**\n * An EffectScheduler is responsible for executing side effects in response to changes in state.\n *\n * You probably don't need to use this directly unless you're integrating this library with a framework of some kind.\n *\n * Instead, use the {@link react} and {@link reactor} functions.\n *\n * @example\n * ```ts\n * const render = new EffectScheduler('render', drawToCanvas)\n *\n * render.attach()\n * render.execute()\n * ```\n *\n * @public\n */\nexport const EffectScheduler = singleton(\n\t'EffectScheduler',\n\t(): {\n\t\tnew <Result>(\n\t\t\tname: string,\n\t\t\trunEffect: (lastReactedEpoch: number) => Result,\n\t\t\toptions?: EffectSchedulerOptions\n\t\t): EffectScheduler<Result>\n\t} => __EffectScheduler__\n)\n/** @public */\nexport interface EffectScheduler<Result> {\n\t/**\n\t * Whether this scheduler is attached and actively listening to its parents.\n\t * @public\n\t */\n\treadonly isActivelyListening: boolean\n\n\t/** @internal */\n\treadonly lastTraversedEpoch: number\n\n\t/** @public */\n\treadonly name: string\n\n\t/** @internal */\n\t__debug_ancestor_epochs__: Map<Signal<any, any>, number> | null\n\n\t/**\n\t * The number of times this effect has been scheduled.\n\t * @public\n\t */\n\treadonly scheduleCount: number\n\n\t/** @internal */\n\treadonly parentSet: ArraySet<Signal<any, any>>\n\n\t/** @internal */\n\treadonly parentEpochs: number[]\n\n\t/** @internal */\n\treadonly parents: Signal<any, any>[]\n\n\t/** @internal */\n\tmaybeScheduleEffect(): void\n\n\t/** @internal */\n\tscheduleEffect(): void\n\n\t/** @internal */\n\tmaybeExecute(): void\n\n\t/**\n\t * Makes this scheduler become 'actively listening' to its parents.\n\t * If it has been executed before it will immediately become eligible to receive 'maybeScheduleEffect' calls.\n\t * If it has not executed before it will need to be manually executed once to become eligible for scheduling, i.e. by calling `EffectScheduler.execute`.\n\t * @public\n\t */\n\tattach(): void\n\n\t/**\n\t * Makes this scheduler stop 'actively listening' to its parents.\n\t * It will no longer be eligible to receive 'maybeScheduleEffect' calls until `EffectScheduler.attach` is called again.\n\t * @public\n\t */\n\tdetach(): void\n\n\t/**\n\t * Executes the effect immediately and returns the result.\n\t * @returns The result of the effect.\n\t * @public\n\t */\n\texecute(): Result\n}\n\n/**\n * Starts a new effect scheduler, scheduling the effect immediately.\n *\n * Returns a function that can be called to stop the scheduler.\n *\n * @example\n * ```ts\n * const color = atom('color', 'red')\n * const stop = react('set style', () => {\n * divElem.style.color = color.get()\n * })\n * color.set('blue')\n * // divElem.style.color === 'blue'\n * stop()\n * color.set('green')\n * // divElem.style.color === 'blue'\n * ```\n *\n *\n * Also useful in React applications for running effects outside of the render cycle.\n *\n * @example\n * ```ts\n * useEffect(() => react('set style', () => {\n * divRef.current.style.color = color.get()\n * }), [])\n * ```\n *\n * @public\n */\nexport function react(\n\tname: string,\n\tfn: (lastReactedEpoch: number) => any,\n\toptions?: EffectSchedulerOptions\n) {\n\tconst scheduler = new EffectScheduler(name, fn, options)\n\tscheduler.attach()\n\tscheduler.scheduleEffect()\n\treturn () => {\n\t\tscheduler.detach()\n\t}\n}\n\n/**\n * The reactor is a user-friendly interface for starting and stopping an `EffectScheduler`.\n *\n * Calling `.start()` will attach the scheduler and execute the effect immediately the first time it is called.\n *\n * If the reactor is stopped, calling `.start()` will re-attach the scheduler but will only execute the effect if any of its parents have changed since it was stopped.\n *\n * You can create a reactor with {@link reactor}.\n * @public\n */\nexport interface Reactor<T = unknown> {\n\t/**\n\t * The underlying effect scheduler.\n\t * @public\n\t */\n\tscheduler: EffectScheduler<T>\n\t/**\n\t * Start the scheduler. The first time this is called the effect will be scheduled immediately.\n\t *\n\t * If the reactor is stopped, calling this will start the scheduler again but will only execute the effect if any of its parents have changed since it was stopped.\n\t *\n\t * If you need to force re-execution of the effect, pass `{ force: true }`.\n\t * @public\n\t */\n\tstart(options?: { force?: boolean }): void\n\t/**\n\t * Stop the scheduler.\n\t * @public\n\t */\n\tstop(): void\n}\n\n/**\n * Creates a {@link Reactor}, which is a thin wrapper around an `EffectScheduler`.\n *\n * @public\n */\nexport function reactor<Result>(\n\tname: string,\n\tfn: (lastReactedEpoch: number) => Result,\n\toptions?: EffectSchedulerOptions\n): Reactor<Result> {\n\tconst scheduler = new EffectScheduler<Result>(name, fn, options)\n\treturn {\n\t\tscheduler,\n\t\tstart: (options?: { force?: boolean }) => {\n\t\t\tconst force = options?.force ?? false\n\t\t\tscheduler.attach()\n\t\t\tif (force) {\n\t\t\t\tscheduler.scheduleEffect()\n\t\t\t} else {\n\t\t\t\tscheduler.maybeScheduleEffect()\n\t\t\t}\n\t\t},\n\t\tstop: () => {\n\t\t\tscheduler.detach()\n\t\t},\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["options"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/state",
|
|
3
3
|
"description": "tldraw infinite canvas SDK (state).",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.0.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -32,31 +32,24 @@
|
|
|
32
32
|
"src"
|
|
33
33
|
],
|
|
34
34
|
"scripts": {
|
|
35
|
-
"test-ci": "
|
|
36
|
-
"test": "yarn run -T
|
|
37
|
-
"test-coverage": "
|
|
35
|
+
"test-ci": "yarn run -T vitest run --passWithNoTests",
|
|
36
|
+
"test": "yarn run -T vitest --passWithNoTests",
|
|
37
|
+
"test-coverage": "yarn run -T vitest run --coverage --passWithNoTests",
|
|
38
38
|
"build": "yarn run -T tsx ../../internal/scripts/build-package.ts",
|
|
39
39
|
"build-api": "yarn run -T tsx ../../internal/scripts/build-api.ts",
|
|
40
40
|
"prepack": "yarn run -T tsx ../../internal/scripts/prepack.ts",
|
|
41
41
|
"postpack": "../../internal/scripts/postpack.sh",
|
|
42
42
|
"pack-tarball": "yarn pack",
|
|
43
|
-
"lint": "yarn run -T tsx ../../internal/scripts/lint.ts"
|
|
44
|
-
|
|
45
|
-
"jest": {
|
|
46
|
-
"preset": "../../internal/config/jest/node/jest-preset.js",
|
|
47
|
-
"setupFiles": [
|
|
48
|
-
"raf/polyfill"
|
|
49
|
-
],
|
|
50
|
-
"moduleNameMapper": {
|
|
51
|
-
"^~(.*)": "<rootDir>/src/$1"
|
|
52
|
-
}
|
|
43
|
+
"lint": "yarn run -T tsx ../../internal/scripts/lint.ts",
|
|
44
|
+
"context": "yarn run -T tsx ../../internal/scripts/context.ts"
|
|
53
45
|
},
|
|
54
46
|
"devDependencies": {
|
|
55
47
|
"@types/lodash": "^4.17.14",
|
|
56
|
-
"lodash": "^4.17.21"
|
|
48
|
+
"lodash": "^4.17.21",
|
|
49
|
+
"vitest": "^3.2.4"
|
|
57
50
|
},
|
|
58
51
|
"dependencies": {
|
|
59
|
-
"@tldraw/utils": "
|
|
52
|
+
"@tldraw/utils": "4.0.0"
|
|
60
53
|
},
|
|
61
54
|
"typedoc": {
|
|
62
55
|
"readmeFile": "none",
|
package/src/lib/Atom.ts
CHANGED
|
@@ -30,7 +30,7 @@ export interface AtomOptions<Value, Diff> {
|
|
|
30
30
|
* By default, values are compared using first using strict equality (`===`), then `Object.is`, and finally any `.equals` method present in the object's prototype chain.
|
|
31
31
|
* @param a - The old value
|
|
32
32
|
* @param b - The new value
|
|
33
|
-
* @returns
|
|
33
|
+
* @returns True if the values are equal, false otherwise.
|
|
34
34
|
*/
|
|
35
35
|
isEqual?(a: any, b: any): boolean
|
|
36
36
|
}
|
package/src/lib/Computed.ts
CHANGED
|
@@ -105,15 +105,15 @@ export interface ComputedOptions<Value, Diff> {
|
|
|
105
105
|
*/
|
|
106
106
|
historyLength?: number
|
|
107
107
|
/**
|
|
108
|
-
* A method used to compute a diff between the
|
|
108
|
+
* A method used to compute a diff between the computed's old and new values. If provided, it will not be used unless you also specify {@link ComputedOptions.historyLength}.
|
|
109
109
|
*/
|
|
110
110
|
computeDiff?: ComputeDiff<Value, Diff>
|
|
111
111
|
/**
|
|
112
|
-
* If provided, this will be used to compare the old and new values of the
|
|
112
|
+
* If provided, this will be used to compare the old and new values of the computed to determine if the value has changed.
|
|
113
113
|
* By default, values are compared using first using strict equality (`===`), then `Object.is`, and finally any `.equals` method present in the object's prototype chain.
|
|
114
114
|
* @param a - The old value
|
|
115
115
|
* @param b - The new value
|
|
116
|
-
* @returns
|
|
116
|
+
* @returns True if the values are equal, false otherwise.
|
|
117
117
|
*/
|
|
118
118
|
isEqual?(a: any, b: any): boolean
|
|
119
119
|
}
|
|
@@ -34,13 +34,14 @@ export interface EffectSchedulerOptions {
|
|
|
34
34
|
* ```
|
|
35
35
|
*
|
|
36
36
|
* @param execute - A function that will execute the effect.
|
|
37
|
-
* @returns
|
|
37
|
+
* @returns void
|
|
38
38
|
*/
|
|
39
39
|
// eslint-disable-next-line @typescript-eslint/method-signature-style
|
|
40
40
|
scheduleEffect?: (execute: () => void) => void
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
class __EffectScheduler__<Result> implements EffectScheduler<Result> {
|
|
44
|
+
/** @internal */
|
|
44
45
|
private _isActivelyListening = false
|
|
45
46
|
/**
|
|
46
47
|
* Whether this scheduler is attached and actively listening to its parents.
|
|
@@ -53,8 +54,12 @@ class __EffectScheduler__<Result> implements EffectScheduler<Result> {
|
|
|
53
54
|
/** @internal */
|
|
54
55
|
lastTraversedEpoch = GLOBAL_START_EPOCH
|
|
55
56
|
|
|
57
|
+
/** @internal */
|
|
56
58
|
private lastReactedEpoch = GLOBAL_START_EPOCH
|
|
59
|
+
|
|
60
|
+
/** @internal */
|
|
57
61
|
private _scheduleCount = 0
|
|
62
|
+
/** @internal */
|
|
58
63
|
__debug_ancestor_epochs__: Map<Signal<any, any>, number> | null = null
|
|
59
64
|
|
|
60
65
|
/**
|
|
@@ -72,6 +77,7 @@ class __EffectScheduler__<Result> implements EffectScheduler<Result> {
|
|
|
72
77
|
readonly parentEpochs: number[] = []
|
|
73
78
|
/** @internal */
|
|
74
79
|
readonly parents: Signal<any, any>[] = []
|
|
80
|
+
/** @internal */
|
|
75
81
|
private readonly _scheduleEffect?: (execute: () => void) => void
|
|
76
82
|
constructor(
|
|
77
83
|
public readonly name: string,
|
|
@@ -133,6 +139,7 @@ class __EffectScheduler__<Result> implements EffectScheduler<Result> {
|
|
|
133
139
|
/**
|
|
134
140
|
* Makes this scheduler stop 'actively listening' to its parents.
|
|
135
141
|
* It will no longer be eligible to receive 'maybeScheduleEffect' calls until `EffectScheduler.attach` is called again.
|
|
142
|
+
* @public
|
|
136
143
|
*/
|
|
137
144
|
detach() {
|
|
138
145
|
this._isActivelyListening = false
|
|
@@ -144,6 +151,7 @@ class __EffectScheduler__<Result> implements EffectScheduler<Result> {
|
|
|
144
151
|
/**
|
|
145
152
|
* Executes the effect immediately and returns the result.
|
|
146
153
|
* @returns The result of the effect.
|
|
154
|
+
* @public
|
|
147
155
|
*/
|
|
148
156
|
execute(): Result {
|
|
149
157
|
try {
|
|
@@ -190,7 +198,6 @@ export const EffectScheduler = singleton(
|
|
|
190
198
|
)
|
|
191
199
|
/** @public */
|
|
192
200
|
export interface EffectScheduler<Result> {
|
|
193
|
-
/** @internal */
|
|
194
201
|
/**
|
|
195
202
|
* Whether this scheduler is attached and actively listening to its parents.
|
|
196
203
|
* @public
|
|
@@ -241,12 +248,14 @@ export interface EffectScheduler<Result> {
|
|
|
241
248
|
/**
|
|
242
249
|
* Makes this scheduler stop 'actively listening' to its parents.
|
|
243
250
|
* It will no longer be eligible to receive 'maybeScheduleEffect' calls until `EffectScheduler.attach` is called again.
|
|
251
|
+
* @public
|
|
244
252
|
*/
|
|
245
253
|
detach(): void
|
|
246
254
|
|
|
247
255
|
/**
|
|
248
256
|
* Executes the effect immediately and returns the result.
|
|
249
257
|
* @returns The result of the effect.
|
|
258
|
+
* @public
|
|
250
259
|
*/
|
|
251
260
|
execute(): Result
|
|
252
261
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { vi } from 'vitest'
|
|
1
2
|
import { atom } from '../Atom'
|
|
2
3
|
import { Computed, _Computed, computed, getComputedInstance, isUninitialized } from '../Computed'
|
|
3
4
|
import { reactor } from '../EffectScheduler'
|
|
@@ -11,7 +12,7 @@ function getLastCheckedEpoch(derivation: Computed<any>): number {
|
|
|
11
12
|
|
|
12
13
|
describe('derivations', () => {
|
|
13
14
|
it('will cache a value forever if it has no parents', () => {
|
|
14
|
-
const derive =
|
|
15
|
+
const derive = vi.fn(() => 1)
|
|
15
16
|
const startEpoch = getGlobalEpoch()
|
|
16
17
|
const derivation = computed('', derive)
|
|
17
18
|
|
|
@@ -45,7 +46,7 @@ describe('derivations', () => {
|
|
|
45
46
|
|
|
46
47
|
it('will update when parent atoms update', () => {
|
|
47
48
|
const a = atom('', 1)
|
|
48
|
-
const double =
|
|
49
|
+
const double = vi.fn(() => a.get() * 2)
|
|
49
50
|
const derivation = computed('', double)
|
|
50
51
|
const startEpoch = getGlobalEpoch()
|
|
51
52
|
expect(double).toHaveBeenCalledTimes(0)
|
|
@@ -122,7 +123,7 @@ describe('derivations', () => {
|
|
|
122
123
|
const startEpoch = getGlobalEpoch()
|
|
123
124
|
const a = atom('', 1)
|
|
124
125
|
|
|
125
|
-
const floor =
|
|
126
|
+
const floor = vi.fn((n: number) => Math.floor(n))
|
|
126
127
|
const derivation = computed('', () => floor(a.get()), {
|
|
127
128
|
historyLength: 3,
|
|
128
129
|
computeDiff: (a, b) => {
|
|
@@ -162,7 +163,7 @@ describe('derivations', () => {
|
|
|
162
163
|
const startEpoch = getGlobalEpoch()
|
|
163
164
|
const a = atom('', 1)
|
|
164
165
|
|
|
165
|
-
const double =
|
|
166
|
+
const double = vi.fn(() => a.get() * 2)
|
|
166
167
|
const derivation = computed('', double)
|
|
167
168
|
|
|
168
169
|
derivation.get()
|
|
@@ -179,7 +180,7 @@ describe('derivations', () => {
|
|
|
179
180
|
|
|
180
181
|
it('receives UNINTIALIZED as the previousValue the first time it computes', () => {
|
|
181
182
|
const a = atom('', 1)
|
|
182
|
-
const double =
|
|
183
|
+
const double = vi.fn((_prevValue) => a.get() * 2)
|
|
183
184
|
const derivation = computed('', double)
|
|
184
185
|
|
|
185
186
|
expect(derivation.get()).toBe(2)
|
|
@@ -195,7 +196,7 @@ describe('derivations', () => {
|
|
|
195
196
|
|
|
196
197
|
it('receives the lastChangedEpoch as the second parameter each time it recomputes', () => {
|
|
197
198
|
const a = atom('', 1)
|
|
198
|
-
const double =
|
|
199
|
+
const double = vi.fn((_prevValue, lastChangedEpoch) => {
|
|
199
200
|
expect(lastChangedEpoch).toBe(derivation.lastChangedEpoch)
|
|
200
201
|
return a.get() * 2
|
|
201
202
|
})
|
|
@@ -438,7 +439,7 @@ describe('incremental derivations', () => {
|
|
|
438
439
|
}
|
|
439
440
|
)
|
|
440
441
|
|
|
441
|
-
const mapper =
|
|
442
|
+
const mapper = vi.fn((val) => val * 2)
|
|
442
443
|
|
|
443
444
|
const doubledNodes = getIncrementalRecordMapper(nodes, mapper)
|
|
444
445
|
|
|
@@ -590,7 +591,7 @@ describe(getComputedInstance, () => {
|
|
|
590
591
|
|
|
591
592
|
describe('computed isEqual', () => {
|
|
592
593
|
it('does not get called for the initialization', () => {
|
|
593
|
-
const isEqual =
|
|
594
|
+
const isEqual = vi.fn((a, b) => a === b)
|
|
594
595
|
|
|
595
596
|
const a = atom('a', 1)
|
|
596
597
|
const b = computed('b', () => a.get() * 2, { isEqual })
|
|
@@ -11,7 +11,7 @@ describe('reactors that error', () => {
|
|
|
11
11
|
react('', () => {
|
|
12
12
|
if (a.get() === 2) throw new Error('test')
|
|
13
13
|
})
|
|
14
|
-
expect(() => a.set(2)).toThrowErrorMatchingInlineSnapshot(`
|
|
14
|
+
expect(() => a.set(2)).toThrowErrorMatchingInlineSnapshot(`[Error: test]`)
|
|
15
15
|
expect(a.get()).toBe(2)
|
|
16
16
|
})
|
|
17
17
|
it('will not roll back the changes in a transaction', () => {
|
|
@@ -27,7 +27,7 @@ describe('reactors that error', () => {
|
|
|
27
27
|
a.set(3)
|
|
28
28
|
b.set(1)
|
|
29
29
|
})
|
|
30
|
-
).toThrowErrorMatchingInlineSnapshot(`
|
|
30
|
+
).toThrowErrorMatchingInlineSnapshot(`[Error: test]`)
|
|
31
31
|
|
|
32
32
|
expect(a.get()).toBe(3)
|
|
33
33
|
expect(b.get()).toBe(1)
|
|
@@ -49,11 +49,11 @@ describe('derivations that error', () => {
|
|
|
49
49
|
|
|
50
50
|
a.set(2)
|
|
51
51
|
|
|
52
|
-
expect(() => b.get()).toThrowErrorMatchingInlineSnapshot(`
|
|
52
|
+
expect(() => b.get()).toThrowErrorMatchingInlineSnapshot(`[Error: test]`)
|
|
53
53
|
expect(numComputations).toBe(2)
|
|
54
|
-
expect(() => b.get()).toThrowErrorMatchingInlineSnapshot(`
|
|
54
|
+
expect(() => b.get()).toThrowErrorMatchingInlineSnapshot(`[Error: test]`)
|
|
55
55
|
expect(numComputations).toBe(2)
|
|
56
|
-
expect(() => b.get()).toThrowErrorMatchingInlineSnapshot(`
|
|
56
|
+
expect(() => b.get()).toThrowErrorMatchingInlineSnapshot(`[Error: test]`)
|
|
57
57
|
expect(numComputations).toBe(2)
|
|
58
58
|
|
|
59
59
|
a.set(3)
|
|
@@ -128,7 +128,7 @@ describe('derivations that error', () => {
|
|
|
128
128
|
|
|
129
129
|
a.set(5)
|
|
130
130
|
|
|
131
|
-
expect(() => b.get()).toThrowErrorMatchingInlineSnapshot(`
|
|
131
|
+
expect(() => b.get()).toThrowErrorMatchingInlineSnapshot(`[Error: test]`)
|
|
132
132
|
expect(b.getDiffSince(startEpoch)).toEqual(RESET_VALUE)
|
|
133
133
|
const errorEpoch = getGlobalEpoch()
|
|
134
134
|
|
|
@@ -153,11 +153,11 @@ test('haveParentsChanged will not throw if one of the parents is throwing', () =
|
|
|
153
153
|
expect(() => {
|
|
154
154
|
scheduler.attach()
|
|
155
155
|
scheduler.execute()
|
|
156
|
-
}).toThrowErrorMatchingInlineSnapshot(`
|
|
156
|
+
}).toThrowErrorMatchingInlineSnapshot(`[Error: test]`)
|
|
157
157
|
|
|
158
158
|
expect(haveParentsChanged(scheduler)).toBe(false)
|
|
159
159
|
|
|
160
|
-
expect(() => a.set(2)).toThrowErrorMatchingInlineSnapshot(`
|
|
160
|
+
expect(() => a.set(2)).toThrowErrorMatchingInlineSnapshot(`[Error: test]`)
|
|
161
161
|
|
|
162
162
|
// haveParentsChanged should still be false because it already
|
|
163
163
|
// executed the effect and it errored
|