@tldraw/state 5.3.2 → 5.4.0-canary.02cd0bd3b597
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/DOCS.md +64 -63
- package/README.md +35 -36
- package/dist-cjs/index.d.ts +26 -27
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/ArraySet.js +47 -144
- package/dist-cjs/lib/ArraySet.js.map +2 -2
- package/dist-cjs/lib/Atom.js +12 -26
- package/dist-cjs/lib/Atom.js.map +2 -2
- package/dist-cjs/lib/Computed.js +36 -64
- package/dist-cjs/lib/Computed.js.map +2 -2
- package/dist-cjs/lib/EffectScheduler.js +1 -1
- package/dist-cjs/lib/EffectScheduler.js.map +2 -2
- package/dist-cjs/lib/HistoryBuffer.js +8 -8
- package/dist-cjs/lib/HistoryBuffer.js.map +2 -2
- package/dist-cjs/lib/capture.js +1 -3
- package/dist-cjs/lib/capture.js.map +2 -2
- package/dist-cjs/lib/constants.js.map +2 -2
- package/dist-cjs/lib/helpers.js +3 -11
- package/dist-cjs/lib/helpers.js.map +2 -2
- package/dist-cjs/lib/localStorageAtom.js +7 -2
- package/dist-cjs/lib/localStorageAtom.js.map +2 -2
- package/dist-cjs/lib/transactions.js +11 -19
- package/dist-cjs/lib/transactions.js.map +2 -2
- package/dist-cjs/lib/types.js.map +1 -1
- package/dist-cjs/lib/warnings.js +2 -4
- package/dist-cjs/lib/warnings.js.map +2 -2
- package/dist-esm/index.d.mts +26 -27
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/ArraySet.mjs +47 -144
- package/dist-esm/lib/ArraySet.mjs.map +2 -2
- package/dist-esm/lib/Atom.mjs +12 -26
- package/dist-esm/lib/Atom.mjs.map +2 -2
- package/dist-esm/lib/Computed.mjs +36 -64
- package/dist-esm/lib/Computed.mjs.map +2 -2
- package/dist-esm/lib/EffectScheduler.mjs +1 -1
- package/dist-esm/lib/EffectScheduler.mjs.map +2 -2
- package/dist-esm/lib/HistoryBuffer.mjs +8 -8
- package/dist-esm/lib/HistoryBuffer.mjs.map +2 -2
- package/dist-esm/lib/capture.mjs +1 -3
- package/dist-esm/lib/capture.mjs.map +2 -2
- package/dist-esm/lib/constants.mjs.map +2 -2
- package/dist-esm/lib/helpers.mjs +3 -11
- package/dist-esm/lib/helpers.mjs.map +2 -2
- package/dist-esm/lib/localStorageAtom.mjs +7 -2
- package/dist-esm/lib/localStorageAtom.mjs.map +2 -2
- package/dist-esm/lib/transactions.mjs +11 -19
- package/dist-esm/lib/transactions.mjs.map +2 -2
- package/dist-esm/lib/types.mjs.map +1 -1
- package/dist-esm/lib/warnings.mjs +2 -4
- package/dist-esm/lib/warnings.mjs.map +2 -2
- package/package.json +2 -2
- package/src/lib/ArraySet.ts +68 -176
- package/src/lib/Atom.ts +27 -31
- package/src/lib/Computed.ts +68 -96
- package/src/lib/EffectScheduler.ts +9 -8
- package/src/lib/HistoryBuffer.ts +12 -10
- package/src/lib/__tests__/ArraySet.test.ts +39 -13
- package/src/lib/__tests__/EffectScheduler.test.ts +18 -0
- package/src/lib/__tests__/HistoryBuffer.test.ts +6 -3
- package/src/lib/__tests__/computed.test.ts +75 -0
- package/src/lib/__tests__/errors.test.ts +24 -0
- package/src/lib/__tests__/helpers.test.ts +7 -11
- package/src/lib/__tests__/history.test.ts +32 -2
- package/src/lib/__tests__/localStorageAtom.test.ts +15 -0
- package/src/lib/capture.ts +13 -13
- package/src/lib/constants.ts +3 -22
- package/src/lib/helpers.ts +15 -140
- package/src/lib/localStorageAtom.ts +9 -2
- package/src/lib/transactions.ts +23 -47
- package/src/lib/types.ts +7 -7
- package/src/lib/warnings.ts +2 -10
package/dist-cjs/lib/Computed.js
CHANGED
|
@@ -111,27 +111,29 @@ class __UNSAFE__Computed {
|
|
|
111
111
|
const result = this.derive(this.state, this.lastCheckedEpoch);
|
|
112
112
|
const newState = result instanceof WithDiff ? result.value : result;
|
|
113
113
|
const isUninitialized2 = this.state === UNINITIALIZED;
|
|
114
|
+
const epoch = (0, import_transactions.getGlobalEpoch)();
|
|
114
115
|
if (isUninitialized2 || !this.isEqual(this.state, newState)) {
|
|
115
116
|
if (this.historyBuffer && !isUninitialized2) {
|
|
116
117
|
const diff = result instanceof WithDiff ? result.diff : void 0;
|
|
117
118
|
this.historyBuffer.pushEntry(
|
|
118
119
|
this.lastChangedEpoch,
|
|
119
|
-
|
|
120
|
-
diff
|
|
120
|
+
epoch,
|
|
121
|
+
diff !== void 0 ? diff : this.computeDiff ? this.computeDiff(this.state, newState, this.lastCheckedEpoch, epoch) : import_types.RESET_VALUE
|
|
121
122
|
);
|
|
122
123
|
}
|
|
123
|
-
this.lastChangedEpoch =
|
|
124
|
+
this.lastChangedEpoch = epoch;
|
|
124
125
|
this.state = newState;
|
|
125
126
|
}
|
|
126
127
|
this.error = null;
|
|
127
|
-
this.lastCheckedEpoch =
|
|
128
|
+
this.lastCheckedEpoch = epoch;
|
|
128
129
|
return this.state;
|
|
129
130
|
} catch (e) {
|
|
130
|
-
|
|
131
|
+
const epoch = (0, import_transactions.getGlobalEpoch)();
|
|
132
|
+
if (this.error === null) {
|
|
131
133
|
this.state = UNINITIALIZED;
|
|
132
|
-
this.lastChangedEpoch =
|
|
134
|
+
this.lastChangedEpoch = epoch;
|
|
133
135
|
}
|
|
134
|
-
this.lastCheckedEpoch =
|
|
136
|
+
this.lastCheckedEpoch = epoch;
|
|
135
137
|
if (this.historyBuffer) {
|
|
136
138
|
this.historyBuffer.clear();
|
|
137
139
|
}
|
|
@@ -159,13 +161,13 @@ class __UNSAFE__Computed {
|
|
|
159
161
|
}
|
|
160
162
|
}
|
|
161
163
|
const _Computed = (0, import_helpers.singleton)("Computed", () => __UNSAFE__Computed);
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const derivationKey = /* @__PURE__ */ Symbol
|
|
165
|
-
|
|
164
|
+
const derivationKeyKey = "@@__computedDerivationKey__@@";
|
|
165
|
+
function makeComputedWrapper(name, compute, options) {
|
|
166
|
+
const derivationKey = /* @__PURE__ */ Symbol("__@tldraw/state__computed__" + name);
|
|
167
|
+
const wrapper = function() {
|
|
166
168
|
let d = this[derivationKey];
|
|
167
169
|
if (!d) {
|
|
168
|
-
d = new _Computed(
|
|
170
|
+
d = new _Computed(name, compute.bind(this), options);
|
|
169
171
|
Object.defineProperty(this, derivationKey, {
|
|
170
172
|
enumerable: false,
|
|
171
173
|
configurable: false,
|
|
@@ -175,75 +177,45 @@ function computedMethodLegacyDecorator(options = {}, _target, key, descriptor) {
|
|
|
175
177
|
}
|
|
176
178
|
return d.get();
|
|
177
179
|
};
|
|
178
|
-
|
|
179
|
-
return
|
|
180
|
-
}
|
|
181
|
-
function computedGetterLegacyDecorator(options = {}, _target, key, descriptor) {
|
|
182
|
-
const originalMethod = descriptor.get;
|
|
183
|
-
const derivationKey = /* @__PURE__ */ Symbol.for("__@tldraw/state__computed__" + key);
|
|
184
|
-
descriptor.get = function() {
|
|
185
|
-
let d = this[derivationKey];
|
|
186
|
-
if (!d) {
|
|
187
|
-
d = new _Computed(key, originalMethod.bind(this), options);
|
|
188
|
-
Object.defineProperty(this, derivationKey, {
|
|
189
|
-
enumerable: false,
|
|
190
|
-
configurable: false,
|
|
191
|
-
writable: false,
|
|
192
|
-
value: d
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
return d.get();
|
|
196
|
-
};
|
|
197
|
-
return descriptor;
|
|
198
|
-
}
|
|
199
|
-
function computedMethodTc39Decorator(options, compute, context) {
|
|
200
|
-
(0, import_utils.assert)(context.kind === "method", "@computed can only be used on methods");
|
|
201
|
-
const derivationKey = /* @__PURE__ */ Symbol.for("__@tldraw/state__computed__" + String(context.name));
|
|
202
|
-
const fn = function() {
|
|
203
|
-
let d = this[derivationKey];
|
|
204
|
-
if (!d) {
|
|
205
|
-
d = new _Computed(String(context.name), compute.bind(this), options);
|
|
206
|
-
Object.defineProperty(this, derivationKey, {
|
|
207
|
-
enumerable: false,
|
|
208
|
-
configurable: false,
|
|
209
|
-
writable: false,
|
|
210
|
-
value: d
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
return d.get();
|
|
214
|
-
};
|
|
215
|
-
fn[isComputedMethodKey] = true;
|
|
216
|
-
return fn;
|
|
180
|
+
wrapper[derivationKeyKey] = derivationKey;
|
|
181
|
+
return wrapper;
|
|
217
182
|
}
|
|
218
183
|
function computedDecorator(options = {}, args) {
|
|
219
184
|
if (args.length === 2) {
|
|
220
185
|
const [originalMethod, context] = args;
|
|
221
|
-
|
|
186
|
+
(0, import_utils.assert)(context.kind === "method", "@computed can only be used on methods");
|
|
187
|
+
return makeComputedWrapper(String(context.name), originalMethod, options);
|
|
222
188
|
} else {
|
|
223
189
|
const [_target, key, descriptor] = args;
|
|
224
190
|
if (descriptor.get) {
|
|
225
191
|
(0, import_warnings.logComputedGetterWarning)();
|
|
226
|
-
|
|
192
|
+
descriptor.get = makeComputedWrapper(key, descriptor.get, options);
|
|
227
193
|
} else {
|
|
228
|
-
|
|
194
|
+
descriptor.value = makeComputedWrapper(key, descriptor.value, options);
|
|
229
195
|
}
|
|
196
|
+
return descriptor;
|
|
230
197
|
}
|
|
231
198
|
}
|
|
232
|
-
const isComputedMethodKey = "@@__isComputedMethod__@@";
|
|
233
199
|
function getComputedInstance(obj, propertyName) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
if (
|
|
239
|
-
|
|
200
|
+
for (let proto = obj; proto; proto = Object.getPrototypeOf(proto)) {
|
|
201
|
+
const descriptor = Object.getOwnPropertyDescriptor(proto, propertyName);
|
|
202
|
+
const wrapper = descriptor?.get ?? descriptor?.value;
|
|
203
|
+
const key = wrapper?.[derivationKeyKey];
|
|
204
|
+
if (!key) continue;
|
|
205
|
+
let inst = obj[key];
|
|
206
|
+
if (!inst) {
|
|
207
|
+
try {
|
|
208
|
+
wrapper.call(obj);
|
|
209
|
+
} catch {
|
|
210
|
+
}
|
|
211
|
+
inst = obj[key];
|
|
240
212
|
}
|
|
241
|
-
inst
|
|
213
|
+
return inst;
|
|
242
214
|
}
|
|
243
|
-
return
|
|
215
|
+
return void 0;
|
|
244
216
|
}
|
|
245
217
|
function computed() {
|
|
246
|
-
if (arguments.length
|
|
218
|
+
if (arguments.length <= 1) {
|
|
247
219
|
const options = arguments[0];
|
|
248
220
|
return (...args) => computedDecorator(options, args);
|
|
249
221
|
} else if (typeof arguments[0] === "string") {
|
|
@@ -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 { maybeCaptureParent, startCapturingParents, stopCapturingParents } from './capture'\nimport { GLOBAL_START_EPOCH } from './constants'\nimport { EMPTY_ARRAY, equals, haveParentsChanged, singleton } from './helpers'\nimport { HistoryBuffer } from './HistoryBuffer'\nimport { getGlobalEpoch, getIsReacting, getReactionEpoch } from './transactions'\nimport { Child, ComputeDiff, RESET_VALUE, Signal } from './types'\nimport { logComputedGetterWarning } from './warnings'\n\n/**\n * A special symbol used to indicate that a computed signal has not been initialized yet.\n * This is passed as the `previousValue` parameter to a computed signal function on its first run.\n *\n * @example\n * ```ts\n * const count = atom('count', 0)\n * const double = computed('double', (prevValue) => {\n * if (isUninitialized(prevValue)) {\n * console.log('First computation!')\n * }\n * return count.get() * 2\n * })\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 * This type represents the uninitialized state of a computed signal before its first calculation.\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/**\n * A singleton class used to wrap computed signal values along with their diffs.\n * This class is used internally by the {@link withDiff} function to provide both\n * the computed value and its diff to the signal system.\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 * })\n * ```\n *\n * @public\n */\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/**\n * Interface representing a value wrapped with its corresponding diff.\n * Used in incremental computation to provide both the new value and the diff from the previous value.\n *\n * @public\n */\nexport interface WithDiff<Value, Diff> {\n\t/**\n\t * The computed value.\n\t */\n\tvalue: Value\n\t/**\n\t * The diff between the previous and current value.\n\t */\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 configuring computed signals. Used when calling `computed` or using the `@computed` decorator.\n *\n * @example\n * ```ts\n * const greeting = computed('greeting', () => `Hello ${name.get()}!`, {\n * historyLength: 10,\n * isEqual: (a, b) => a === b,\n * computeDiff: (oldVal, newVal) => ({ type: 'change', from: oldVal, to: newVal })\n * })\n * ```\n *\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 the `computed` function or `@computed` decorator.\n * Computed signals derive their values from other signals and automatically update when their dependencies change.\n * They use lazy evaluation, only recalculating when accessed and dependencies have changed.\n *\n * @example\n * ```ts\n * const firstName = atom('firstName', 'John')\n * const lastName = atom('lastName', 'Doe')\n * const fullName = computed('fullName', () => `${firstName.get()} ${lastName.get()}`)\n *\n * console.log(fullName.get()) // \"John Doe\"\n * firstName.set('Jane')\n * console.log(fullName.get()) // \"Jane Doe\"\n * ```\n *\n * @public\n */\nexport interface Computed<Value, Diff = unknown> extends Signal<Value, Diff> {\n\t/**\n\t * Whether this computed signal is involved in an actively-running effect graph.\n\t * Returns true if there are any reactions or other computed signals depending on this one.\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\treadonly __isComputed = true as const\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 tldraw/no-setter-getter\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(this.state, newState)) {\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\n/**\n * Singleton reference to the computed signal implementation class.\n * Used internally by the library to create computed signal instances.\n *\n * @internal\n */\nexport const _Computed = singleton('Computed', () => __UNSAFE__Computed)\n\n/**\n * Type alias for the computed signal implementation class.\n *\n * @internal\n */\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 that derives its value from other signals.\n * Computed signals automatically update when their dependencies change and use lazy evaluation\n * for optimal performance.\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 for debugging purposes\n * @param compute - The function that computes the value of the signal. Receives the previous value and last computed epoch\n * @param options - Optional configuration for the computed signal\n * @returns A new computed signal\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 * TC39 decorator for creating computed methods in classes.\n *\n * @example\n * ```ts\n * class MyClass {\n * value = atom('value', 10)\n *\n * @computed\n * doubled() {\n * return this.value.get() * 2\n * }\n * }\n * ```\n *\n * @param compute - The method to be decorated\n * @param context - The decorator context provided by TypeScript\n * @returns The decorated method\n * @public\n */\nexport function computed<This extends object, Value>(\n\tcompute: () => Value,\n\tcontext: ClassMethodDecoratorContext<This, () => Value>\n): () => Value\n/**\n * Legacy TypeScript decorator for creating computed methods in classes.\n *\n * @example\n * ```ts\n * class MyClass {\n * value = atom('value', 10)\n *\n * @computed\n * doubled() {\n * return this.value.get() * 2\n * }\n * }\n * ```\n *\n * @param target - The class prototype\n * @param key - The property key\n * @param descriptor - The property descriptor\n * @returns The modified property descriptor\n * @public\n */\nexport function computed(\n\ttarget: any,\n\tkey: string,\n\tdescriptor: PropertyDescriptor\n): PropertyDescriptor\n/**\n * Decorator factory for creating computed methods with options.\n *\n * @example\n * ```ts\n * class MyClass {\n * items = atom('items', [1, 2, 3])\n *\n * @computed({ historyLength: 10 })\n * sum() {\n * return this.items.get().reduce((a, b) => a + b, 0)\n * }\n * }\n * ```\n *\n * @param options - Configuration options for the computed signal\n * @returns A decorator function that can be applied to methods\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/**\n * Implementation function that handles all computed signal creation and decoration scenarios.\n * This function is overloaded to support multiple usage patterns:\n * - Creating computed signals directly\n * - Using as a TC39 decorator\n * - Using as a legacy decorator\n * - Using as a decorator factory with options\n *\n * @returns Either a computed signal instance or a decorator function depending on usage\n * @public\n */\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\nimport { isComputed as _isComputed } from './isComputed'\n\n/**\n * Returns true if the given value is a computed signal.\n * @public\n */\nexport function isComputed(value: any): value is Computed<any> {\n\treturn _isComputed(value)\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,mBAAuB;AACvB,sBAAyB;AACzB,qBAAgF;AAChF,uBAAmC;AACnC,qBAAmE;AACnE,2BAA8B;AAC9B,0BAAgE;AAChE,mBAAwD;AACxD,sBAAyC;
|
|
4
|
+
"sourcesContent": ["/* eslint-disable prefer-rest-params */\nimport { assert } from '@tldraw/utils'\nimport { ArraySet } from './ArraySet'\nimport { maybeCaptureParent, startCapturingParents, stopCapturingParents } from './capture'\nimport { GLOBAL_START_EPOCH } from './constants'\nimport { EMPTY_ARRAY, equals, haveParentsChanged, singleton } from './helpers'\nimport { HistoryBuffer } from './HistoryBuffer'\nimport { getGlobalEpoch, getIsReacting, getReactionEpoch } from './transactions'\nimport { Child, ComputeDiff, RESET_VALUE, Signal } from './types'\nimport { logComputedGetterWarning } from './warnings'\n\n/**\n * A special symbol used to indicate that a computed signal has not been initialized yet.\n * This is passed as the `previousValue` parameter to a computed signal function on its first run.\n *\n * @example\n * ```ts\n * const count = atom('count', 0)\n * const double = computed('double', (prevValue) => {\n * if (isUninitialized(prevValue)) {\n * console.log('First computation!')\n * }\n * return count.get() * 2\n * })\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 * This type represents the uninitialized state of a computed signal before its first calculation.\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/**\n * A singleton class used to wrap computed signal values along with their diffs.\n * This class is used internally by the {@link withDiff} function to provide both\n * the computed value and its diff to the signal system.\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 * })\n * ```\n *\n * @public\n */\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/**\n * Interface representing a value wrapped with its corresponding diff.\n * Used in incremental computation to provide both the new value and the diff from the previous value.\n *\n * @public\n */\nexport interface WithDiff<Value, Diff> {\n\t/**\n\t * The computed value.\n\t */\n\tvalue: Value\n\t/**\n\t * The diff between the previous and current value.\n\t */\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 configuring computed signals. Used when calling `computed` or using the `@computed` decorator.\n *\n * @example\n * ```ts\n * const greeting = computed('greeting', () => `Hello ${name.get()}!`, {\n * historyLength: 10,\n * isEqual: (a, b) => a === b,\n * computeDiff: (oldVal, newVal) => ({ type: 'change', from: oldVal, to: newVal })\n * })\n * ```\n *\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 diffs, leave this as `undefined` and no history buffer will be created. Diffs supplied via {@link withDiff} or {@link ComputedOptions.computeDiff} are only recorded when this is set.\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 the `computed` function or `@computed` decorator.\n * Computed signals derive their values from other signals and automatically update when their dependencies change.\n * They use lazy evaluation, only recalculating when accessed and dependencies have changed.\n *\n * @example\n * ```ts\n * const firstName = atom('firstName', 'John')\n * const lastName = atom('lastName', 'Doe')\n * const fullName = computed('fullName', () => `${firstName.get()} ${lastName.get()}`)\n *\n * console.log(fullName.get()) // \"John Doe\"\n * firstName.set('Jane')\n * console.log(fullName.get()) // \"Jane Doe\"\n * ```\n *\n * @public\n */\nexport interface Computed<Value, Diff = unknown> extends Signal<Value, Diff> {\n\t/**\n\t * Whether this computed signal is involved in an actively-running effect graph.\n\t * Returns true if there are any reactions or other computed signals depending on this one.\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\treadonly __isComputed = true as const\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 tldraw/no-setter-getter\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\t// `derive` may have advanced the epoch, so re-read it here, but only once \u2014 the whole\n\t\t\t// commit below belongs to a single epoch.\n\t\t\tconst epoch = getGlobalEpoch()\n\t\t\tif (isUninitialized || !this.isEqual(this.state, newState)) {\n\t\t\t\tif (this.historyBuffer && !isUninitialized) {\n\t\t\t\t\t// Only `undefined` means \"no diff supplied\"; `null` can be a legitimate diff.\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\tepoch,\n\t\t\t\t\t\tdiff !== undefined\n\t\t\t\t\t\t\t? diff\n\t\t\t\t\t\t\t: this.computeDiff\n\t\t\t\t\t\t\t\t? this.computeDiff(this.state, newState, this.lastCheckedEpoch, epoch)\n\t\t\t\t\t\t\t\t: RESET_VALUE\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tthis.lastChangedEpoch = epoch\n\t\t\t\tthis.state = newState\n\t\t\t}\n\t\t\tthis.error = null\n\t\t\tthis.lastCheckedEpoch = epoch\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\tconst epoch = getGlobalEpoch()\n\t\t\t// Entering the error state (from a value, or from never having computed) is a change;\n\t\t\t// throwing again while already in it is not. Checking `error` rather than `state` matters\n\t\t\t// for a first run that throws: `state` is already UNINITIALIZED then, and leaving\n\t\t\t// `lastChangedEpoch` at GLOBAL_START_EPOCH would keep `isNew` true and re-run `derive` on\n\t\t\t// every read instead of rethrowing the cached error.\n\t\t\tif (this.error === null) {\n\t\t\t\tthis.state = UNINITIALIZED as unknown as Value\n\t\t\t\tthis.lastChangedEpoch = epoch\n\t\t\t}\n\t\t\tthis.lastCheckedEpoch = epoch\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\n/**\n * Singleton reference to the computed signal implementation class.\n * Used internally by the library to create computed signal instances.\n *\n * @internal\n */\nexport const _Computed = singleton('Computed', () => __UNSAFE__Computed)\n\n/**\n * Type alias for the computed signal implementation class.\n *\n * @internal\n */\nexport type _Computed = InstanceType<typeof __UNSAFE__Computed>\n\n// Set on every decorated wrapper: the symbol its per-instance computed is stored under.\nconst derivationKeyKey = '@@__computedDerivationKey__@@'\n\n/**\n * Builds the method (or getter) body that `@computed` installs: a per-instance, lazily-created\n * computed over the original method. The storage symbol is unique per decoration rather than\n * `Symbol.for(name)` so that a subclass which overrides a `@computed` method and calls\n * `super.method()` reaches the superclass's computed instead of re-entering its own.\n */\nfunction makeComputedWrapper(name: string, compute: () => any, options: ComputedOptions<any, any>) {\n\tconst derivationKey = Symbol('__@tldraw/state__computed__' + name)\n\n\tconst wrapper = function (this: any) {\n\t\tlet d = this[derivationKey] as Computed<any> | undefined\n\n\t\tif (!d) {\n\t\t\td = new _Computed(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} as any\n\twrapper[derivationKeyKey] = derivationKey\n\treturn wrapper\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\tassert(context.kind === 'method', '@computed can only be used on methods')\n\t\treturn makeComputedWrapper(String(context.name), originalMethod, options)\n\t} else {\n\t\tconst [_target, key, descriptor] = args\n\t\tif (descriptor.get) {\n\t\t\tlogComputedGetterWarning()\n\t\t\tdescriptor.get = makeComputedWrapper(key, descriptor.get, options)\n\t\t} else {\n\t\t\tdescriptor.value = makeComputedWrapper(key, descriptor.value, options)\n\t\t}\n\t\treturn descriptor\n\t}\n}\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('count', 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] extends () => infer Value ? Value : Obj[Prop]> {\n\t// The nearest decorated wrapper (a method, or a legacy getter) on the prototype chain knows\n\t// which symbol the instance's computed is stored under.\n\tfor (let proto: any = obj; proto; proto = Object.getPrototypeOf(proto)) {\n\t\tconst descriptor = Object.getOwnPropertyDescriptor(proto, propertyName)\n\t\tconst wrapper = descriptor?.get ?? descriptor?.value\n\t\tconst key = wrapper?.[derivationKeyKey]\n\t\tif (!key) continue\n\n\t\tlet inst = (obj as any)[key]\n\t\tif (!inst) {\n\t\t\t// calling the wrapper creates the computed (before it first derives, so a throwing derive\n\t\t\t// still leaves the instance behind)\n\t\t\ttry {\n\t\t\t\twrapper.call(obj)\n\t\t\t} catch {\n\t\t\t\t// the caller asked for the instance, not its value\n\t\t\t}\n\t\t\tinst = (obj as any)[key]\n\t\t}\n\t\treturn inst\n\t}\n\treturn undefined as any\n}\n\n/**\n * Creates a computed signal that derives its value from other signals.\n * Computed signals automatically update when their dependencies change and use lazy evaluation\n * for optimal performance.\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('count', 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('count', 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 for debugging purposes\n * @param compute - The function that computes the value of the signal. Receives the previous value and last computed epoch\n * @param options - Optional configuration for the computed signal\n * @returns A new computed signal\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 * TC39 decorator for creating computed methods in classes.\n *\n * @example\n * ```ts\n * class MyClass {\n * value = atom('value', 10)\n *\n * @computed\n * doubled() {\n * return this.value.get() * 2\n * }\n * }\n * ```\n *\n * @param compute - The method to be decorated\n * @param context - The decorator context provided by TypeScript\n * @returns The decorated method\n * @public\n */\nexport function computed<This extends object, Value>(\n\tcompute: () => Value,\n\tcontext: ClassMethodDecoratorContext<This, () => Value>\n): () => Value\n/**\n * Legacy TypeScript decorator for creating computed methods in classes.\n *\n * @example\n * ```ts\n * class MyClass {\n * value = atom('value', 10)\n *\n * @computed\n * doubled() {\n * return this.value.get() * 2\n * }\n * }\n * ```\n *\n * @param target - The class prototype\n * @param key - The property key\n * @param descriptor - The property descriptor\n * @returns The modified property descriptor\n * @public\n */\nexport function computed(\n\ttarget: any,\n\tkey: string,\n\tdescriptor: PropertyDescriptor\n): PropertyDescriptor\n/**\n * Decorator factory for creating computed methods with options.\n *\n * @example\n * ```ts\n * class MyClass {\n * items = atom('items', [1, 2, 3])\n *\n * @computed({ historyLength: 10 })\n * sum() {\n * return this.items.get().reduce((a, b) => a + b, 0)\n * }\n * }\n * ```\n *\n * @param options - Configuration options for the computed signal\n * @returns A decorator function that can be applied to methods\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/**\n * Implementation function that handles all computed signal creation and decoration scenarios.\n * This function is overloaded to support multiple usage patterns:\n * - Creating computed signals directly\n * - Using as a TC39 decorator\n * - Using as a legacy decorator\n * - Using as a decorator factory with options\n *\n * @returns Either a computed signal instance or a decorator function depending on usage\n * @public\n */\nexport function computed() {\n\tif (arguments.length <= 1) {\n\t\t// decorator factory: `@computed(options)` or `@computed()`\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\nimport { isComputed as _isComputed } from './isComputed'\n\n/**\n * Returns true if the given value is a computed signal.\n * @public\n */\nexport function isComputed(value: any): value is Computed<any> {\n\treturn _isComputed(value)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["isUninitialized", "_isComputed"]
|
|
7
7
|
}
|
|
@@ -75,7 +75,7 @@ class __EffectScheduler__ {
|
|
|
75
75
|
maybeScheduleEffect() {
|
|
76
76
|
if (!this._isActivelyListening) return;
|
|
77
77
|
if (this.lastReactedEpoch === (0, import_transactions.getGlobalEpoch)()) return;
|
|
78
|
-
if (this.parents.length && !(0, import_helpers.haveParentsChanged)(this)) {
|
|
78
|
+
if ((this.lastReactedEpoch !== import_constants.GLOBAL_START_EPOCH || this.parents.length > 0) && !(0, import_helpers.haveParentsChanged)(this)) {
|
|
79
79
|
this.lastReactedEpoch = (0, import_transactions.getGlobalEpoch)();
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
@@ -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 void\n\t */\n\t// eslint-disable-next-line tldraw/method-signature-style\n\tscheduleEffect?: (execute: () => void) => void\n}\n\nclass __EffectScheduler__<Result> implements EffectScheduler<Result> {\n\treadonly __isEffectScheduler = true as const\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 tldraw/no-setter-getter\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 tldraw/no-setter-getter\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 tldraw/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": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAAyB;AACzB,qBAA4D;AAC5D,uBAAmC;AACnC,qBAA8D;AAC9D,0BAA+B;
|
|
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// eslint-disable-next-line tldraw/method-signature-style\n\tscheduleEffect?: (execute: () => void) => void\n}\n\nclass __EffectScheduler__<Result> implements EffectScheduler<Result> {\n\treadonly __isEffectScheduler = true as const\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 tldraw/no-setter-getter\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 tldraw/no-setter-getter\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// An effect that has run before (or captured parents before throwing) only needs to run\n\t\t// again if one of those parents changed; that includes an effect that captured no parents at\n\t\t// all. An effect that has never run always runs.\n\t\tif (\n\t\t\t(this.lastReactedEpoch !== GLOBAL_START_EPOCH || this.parents.length > 0) &&\n\t\t\t!haveParentsChanged(this)\n\t\t) {\n\t\t\tthis.lastReactedEpoch = getGlobalEpoch()\n\t\t\treturn\n\t\t}\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 tldraw/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
|
}
|
|
@@ -21,6 +21,7 @@ __export(HistoryBuffer_exports, {
|
|
|
21
21
|
HistoryBuffer: () => HistoryBuffer
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(HistoryBuffer_exports);
|
|
24
|
+
var import_helpers = require("./helpers");
|
|
24
25
|
var import_types = require("./types");
|
|
25
26
|
class HistoryBuffer {
|
|
26
27
|
/**
|
|
@@ -50,12 +51,14 @@ class HistoryBuffer {
|
|
|
50
51
|
/**
|
|
51
52
|
* Adds a diff entry to the history buffer, representing a change between two epochs.
|
|
52
53
|
*
|
|
53
|
-
* If the diff is
|
|
54
|
-
*
|
|
54
|
+
* If the diff is RESET_VALUE, or undefined (meaning no diff is available), the entire buffer is
|
|
55
|
+
* cleared to indicate that historical tracking should restart. Silently skipping an entry would
|
|
56
|
+
* leave a gap, and a later `getChangesSince` from before the gap would return an incomplete
|
|
57
|
+
* diff list rather than RESET_VALUE.
|
|
55
58
|
*
|
|
56
59
|
* @param lastComputedEpoch - The epoch when the previous value was computed
|
|
57
60
|
* @param currentEpoch - The epoch when the current value was computed
|
|
58
|
-
* @param diff - The diff representing the change, or RESET_VALUE to clear history
|
|
61
|
+
* @param diff - The diff representing the change, or RESET_VALUE / undefined to clear history
|
|
59
62
|
* @example
|
|
60
63
|
* ```ts
|
|
61
64
|
* const buffer = new HistoryBuffer<string>(5)
|
|
@@ -64,10 +67,7 @@ class HistoryBuffer {
|
|
|
64
67
|
* ```
|
|
65
68
|
*/
|
|
66
69
|
pushEntry(lastComputedEpoch, currentEpoch, diff) {
|
|
67
|
-
if (diff === void 0) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
if (diff === import_types.RESET_VALUE) {
|
|
70
|
+
if (diff === import_types.RESET_VALUE || diff === void 0) {
|
|
71
71
|
this.clear();
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
@@ -120,7 +120,7 @@ class HistoryBuffer {
|
|
|
120
120
|
}
|
|
121
121
|
const [fromEpoch, toEpoch] = elem;
|
|
122
122
|
if (i === 0 && sinceEpoch >= toEpoch) {
|
|
123
|
-
return
|
|
123
|
+
return import_helpers.EMPTY_ARRAY;
|
|
124
124
|
}
|
|
125
125
|
if (fromEpoch <= sinceEpoch && sinceEpoch < toEpoch) {
|
|
126
126
|
const len = i + 1;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/HistoryBuffer.ts"],
|
|
4
|
-
"sourcesContent": ["import { RESET_VALUE } from './types'\n\n/**\n * A tuple representing a range of epochs and the associated diff.\n * Used internally by HistoryBuffer to store change information.\n *\n * @internal\n */\ntype RangeTuple<Diff> = [fromEpoch: number, toEpoch: number, diff: Diff]\n\n/**\n * A circular buffer that stores diffs between sequential values of an atom or computed signal.\n * This enables efficient change tracking and history retrieval for features like undo/redo.\n *\n * The buffer uses a wrap-around strategy to maintain a fixed-size history of the most recent\n * changes, automatically overwriting older entries when the capacity is exceeded.\n *\n * @example\n * ```ts\n * const buffer = new HistoryBuffer<string>(5)\n * buffer.pushEntry(0, 1, 'first change')\n * buffer.pushEntry(1, 2, 'second change')\n * const changes = buffer.getChangesSince(0) // ['first change', 'second change']\n * ```\n *\n * @internal\n */\nexport class HistoryBuffer<Diff> {\n\t/**\n\t * Current write position in the circular buffer.\n\t * @internal\n\t */\n\tprivate index = 0\n\n\t/**\n\t * Circular buffer storing range tuples. Uses undefined to represent empty slots.\n\t * @internal\n\t */\n\tbuffer: Array<RangeTuple<Diff> | undefined>\n\n\t/**\n\t * Creates a new HistoryBuffer with the specified capacity.\n\t *\n\t * capacity - Maximum number of diffs to store in the buffer\n\t * @example\n\t * ```ts\n\t * const buffer = new HistoryBuffer<number>(10) // Store up to 10 diffs\n\t * ```\n\t */\n\tconstructor(private readonly capacity: number) {\n\t\tthis.buffer = new Array(capacity)\n\t}\n\n\t/**\n\t * Adds a diff entry to the history buffer, representing a change between two epochs.\n\t *\n\t * If the diff is
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA4B;
|
|
4
|
+
"sourcesContent": ["import { EMPTY_ARRAY } from './helpers'\nimport { RESET_VALUE } from './types'\n\n/**\n * A tuple representing a range of epochs and the associated diff.\n * Used internally by HistoryBuffer to store change information.\n *\n * @internal\n */\ntype RangeTuple<Diff> = [fromEpoch: number, toEpoch: number, diff: Diff]\n\n/**\n * A circular buffer that stores diffs between sequential values of an atom or computed signal.\n * This enables efficient change tracking and history retrieval for features like undo/redo.\n *\n * The buffer uses a wrap-around strategy to maintain a fixed-size history of the most recent\n * changes, automatically overwriting older entries when the capacity is exceeded.\n *\n * Entries must be contiguous (`entry[k].toEpoch === entry[k+1].fromEpoch`); `getChangesSince`\n * relies on that to find the entry covering an epoch.\n *\n * @example\n * ```ts\n * const buffer = new HistoryBuffer<string>(5)\n * buffer.pushEntry(0, 1, 'first change')\n * buffer.pushEntry(1, 2, 'second change')\n * const changes = buffer.getChangesSince(0) // ['first change', 'second change']\n * ```\n *\n * @internal\n */\nexport class HistoryBuffer<Diff> {\n\t/**\n\t * Current write position in the circular buffer.\n\t * @internal\n\t */\n\tprivate index = 0\n\n\t/**\n\t * Circular buffer storing range tuples. Uses undefined to represent empty slots.\n\t * @internal\n\t */\n\tbuffer: Array<RangeTuple<Diff> | undefined>\n\n\t/**\n\t * Creates a new HistoryBuffer with the specified capacity.\n\t *\n\t * capacity - Maximum number of diffs to store in the buffer\n\t * @example\n\t * ```ts\n\t * const buffer = new HistoryBuffer<number>(10) // Store up to 10 diffs\n\t * ```\n\t */\n\tconstructor(private readonly capacity: number) {\n\t\tthis.buffer = new Array(capacity)\n\t}\n\n\t/**\n\t * Adds a diff entry to the history buffer, representing a change between two epochs.\n\t *\n\t * If the diff is RESET_VALUE, or undefined (meaning no diff is available), the entire buffer is\n\t * cleared to indicate that historical tracking should restart. Silently skipping an entry would\n\t * leave a gap, and a later `getChangesSince` from before the gap would return an incomplete\n\t * diff list rather than RESET_VALUE.\n\t *\n\t * @param lastComputedEpoch - The epoch when the previous value was computed\n\t * @param currentEpoch - The epoch when the current value was computed\n\t * @param diff - The diff representing the change, or RESET_VALUE / undefined to clear history\n\t * @example\n\t * ```ts\n\t * const buffer = new HistoryBuffer<string>(5)\n\t * buffer.pushEntry(0, 1, 'added text')\n\t * buffer.pushEntry(1, 2, RESET_VALUE) // Clears the buffer\n\t * ```\n\t */\n\tpushEntry(lastComputedEpoch: number, currentEpoch: number, diff: Diff | RESET_VALUE | undefined) {\n\t\tif (diff === RESET_VALUE || diff === undefined) {\n\t\t\tthis.clear()\n\t\t\treturn\n\t\t}\n\n\t\t// Add the diff to the buffer as a range tuple.\n\t\tthis.buffer[this.index] = [lastComputedEpoch, currentEpoch, diff]\n\n\t\t// Bump the index, wrapping around if necessary.\n\t\tthis.index = (this.index + 1) % this.capacity\n\t}\n\n\t/**\n\t * Clears all entries from the history buffer and resets the write position.\n\t * This is called when a RESET_VALUE diff is encountered.\n\t *\n\t * @example\n\t * ```ts\n\t * const buffer = new HistoryBuffer<string>(5)\n\t * buffer.pushEntry(0, 1, 'change')\n\t * buffer.clear()\n\t * console.log(buffer.getChangesSince(0)) // RESET_VALUE\n\t * ```\n\t */\n\tclear() {\n\t\tthis.index = 0\n\t\tthis.buffer.fill(undefined)\n\t}\n\n\t/**\n\t * Retrieves all diffs that occurred since the specified epoch.\n\t *\n\t * The method searches backwards through the circular buffer to find changes\n\t * that occurred after the given epoch. If insufficient history is available\n\t * or the requested epoch is too old, returns RESET_VALUE indicating that\n\t * a complete state rebuild is required.\n\t *\n\t * @param sinceEpoch - The epoch from which to retrieve changes\n\t * @returns Array of diffs since the epoch, or RESET_VALUE if history is insufficient\n\t * @example\n\t * ```ts\n\t * const buffer = new HistoryBuffer<string>(5)\n\t * buffer.pushEntry(0, 1, 'first')\n\t * buffer.pushEntry(1, 2, 'second')\n\t * const changes = buffer.getChangesSince(0) // ['first', 'second']\n\t * const recentChanges = buffer.getChangesSince(1) // ['second']\n\t * const tooOld = buffer.getChangesSince(-100) // RESET_VALUE\n\t * ```\n\t */\n\tgetChangesSince(sinceEpoch: number): RESET_VALUE | Diff[] {\n\t\tconst { index, capacity, buffer } = this\n\n\t\t// For each item in the buffer...\n\t\tfor (let i = 0; i < capacity; i++) {\n\t\t\tconst offset = (index - 1 + capacity - i) % capacity\n\n\t\t\tconst elem = buffer[offset]\n\n\t\t\t// If there's no element in the offset position, return the reset value\n\t\t\tif (!elem) {\n\t\t\t\treturn RESET_VALUE\n\t\t\t}\n\n\t\t\tconst [fromEpoch, toEpoch] = elem\n\n\t\t\t// If the first element is already too early, bail\n\t\t\tif (i === 0 && sinceEpoch >= toEpoch) {\n\t\t\t\treturn EMPTY_ARRAY\n\t\t\t}\n\n\t\t\t// If the element is since the given epoch, return an array with all diffs from this element and all following elements\n\t\t\tif (fromEpoch <= sinceEpoch && sinceEpoch < toEpoch) {\n\t\t\t\tconst len = i + 1\n\t\t\t\tconst result = new Array(len)\n\n\t\t\t\tfor (let j = 0; j < len; j++) {\n\t\t\t\t\tresult[j] = buffer[(offset + j) % capacity]![2]\n\t\t\t\t}\n\n\t\t\t\treturn result\n\t\t\t}\n\t\t}\n\n\t\t// If we haven't returned yet, return the reset value\n\t\treturn RESET_VALUE\n\t}\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA4B;AAC5B,mBAA4B;AA8BrB,MAAM,cAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBhC,YAA6B,UAAkB;AAAlB;AAC5B,SAAK,SAAS,IAAI,MAAM,QAAQ;AAAA,EACjC;AAAA,EAF6B;AAAA;AAAA;AAAA;AAAA;AAAA,EAjBrB,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMhB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiCA,UAAU,mBAA2B,cAAsB,MAAsC;AAChG,QAAI,SAAS,4BAAe,SAAS,QAAW;AAC/C,WAAK,MAAM;AACX;AAAA,IACD;AAGA,SAAK,OAAO,KAAK,KAAK,IAAI,CAAC,mBAAmB,cAAc,IAAI;AAGhE,SAAK,SAAS,KAAK,QAAQ,KAAK,KAAK;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,QAAQ;AACP,SAAK,QAAQ;AACb,SAAK,OAAO,KAAK,MAAS;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBA,gBAAgB,YAA0C;AACzD,UAAM,EAAE,OAAO,UAAU,OAAO,IAAI;AAGpC,aAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AAClC,YAAM,UAAU,QAAQ,IAAI,WAAW,KAAK;AAE5C,YAAM,OAAO,OAAO,MAAM;AAG1B,UAAI,CAAC,MAAM;AACV,eAAO;AAAA,MACR;AAEA,YAAM,CAAC,WAAW,OAAO,IAAI;AAG7B,UAAI,MAAM,KAAK,cAAc,SAAS;AACrC,eAAO;AAAA,MACR;AAGA,UAAI,aAAa,cAAc,aAAa,SAAS;AACpD,cAAM,MAAM,IAAI;AAChB,cAAM,SAAS,IAAI,MAAM,GAAG;AAE5B,iBAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC7B,iBAAO,CAAC,IAAI,QAAQ,SAAS,KAAK,QAAQ,EAAG,CAAC;AAAA,QAC/C;AAEA,eAAO;AAAA,MACR;AAAA,IACD;AAGA,WAAO;AAAA,EACR;AACD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-cjs/lib/capture.js
CHANGED
|
@@ -86,11 +86,9 @@ function stopCapturingParents() {
|
|
|
86
86
|
}
|
|
87
87
|
function maybeCaptureParent(p) {
|
|
88
88
|
if (inst.stack) {
|
|
89
|
-
|
|
90
|
-
if (wasCapturedAlready) {
|
|
89
|
+
if (!inst.stack.child.parentSet.add(p)) {
|
|
91
90
|
return;
|
|
92
91
|
}
|
|
93
|
-
inst.stack.child.parentSet.add(p);
|
|
94
92
|
if (inst.stack.child.isActivelyListening) {
|
|
95
93
|
(0, import_helpers.attach)(p, inst.stack.child);
|
|
96
94
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/capture.ts"],
|
|
4
|
-
"sourcesContent": ["import { attach, detach, singleton } from './helpers'\nimport { isComputed } from './isComputed'\nimport type { Child, Signal } from './types'\n\nclass CaptureStackFrame {\n\toffset = 0\n\n\tmaybeRemoved?: Signal<any>[]\n\n\tconstructor(\n\t\tpublic readonly below: CaptureStackFrame | null,\n\t\tpublic readonly child: Child\n\t) {}\n}\n\nconst inst = singleton('capture', () => ({ stack: null as null | CaptureStackFrame }))\n\n/**\n * Executes the given function without capturing any parents in the current capture context.\n *\n * This is mainly useful if you want to run an effect only when certain signals change while also\n * dereferencing other signals which should not cause the effect to rerun on their own.\n *\n * @example\n * ```ts\n * const name = atom('name', 'Sam')\n * const time = atom('time',
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA0C;AAC1C,wBAA2B;AAG3B,MAAM,kBAAkB;AAAA,EAKvB,YACiB,OACA,OACf;AAFe;AACA;AAAA,EACd;AAAA,EAFc;AAAA,EACA;AAAA,EANjB,SAAS;AAAA,EAET;AAMD;AAEA,MAAM,WAAO,0BAAU,WAAW,OAAO,EAAE,OAAO,KAAiC,EAAE;AAyB9E,SAAS,uBAA0B,IAAgB;AACzD,QAAM,WAAW,KAAK;AACtB,OAAK,QAAQ;AACb,MAAI;AACH,WAAO,GAAG;AAAA,EACX,UAAE;AACD,SAAK,QAAQ;AAAA,EACd;AACD;AAqBO,SAAS,sBAAsB,OAAc;AACnD,OAAK,QAAQ,IAAI,kBAAkB,KAAK,OAAO,KAAK;AACpD,MAAI,MAAM,2BAA2B;AACpC,UAAM,yBAAyB,MAAM;AACrC,UAAM,4BAA4B;AAClC,eAAW,KAAK,MAAM,SAAS;AAC9B,QAAE,4BAA4B,IAAI;AAAA,IACnC;AACA,wBAAoB,OAAO,sBAAsB;AAAA,EAClD;AACA,QAAM,UAAU,MAAM;AACvB;AAmBO,SAAS,uBAAuB;AACtC,QAAM,QAAQ,KAAK;AACnB,OAAK,QAAQ,MAAM;AAEnB,MAAI,MAAM,SAAS,MAAM,MAAM,QAAQ,QAAQ;AAC9C,aAAS,IAAI,MAAM,QAAQ,IAAI,MAAM,MAAM,QAAQ,QAAQ,KAAK;AAC/D,YAAM,qBAAqB,MAAM,MAAM,QAAQ,CAAC;AAChD,UAAI,CAAC,MAAM,MAAM,UAAU,IAAI,kBAAkB,GAAG;AACnD,mCAAO,oBAAoB,MAAM,KAAK;AAAA,MACvC;AAAA,IACD;AAEA,UAAM,MAAM,QAAQ,SAAS,MAAM;AACnC,UAAM,MAAM,aAAa,SAAS,MAAM;AAAA,EACzC;AAEA,MAAI,MAAM,cAAc;AACvB,aAAS,IAAI,GAAG,IAAI,MAAM,aAAa,QAAQ,KAAK;AACnD,YAAM,qBAAqB,MAAM,aAAa,CAAC;AAC/C,UAAI,CAAC,MAAM,MAAM,UAAU,IAAI,kBAAkB,GAAG;AACnD,mCAAO,oBAAoB,MAAM,KAAK;AAAA,MACvC;AAAA,IACD;AAAA,EACD;AAEA,MAAI,MAAM,MAAM,2BAA2B;AAC1C,0BAAsB,MAAM,OAAO,MAAM,MAAM,yBAAyB;AAAA,EACzE;AACD;AAsBO,SAAS,mBAAmB,GAAqB;AACvD,MAAI,KAAK,OAAO;
|
|
4
|
+
"sourcesContent": ["import { attach, detach, singleton } from './helpers'\nimport { isComputed } from './isComputed'\nimport type { Child, Signal } from './types'\n\nclass CaptureStackFrame {\n\toffset = 0\n\n\tmaybeRemoved?: Signal<any>[]\n\n\tconstructor(\n\t\tpublic readonly below: CaptureStackFrame | null,\n\t\tpublic readonly child: Child\n\t) {}\n}\n\nconst inst = singleton('capture', () => ({ stack: null as null | CaptureStackFrame }))\n\n/**\n * Executes the given function without capturing any parents in the current capture context.\n *\n * This is mainly useful if you want to run an effect only when certain signals change while also\n * dereferencing other signals which should not cause the effect to rerun on their own.\n *\n * @example\n * ```ts\n * const name = atom('name', 'Sam')\n * const time = atom('time', Date.now())\n *\n * setInterval(() => {\n * time.set(Date.now())\n * })\n *\n * react('log name changes', () => {\n * \t print(name.get(), 'was changed at', unsafe__withoutCapture(() => time.get()))\n * })\n *\n * ```\n *\n * @public\n */\nexport function unsafe__withoutCapture<T>(fn: () => T): T {\n\tconst oldStack = inst.stack\n\tinst.stack = null\n\ttry {\n\t\treturn fn()\n\t} finally {\n\t\tinst.stack = oldStack\n\t}\n}\n\n/**\n * Begins capturing parent signal dependencies for the given child signal.\n *\n * This function initiates a capture session where any signal accessed via `.get()`\n * will be automatically registered as a dependency of the child signal. It sets up\n * the capture stack frame and clears the existing parent set to prepare for fresh\n * dependency tracking.\n *\n * @param child - The child signal (computed or effect) that will capture dependencies\n *\n * @example\n * ```ts\n * const effect = createEffect('myEffect', () => { /* ... *\\/ })\n * startCapturingParents(effect)\n * // Now any signal.get() calls will be captured as dependencies\n * ```\n *\n * @internal\n */\nexport function startCapturingParents(child: Child) {\n\tinst.stack = new CaptureStackFrame(inst.stack, child)\n\tif (child.__debug_ancestor_epochs__) {\n\t\tconst previousAncestorEpochs = child.__debug_ancestor_epochs__\n\t\tchild.__debug_ancestor_epochs__ = null\n\t\tfor (const p of child.parents) {\n\t\t\tp.__unsafe__getWithoutCapture(true)\n\t\t}\n\t\tlogChangedAncestors(child, previousAncestorEpochs)\n\t}\n\tchild.parentSet.clear()\n}\n\n/**\n * Completes the parent dependency capture session and finalizes the dependency graph.\n *\n * This function cleans up the capture session by removing dependencies that are no\n * longer needed, detaching signals that should no longer be parents, and updating\n * the dependency arrays to reflect the current set of captured parents. It must be\n * called after `startCapturingParents` to complete the capture cycle.\n *\n * @example\n * ```ts\n * startCapturingParents(effect)\n * // ... signal.get() calls happen here ...\n * stopCapturingParents() // Finalizes the dependency graph\n * ```\n *\n * @internal\n */\nexport function stopCapturingParents() {\n\tconst frame = inst.stack!\n\tinst.stack = frame.below\n\n\tif (frame.offset < frame.child.parents.length) {\n\t\tfor (let i = frame.offset; i < frame.child.parents.length; i++) {\n\t\t\tconst maybeRemovedParent = frame.child.parents[i]\n\t\t\tif (!frame.child.parentSet.has(maybeRemovedParent)) {\n\t\t\t\tdetach(maybeRemovedParent, frame.child)\n\t\t\t}\n\t\t}\n\n\t\tframe.child.parents.length = frame.offset\n\t\tframe.child.parentEpochs.length = frame.offset\n\t}\n\n\tif (frame.maybeRemoved) {\n\t\tfor (let i = 0; i < frame.maybeRemoved.length; i++) {\n\t\t\tconst maybeRemovedParent = frame.maybeRemoved[i]\n\t\t\tif (!frame.child.parentSet.has(maybeRemovedParent)) {\n\t\t\t\tdetach(maybeRemovedParent, frame.child)\n\t\t\t}\n\t\t}\n\t}\n\n\tif (frame.child.__debug_ancestor_epochs__) {\n\t\tcaptureAncestorEpochs(frame.child, frame.child.__debug_ancestor_epochs__)\n\t}\n}\n\n/**\n * Conditionally captures a signal as a parent dependency during an active capture session.\n *\n * This function is called whenever a signal's `.get()` method is invoked during a\n * capture session. It checks if the signal should be added as a dependency and manages\n * the parent-child relationship in the reactive graph. The function handles deduplication,\n * attachment/detachment, and tracks changes in dependency order.\n *\n * Note: This must be called after the parent signal is up to date.\n *\n * @param p - The signal that might be captured as a parent dependency\n *\n * @example\n * ```ts\n * // This is called internally when you do:\n * const value = someAtom.get() // maybeCaptureParent(someAtom) is called\n * ```\n *\n * @internal\n */\nexport function maybeCaptureParent(p: Signal<any, any>) {\n\tif (inst.stack) {\n\t\t// `add` returns false when the parent was already captured this run. In array mode both\n\t\t// `has` and `add` scan with indexOf, so going straight to `add` halves the scans per\n\t\t// captured parent.\n\t\tif (!inst.stack.child.parentSet.add(p)) {\n\t\t\treturn\n\t\t}\n\n\t\tif (inst.stack.child.isActivelyListening) {\n\t\t\tattach(p, inst.stack.child)\n\t\t}\n\n\t\t// Parents are recorded in deref order. If the slot at this offset held a different parent\n\t\t// last run, that parent may have moved later in the order or been dropped; only\n\t\t// stopCapturingParents can tell, so remember it for then.\n\t\tif (inst.stack.offset < inst.stack.child.parents.length) {\n\t\t\tconst maybeRemovedParent = inst.stack.child.parents[inst.stack.offset]\n\t\t\tif (maybeRemovedParent !== p) {\n\t\t\t\tif (!inst.stack.maybeRemoved) {\n\t\t\t\t\tinst.stack.maybeRemoved = [maybeRemovedParent]\n\t\t\t\t} else {\n\t\t\t\t\tinst.stack.maybeRemoved.push(maybeRemovedParent)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tinst.stack.child.parents[inst.stack.offset] = p\n\t\tinst.stack.child.parentEpochs[inst.stack.offset] = p.lastChangedEpoch\n\t\tinst.stack.offset++\n\t}\n}\n\n/**\n * A debugging tool that tells you why a computed signal or effect is running.\n * Call in the body of a computed signal or effect function. Nothing is logged for the run that\n * calls it; from the next run on, each run logs the ancestors that changed.\n *\n * @example\n * ```ts\n * const name = atom('name', 'Bob')\n * react('greeting', () => {\n * \twhyAmIRunning()\n *\tprint('Hello', name.get())\n * })\n *\n * name.set('Alice')\n *\n * // Effect(greeting) is executing because:\n * // \u21B3 Atom(name) changed\n * ```\n *\n * @public\n */\nexport function whyAmIRunning() {\n\tconst child = inst.stack?.child\n\tif (!child) {\n\t\tthrow new Error('whyAmIRunning() called outside of a reactive context')\n\t}\n\tchild.__debug_ancestor_epochs__ = new Map()\n}\n\nfunction captureAncestorEpochs(child: Child, ancestorEpochs: Map<Signal<any>, number>) {\n\tfor (let i = 0; i < child.parents.length; i++) {\n\t\tconst parent = child.parents[i]\n\t\tconst epoch = child.parentEpochs[i]\n\t\tancestorEpochs.set(parent, epoch)\n\t\tif (isComputed(parent)) {\n\t\t\tcaptureAncestorEpochs(parent as any, ancestorEpochs)\n\t\t}\n\t}\n\treturn ancestorEpochs\n}\n\ntype ChangeTree = { [signalName: string]: ChangeTree } | null\nfunction collectChangedAncestors(\n\tchild: Child,\n\tancestorEpochs: Map<Signal<any>, number>\n): NonNullable<ChangeTree> {\n\tconst changeTree: ChangeTree = {}\n\tfor (let i = 0; i < child.parents.length; i++) {\n\t\tconst parent = child.parents[i]\n\t\tif (!ancestorEpochs.has(parent)) {\n\t\t\tcontinue\n\t\t}\n\t\tconst prevEpoch = ancestorEpochs.get(parent)\n\t\tconst currentEpoch = parent.lastChangedEpoch\n\t\tif (currentEpoch !== prevEpoch) {\n\t\t\tif (isComputed(parent)) {\n\t\t\t\tchangeTree[parent.name] = collectChangedAncestors(parent as any, ancestorEpochs)\n\t\t\t} else {\n\t\t\t\tchangeTree[parent.name] = null\n\t\t\t}\n\t\t}\n\t}\n\treturn changeTree\n}\n\nfunction logChangedAncestors(child: Child, ancestorEpochs: Map<Signal<any>, number>) {\n\tconst changeTree = collectChangedAncestors(child, ancestorEpochs)\n\tif (Object.keys(changeTree).length === 0) {\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(`Effect(${child.name}) was executed manually.`)\n\t\treturn\n\t}\n\n\tlet str = isComputed(child)\n\t\t? `Computed(${child.name}) is recomputing because:`\n\t\t: `Effect(${child.name}) is executing because:`\n\n\tfunction logParent(tree: NonNullable<ChangeTree>, indent: number) {\n\t\tconst indentStr = '\\n' + ' '.repeat(indent) + '\u21B3 '\n\t\tfor (const [name, val] of Object.entries(tree)) {\n\t\t\tif (val) {\n\t\t\t\tstr += `${indentStr}Computed(${name}) changed`\n\t\t\t\tlogParent(val, indent + 2)\n\t\t\t} else {\n\t\t\t\tstr += `${indentStr}Atom(${name}) changed`\n\t\t\t}\n\t\t}\n\t}\n\n\tlogParent(changeTree, 1)\n\n\t// eslint-disable-next-line no-console\n\tconsole.log(str)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/constants.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n *
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
4
|
+
"sourcesContent": ["/**\n * Sentinel below any real epoch. Derived signals and effects start with their epochs set to this\n * so they are dirty until their first run; real epochs start at `GLOBAL_START_EPOCH + 1`.\n *\n * @internal\n */\nexport const GLOBAL_START_EPOCH = -1\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAMO,MAAM,qBAAqB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-cjs/lib/helpers.js
CHANGED
|
@@ -22,7 +22,6 @@ __export(helpers_exports, {
|
|
|
22
22
|
attach: () => attach,
|
|
23
23
|
detach: () => detach,
|
|
24
24
|
equals: () => equals,
|
|
25
|
-
hasReactors: () => hasReactors,
|
|
26
25
|
haveParentsChanged: () => haveParentsChanged,
|
|
27
26
|
singleton: () => singleton
|
|
28
27
|
});
|
|
@@ -32,8 +31,9 @@ function isChild(x) {
|
|
|
32
31
|
}
|
|
33
32
|
function haveParentsChanged(child) {
|
|
34
33
|
for (let i = 0, n = child.parents.length; i < n; i++) {
|
|
35
|
-
child.parents[i]
|
|
36
|
-
|
|
34
|
+
const parent = child.parents[i];
|
|
35
|
+
parent.__unsafe__getWithoutCapture(true);
|
|
36
|
+
if (parent.lastChangedEpoch !== child.parentEpochs[i]) {
|
|
37
37
|
return true;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -70,12 +70,4 @@ function singleton(key, init) {
|
|
|
70
70
|
return global[symbol];
|
|
71
71
|
}
|
|
72
72
|
const EMPTY_ARRAY = singleton("empty_array", () => Object.freeze([]));
|
|
73
|
-
function hasReactors(signal) {
|
|
74
|
-
for (const child of signal.children) {
|
|
75
|
-
if (child.isActivelyListening) {
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
73
|
//# sourceMappingURL=helpers.js.map
|