Use a global singleton for tlstate (#2322)
One minor issue with signia is that it uses global state for bookkeeping, so it is potentially disastrous if there is more than one version of it included in a bundle. To prevent that being an issue before we had a warning that would trigger if signia detects multiple initializations. > Multiple versions of @tldraw/state detected. This will cause unexpected behavior. Please add "resolutions" (yarn/pnpm) or "overrides" (npm) in your package.json to ensure only one version of @tldraw/state is loaded. Alas I think this warning triggers too often in development environments, e.g. during HMR or janky bundlers. Something that can prevent the need for this particular warning is having a global singleton version of signia that we only instantiate once, and then re-use that one on subsequent module initializations. We didn't do this before because it has a few downsides: - breaks HMR if you are working on signia itself, since updated modules won't be used and you'll need to do a full refresh. - introduces the possibility of breakage if we remove or even add APIs to signia. We can't rely on having the latest version of signia be the first to instantiate, and we can't allow later instantiations to take precedence since atoms n stuff may have already been created with the prior version. To mitigate this I've introduced a `apiVersion` const that we can increment when we make any kind of additions or removals. If there is a mismatch between the `apiVersion` in the global singleton vs the currently-initializing module, then it throws. Ultimately i think the pros outweigh the cons here, i.e. far fewer people will see and have to deal with the error message shown above, and fewer people should encounter a situation where the editor appears to load but nothing changes when you interact with it. ### Change Type - [x] `patch` — Bug fix [^1]: publishes a `patch` release, for devDependencies use `internal` [^2]: will not publish a new version ### Release Notes - Make a global singleton for tlstate.
This commit is contained in:
parent
e8761c8e51
commit
b133c59391
10 changed files with 821 additions and 1500 deletions
|
@ -14,10 +14,26 @@ export interface Atom<Value, Diff = unknown> extends Signal<Value, Diff> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export function atom<Value, Diff = unknown>(
|
export const
|
||||||
name: string,
|
/**
|
||||||
initialValue: Value,
|
* Creates a new [[Atom]].
|
||||||
options?: AtomOptions<Value, Diff>): Atom<Value, Diff>;
|
*
|
||||||
|
* An Atom is a signal that can be updated directly by calling [[Atom.set]] or [[Atom.update]].
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const name = atom('name', 'John')
|
||||||
|
*
|
||||||
|
* name.get() // 'John'
|
||||||
|
*
|
||||||
|
* name.set('Jane')
|
||||||
|
*
|
||||||
|
* name.get() // 'Jane'
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
atom: typeof atom_2;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export interface AtomOptions<Value, Diff> {
|
export interface AtomOptions<Value, Diff> {
|
||||||
|
@ -36,13 +52,26 @@ export interface Computed<Value, Diff = unknown> extends Signal<Value, Diff> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export function computed<Value, Diff = unknown>(name: string, compute: (previousValue: typeof UNINITIALIZED | Value, lastComputedEpoch: number) => Value | WithDiff<Value, Diff>, options?: ComputedOptions<Value, Diff>): Computed<Value, Diff>;
|
export const
|
||||||
|
/**
|
||||||
// @public (undocumented)
|
* Creates a new [[Atom]].
|
||||||
export function computed(target: any, key: string, descriptor: PropertyDescriptor): PropertyDescriptor;
|
*
|
||||||
|
* An Atom is a signal that can be updated directly by calling [[Atom.set]] or [[Atom.update]].
|
||||||
// @public (undocumented)
|
*
|
||||||
export function computed<Value, Diff = unknown>(options?: ComputedOptions<Value, Diff>): (target: any, key: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const name = atom('name', 'John')
|
||||||
|
*
|
||||||
|
* name.get() // 'John'
|
||||||
|
*
|
||||||
|
* name.set('Jane')
|
||||||
|
*
|
||||||
|
* name.get() // 'Jane'
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
computed: typeof computed_2;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export interface ComputedOptions<Value, Diff> {
|
export interface ComputedOptions<Value, Diff> {
|
||||||
|
@ -52,48 +81,124 @@ export interface ComputedOptions<Value, Diff> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export class EffectScheduler<Result> {
|
export const
|
||||||
constructor(name: string, runEffect: (lastReactedEpoch: number) => Result, options?: EffectSchedulerOptions);
|
/**
|
||||||
attach(): void;
|
* Creates a new [[Atom]].
|
||||||
detach(): void;
|
*
|
||||||
execute(): Result;
|
* An Atom is a signal that can be updated directly by calling [[Atom.set]] or [[Atom.update]].
|
||||||
get isActivelyListening(): boolean;
|
*
|
||||||
// @internal (undocumented)
|
* @example
|
||||||
lastTraversedEpoch: number;
|
* ```ts
|
||||||
// @internal (undocumented)
|
* const name = atom('name', 'John')
|
||||||
maybeScheduleEffect(): void;
|
*
|
||||||
// (undocumented)
|
* name.get() // 'John'
|
||||||
readonly name: string;
|
*
|
||||||
// @internal (undocumented)
|
* name.set('Jane')
|
||||||
parentEpochs: number[];
|
*
|
||||||
// @internal (undocumented)
|
* name.get() // 'Jane'
|
||||||
parents: Signal<any, any>[];
|
* ```
|
||||||
get scheduleCount(): number;
|
*
|
||||||
// @internal (undocumented)
|
* @public
|
||||||
scheduleEffect(): void;
|
*/
|
||||||
}
|
EffectScheduler: typeof EffectScheduler_2;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export const EMPTY_ARRAY: [];
|
export const
|
||||||
|
/**
|
||||||
|
* Creates a new [[Atom]].
|
||||||
|
*
|
||||||
|
* An Atom is a signal that can be updated directly by calling [[Atom.set]] or [[Atom.update]].
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const name = atom('name', 'John')
|
||||||
|
*
|
||||||
|
* name.get() // 'John'
|
||||||
|
*
|
||||||
|
* name.set('Jane')
|
||||||
|
*
|
||||||
|
* name.get() // 'Jane'
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
EMPTY_ARRAY: [];
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export function getComputedInstance<Obj extends object, Prop extends keyof Obj>(obj: Obj, propertyName: Prop): Computed<Obj[Prop]>;
|
export function getComputedInstance<Obj extends object, Prop extends keyof Obj>(obj: Obj, propertyName: Prop): Computed<Obj[Prop]>;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export function isAtom(value: unknown): value is Atom<unknown>;
|
export const
|
||||||
|
/**
|
||||||
|
* Creates a new [[Atom]].
|
||||||
|
*
|
||||||
|
* An Atom is a signal that can be updated directly by calling [[Atom.set]] or [[Atom.update]].
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const name = atom('name', 'John')
|
||||||
|
*
|
||||||
|
* name.get() // 'John'
|
||||||
|
*
|
||||||
|
* name.set('Jane')
|
||||||
|
*
|
||||||
|
* name.get() // 'Jane'
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
isAtom: typeof isAtom_2;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export function isSignal(value: any): value is Signal<any>;
|
export const
|
||||||
|
/**
|
||||||
|
* Creates a new [[Atom]].
|
||||||
|
*
|
||||||
|
* An Atom is a signal that can be updated directly by calling [[Atom.set]] or [[Atom.update]].
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const name = atom('name', 'John')
|
||||||
|
*
|
||||||
|
* name.get() // 'John'
|
||||||
|
*
|
||||||
|
* name.set('Jane')
|
||||||
|
*
|
||||||
|
* name.get() // 'Jane'
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
isSignal: typeof isSignal_2;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export const isUninitialized: (value: any) => value is typeof UNINITIALIZED;
|
export const isUninitialized: (value: any) => value is typeof UNINITIALIZED;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export function react(name: string, fn: (lastReactedEpoch: number) => any, options?: EffectSchedulerOptions): () => void;
|
export const
|
||||||
|
/**
|
||||||
|
* Creates a new [[Atom]].
|
||||||
|
*
|
||||||
|
* An Atom is a signal that can be updated directly by calling [[Atom.set]] or [[Atom.update]].
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const name = atom('name', 'John')
|
||||||
|
*
|
||||||
|
* name.get() // 'John'
|
||||||
|
*
|
||||||
|
* name.set('Jane')
|
||||||
|
*
|
||||||
|
* name.get() // 'Jane'
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
react: typeof react_2;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export interface Reactor<T = unknown> {
|
export interface Reactor<T = unknown> {
|
||||||
scheduler: EffectScheduler<T>;
|
scheduler: EffectScheduler_2<T>;
|
||||||
start(options?: {
|
start(options?: {
|
||||||
force?: boolean;
|
force?: boolean;
|
||||||
}): void;
|
}): void;
|
||||||
|
@ -101,7 +206,26 @@ export interface Reactor<T = unknown> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export function reactor<Result>(name: string, fn: (lastReactedEpoch: number) => Result, options?: EffectSchedulerOptions): Reactor<Result>;
|
export const
|
||||||
|
/**
|
||||||
|
* Creates a new [[Atom]].
|
||||||
|
*
|
||||||
|
* An Atom is a signal that can be updated directly by calling [[Atom.set]] or [[Atom.update]].
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const name = atom('name', 'John')
|
||||||
|
*
|
||||||
|
* name.get() // 'John'
|
||||||
|
*
|
||||||
|
* name.set('Jane')
|
||||||
|
*
|
||||||
|
* name.get() // 'Jane'
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
reactor: typeof reactor_2;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export const RESET_VALUE: unique symbol;
|
export const RESET_VALUE: unique symbol;
|
||||||
|
@ -126,13 +250,70 @@ export interface Signal<Value, Diff = unknown> {
|
||||||
export function track<T extends FunctionComponent<any>>(baseComponent: T): T extends React_2.MemoExoticComponent<any> ? T : React_2.MemoExoticComponent<T>;
|
export function track<T extends FunctionComponent<any>>(baseComponent: T): T extends React_2.MemoExoticComponent<any> ? T : React_2.MemoExoticComponent<T>;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export function transact<T>(fn: () => T): T;
|
export const
|
||||||
|
/**
|
||||||
|
* Creates a new [[Atom]].
|
||||||
|
*
|
||||||
|
* An Atom is a signal that can be updated directly by calling [[Atom.set]] or [[Atom.update]].
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const name = atom('name', 'John')
|
||||||
|
*
|
||||||
|
* name.get() // 'John'
|
||||||
|
*
|
||||||
|
* name.set('Jane')
|
||||||
|
*
|
||||||
|
* name.get() // 'Jane'
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
transact: typeof transact_2;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export function transaction<T>(fn: (rollback: () => void) => T): T;
|
export const
|
||||||
|
/**
|
||||||
|
* Creates a new [[Atom]].
|
||||||
|
*
|
||||||
|
* An Atom is a signal that can be updated directly by calling [[Atom.set]] or [[Atom.update]].
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const name = atom('name', 'John')
|
||||||
|
*
|
||||||
|
* name.get() // 'John'
|
||||||
|
*
|
||||||
|
* name.set('Jane')
|
||||||
|
*
|
||||||
|
* name.get() // 'Jane'
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
transaction: typeof transaction_2;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export function unsafe__withoutCapture<T>(fn: () => T): T;
|
export const
|
||||||
|
/**
|
||||||
|
* Creates a new [[Atom]].
|
||||||
|
*
|
||||||
|
* An Atom is a signal that can be updated directly by calling [[Atom.set]] or [[Atom.update]].
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const name = atom('name', 'John')
|
||||||
|
*
|
||||||
|
* name.get() // 'John'
|
||||||
|
*
|
||||||
|
* name.set('Jane')
|
||||||
|
*
|
||||||
|
* name.get() // 'Jane'
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
unsafe__withoutCapture: typeof unsafe__withoutCapture_2;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export function useAtom<Value, Diff = unknown>(
|
export function useAtom<Value, Diff = unknown>(
|
||||||
|
@ -162,10 +343,48 @@ export function useValue<Value>(value: Signal<Value>): Value;
|
||||||
export function useValue<Value>(name: string, fn: () => Value, deps: unknown[]): Value;
|
export function useValue<Value>(name: string, fn: () => Value, deps: unknown[]): Value;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export function whyAmIRunning(): void;
|
export const
|
||||||
|
/**
|
||||||
|
* Creates a new [[Atom]].
|
||||||
|
*
|
||||||
|
* An Atom is a signal that can be updated directly by calling [[Atom.set]] or [[Atom.update]].
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const name = atom('name', 'John')
|
||||||
|
*
|
||||||
|
* name.get() // 'John'
|
||||||
|
*
|
||||||
|
* name.set('Jane')
|
||||||
|
*
|
||||||
|
* name.get() // 'Jane'
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
whyAmIRunning: typeof whyAmIRunning_2;
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export function withDiff<Value, Diff>(value: Value, diff: Diff): WithDiff<Value, Diff>;
|
export const
|
||||||
|
/**
|
||||||
|
* Creates a new [[Atom]].
|
||||||
|
*
|
||||||
|
* An Atom is a signal that can be updated directly by calling [[Atom.set]] or [[Atom.update]].
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const name = atom('name', 'John')
|
||||||
|
*
|
||||||
|
* name.get() // 'John'
|
||||||
|
*
|
||||||
|
* name.set('Jane')
|
||||||
|
*
|
||||||
|
* name.get() // 'Jane'
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
withDiff: typeof withDiff_2;
|
||||||
|
|
||||||
// (No @packageDocumentation comment for this package)
|
// (No @packageDocumentation comment for this package)
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -162,24 +162,6 @@ export class _Atom<Value, Diff = unknown> implements Atom<Value, Diff> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new [[Atom]].
|
|
||||||
*
|
|
||||||
* An Atom is a signal that can be updated directly by calling [[Atom.set]] or [[Atom.update]].
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const name = atom('name', 'John')
|
|
||||||
*
|
|
||||||
* name.get() // 'John'
|
|
||||||
*
|
|
||||||
* name.set('Jane')
|
|
||||||
*
|
|
||||||
* name.get() // 'Jane'
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export function atom<Value, Diff = unknown>(
|
export function atom<Value, Diff = unknown>(
|
||||||
/**
|
/**
|
||||||
* A name for the signal. This is used for debugging and profiling purposes, it does not need to be unique.
|
* A name for the signal. This is used for debugging and profiling purposes, it does not need to be unique.
|
||||||
|
@ -197,10 +179,6 @@ export function atom<Value, Diff = unknown>(
|
||||||
return new _Atom(name, initialValue, options)
|
return new _Atom(name, initialValue, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the given value is an [[Atom]].
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export function isAtom(value: unknown): value is Atom<unknown> {
|
export function isAtom(value: unknown): value is Atom<unknown> {
|
||||||
return value instanceof _Atom
|
return value instanceof _Atom
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,17 @@ import { globalEpoch } from './transactions'
|
||||||
import { Child, ComputeDiff, RESET_VALUE, Signal } from './types'
|
import { Child, ComputeDiff, RESET_VALUE, Signal } from './types'
|
||||||
import { logComputedGetterWarning, logDotValueWarning } from './warnings'
|
import { logComputedGetterWarning, logDotValueWarning } from './warnings'
|
||||||
|
|
||||||
const UNINITIALIZED = Symbol('UNINITIALIZED')
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export const UNINITIALIZED = Symbol.for('com.tldraw.state/UNINITIALIZED')
|
||||||
/**
|
/**
|
||||||
* The type of the first value passed to a computed signal function as the 'prevValue' parameter.
|
* The type of the first value passed to a computed signal function as the 'prevValue' parameter.
|
||||||
*
|
*
|
||||||
* @see [[isUninitialized]].
|
* @see [[isUninitialized]].
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
type UNINITIALIZED = typeof UNINITIALIZED
|
export type UNINITIALIZED = typeof UNINITIALIZED
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call this inside a computed signal function to determine whether it is the first time the function is being called.
|
* Call this inside a computed signal function to determine whether it is the first time the function is being called.
|
||||||
|
@ -44,28 +47,6 @@ class WithDiff<Value, Diff> {
|
||||||
constructor(public value: Value, public diff: Diff) {}
|
constructor(public value: Value, public diff: Diff) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* When writing incrementally-computed signals it is convenient (and usually more performant) to incrementally compute the diff too.
|
|
||||||
*
|
|
||||||
* 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 [[AtomOptions.computeDiff]].
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const count = atom('count', 0)
|
|
||||||
* const double = computed('double', (prevValue) => {
|
|
||||||
* const nextValue = count.get() * 2
|
|
||||||
* if (isUninitialized(prevValue)) {
|
|
||||||
* return nextValue
|
|
||||||
* }
|
|
||||||
* return withDiff(nextValue, nextValue - prevValue)
|
|
||||||
* }, { historyLength: 10 })
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param value - The value.
|
|
||||||
* @param diff - The diff.
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export function withDiff<Value, Diff>(value: Value, diff: Diff): WithDiff<Value, Diff> {
|
export function withDiff<Value, Diff>(value: Value, diff: Diff): WithDiff<Value, Diff> {
|
||||||
return new WithDiff(value, diff)
|
return new WithDiff(value, diff)
|
||||||
}
|
}
|
||||||
|
@ -347,51 +328,6 @@ export function getComputedInstance<Obj extends object, Prop extends keyof Obj>(
|
||||||
return inst as any
|
return inst as any
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a computed signal.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const name = atom('name', 'John')
|
|
||||||
* const greeting = computed('greeting', () => `Hello ${name.get()}!`)
|
|
||||||
* console.log(greeting.get()) // 'Hello John!'
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* `computed` may also be used as a decorator for creating computed getter methods.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* class Counter {
|
|
||||||
* max = 100
|
|
||||||
* count = atom<number>(0)
|
|
||||||
*
|
|
||||||
* @computed getRemaining() {
|
|
||||||
* return this.max - this.count.get()
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* You may optionally pass in a [[ComputedOptions]] when used as a decorator:
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* class Counter {
|
|
||||||
* max = 100
|
|
||||||
* count = atom<number>(0)
|
|
||||||
*
|
|
||||||
* @computed({isEqual: (a, b) => a === b})
|
|
||||||
* getRemaining() {
|
|
||||||
* return this.max - this.count.get()
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param name - The name of the signal.
|
|
||||||
* @param compute - The function that computes the value of the signal.
|
|
||||||
* @param options - Options for the signal.
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export function computed<Value, Diff = unknown>(
|
export function computed<Value, Diff = unknown>(
|
||||||
name: string,
|
name: string,
|
||||||
compute: (
|
compute: (
|
||||||
|
|
|
@ -37,23 +37,6 @@ interface EffectSchedulerOptions {
|
||||||
scheduleEffect?: (execute: () => void) => void
|
scheduleEffect?: (execute: () => void) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* An EffectScheduler is responsible for executing side effects in response to changes in state.
|
|
||||||
*
|
|
||||||
* You probably don't need to use this directly unless you're integrating this library with a framework of some kind.
|
|
||||||
*
|
|
||||||
* Instead, use the [[react]] and [[reactor]] functions.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const render = new EffectScheduler('render', drawToCanvas)
|
|
||||||
*
|
|
||||||
* render.attach()
|
|
||||||
* render.execute()
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export class EffectScheduler<Result> {
|
export class EffectScheduler<Result> {
|
||||||
private _isActivelyListening = false
|
private _isActivelyListening = false
|
||||||
/**
|
/**
|
||||||
|
@ -166,36 +149,6 @@ export class EffectScheduler<Result> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts a new effect scheduler, scheduling the effect immediately.
|
|
||||||
*
|
|
||||||
* Returns a function that can be called to stop the scheduler.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const color = atom('color', 'red')
|
|
||||||
* const stop = react('set style', () => {
|
|
||||||
* divElem.style.color = color.get()
|
|
||||||
* })
|
|
||||||
* color.set('blue')
|
|
||||||
* // divElem.style.color === 'blue'
|
|
||||||
* stop()
|
|
||||||
* color.set('green')
|
|
||||||
* // divElem.style.color === 'blue'
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Also useful in React applications for running effects outside of the render cycle.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* useEffect(() => react('set style', () => {
|
|
||||||
* divRef.current.style.color = color.get()
|
|
||||||
* }), [])
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export function react(
|
export function react(
|
||||||
name: string,
|
name: string,
|
||||||
fn: (lastReactedEpoch: number) => any,
|
fn: (lastReactedEpoch: number) => any,
|
||||||
|
@ -241,11 +194,6 @@ export interface Reactor<T = unknown> {
|
||||||
stop(): void
|
stop(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a [[Reactor]], which is a thin wrapper around an [[EffectScheduler]].
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export function reactor<Result>(
|
export function reactor<Result>(
|
||||||
name: string,
|
name: string,
|
||||||
fn: (lastReactedEpoch: number) => Result,
|
fn: (lastReactedEpoch: number) => Result,
|
||||||
|
|
|
@ -1,17 +1,6 @@
|
||||||
import { attach, detach } from './helpers'
|
import { attach, detach } from './helpers'
|
||||||
import { Child, Signal } from './types'
|
import { Child, Signal } from './types'
|
||||||
|
|
||||||
const tldrawStateGlobalKey = Symbol.for('__@tldraw/state__')
|
|
||||||
const tldrawStateGlobal = globalThis as { [tldrawStateGlobalKey]?: true }
|
|
||||||
|
|
||||||
if (tldrawStateGlobal[tldrawStateGlobalKey]) {
|
|
||||||
console.error(
|
|
||||||
'Multiple versions of @tldraw/state detected. This will cause unexpected behavior. Please add "resolutions" (yarn/pnpm) or "overrides" (npm) in your package.json to ensure only one version of @tldraw/state is loaded.'
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
tldrawStateGlobal[tldrawStateGlobalKey] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
class CaptureStackFrame {
|
class CaptureStackFrame {
|
||||||
offset = 0
|
offset = 0
|
||||||
numNewParents = 0
|
numNewParents = 0
|
||||||
|
@ -23,29 +12,6 @@ class CaptureStackFrame {
|
||||||
|
|
||||||
let stack: CaptureStackFrame | null = null
|
let stack: CaptureStackFrame | null = null
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the given function without capturing any parents in the current capture context.
|
|
||||||
*
|
|
||||||
* This is mainly useful if you want to run an effect only when certain signals change while also
|
|
||||||
* dereferencing other signals which should not cause the effect to rerun on their own.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const name = atom('name', 'Sam')
|
|
||||||
* const time = atom('time', () => new Date().getTime())
|
|
||||||
*
|
|
||||||
* setInterval(() => {
|
|
||||||
* time.set(new Date().getTime())
|
|
||||||
* })
|
|
||||||
*
|
|
||||||
* react('log name changes', () => {
|
|
||||||
* print(name.get(), 'was changed at', unsafe__withoutCapture(() => time.get()))
|
|
||||||
* })
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export function unsafe__withoutCapture<T>(fn: () => T): T {
|
export function unsafe__withoutCapture<T>(fn: () => T): T {
|
||||||
const oldStack = stack
|
const oldStack = stack
|
||||||
stack = null
|
stack = null
|
||||||
|
@ -125,26 +91,6 @@ export function maybeCaptureParent(p: Signal<any, any>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A debugging tool that tells you why a computed signal or effect is running.
|
|
||||||
* Call in the body of a computed signal or effect function.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const name = atom('name', 'Bob')
|
|
||||||
* react('greeting', () => {
|
|
||||||
* whyAmIRunning()
|
|
||||||
* print('Hello', name.get())
|
|
||||||
* })
|
|
||||||
*
|
|
||||||
* name.set('Alice')
|
|
||||||
*
|
|
||||||
* // 'greeting' is running because:
|
|
||||||
* // 'name' changed => 'Alice'
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export function whyAmIRunning() {
|
export function whyAmIRunning() {
|
||||||
const child = stack?.child
|
const child = stack?.child
|
||||||
if (!child) {
|
if (!child) {
|
||||||
|
|
|
@ -1,12 +1,347 @@
|
||||||
export { atom, isAtom } from './Atom'
|
import { atom as _atom, isAtom as _isAtom } from './Atom'
|
||||||
|
import { computed as _computed, withDiff as _withDiff } from './Computed'
|
||||||
|
import {
|
||||||
|
EffectScheduler as _EffectScheduler,
|
||||||
|
react as _react,
|
||||||
|
reactor as _reactor,
|
||||||
|
} from './EffectScheduler'
|
||||||
|
import {
|
||||||
|
unsafe__withoutCapture as _unsafe__withoutCapture,
|
||||||
|
whyAmIRunning as _whyAmIRunning,
|
||||||
|
} from './capture'
|
||||||
|
import { EMPTY_ARRAY as _EMPTY_ARRAY } from './helpers'
|
||||||
|
import { isSignal as _isSignal } from './isSignal'
|
||||||
|
import { transact as _transact, transaction as _transaction } from './transactions'
|
||||||
|
|
||||||
|
const sym = Symbol.for('com.tldraw.state')
|
||||||
|
const glob = globalThis as any
|
||||||
|
|
||||||
|
// This should be incremented any time an API change is made. i.e. for additions or removals.
|
||||||
|
// Bugfixes need not increment this.
|
||||||
|
const currentApiVersion = 1
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
return {
|
||||||
|
apiVersion: currentApiVersion,
|
||||||
|
atom: _atom,
|
||||||
|
isAtom: _isAtom,
|
||||||
|
computed: _computed,
|
||||||
|
withDiff: _withDiff,
|
||||||
|
EffectScheduler: _EffectScheduler,
|
||||||
|
react: _react,
|
||||||
|
reactor: _reactor,
|
||||||
|
unsafe__withoutCapture: _unsafe__withoutCapture,
|
||||||
|
whyAmIRunning: _whyAmIRunning,
|
||||||
|
EMPTY_ARRAY: _EMPTY_ARRAY,
|
||||||
|
isSignal: _isSignal,
|
||||||
|
transact: _transact,
|
||||||
|
transaction: _transaction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const obj: ReturnType<typeof init> = glob[sym] || init()
|
||||||
|
glob[sym] = obj
|
||||||
|
|
||||||
|
const {
|
||||||
|
apiVersion,
|
||||||
|
/**
|
||||||
|
* Creates a new [[Atom]].
|
||||||
|
*
|
||||||
|
* An Atom is a signal that can be updated directly by calling [[Atom.set]] or [[Atom.update]].
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const name = atom('name', 'John')
|
||||||
|
*
|
||||||
|
* name.get() // 'John'
|
||||||
|
*
|
||||||
|
* name.set('Jane')
|
||||||
|
*
|
||||||
|
* name.get() // 'Jane'
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
atom,
|
||||||
|
/**
|
||||||
|
* Returns true if the given value is an [[Atom]].
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
isAtom,
|
||||||
|
/**
|
||||||
|
* Creates a computed signal.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const name = atom('name', 'John')
|
||||||
|
* const greeting = computed('greeting', () => `Hello ${name.get()}!`)
|
||||||
|
* console.log(greeting.get()) // 'Hello John!'
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* `computed` may also be used as a decorator for creating computed getter methods.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* class Counter {
|
||||||
|
* max = 100
|
||||||
|
* count = atom<number>(0)
|
||||||
|
*
|
||||||
|
* @computed getRemaining() {
|
||||||
|
* return this.max - this.count.get()
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* You may optionally pass in a [[ComputedOptions]] when used as a decorator:
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* class Counter {
|
||||||
|
* max = 100
|
||||||
|
* count = atom<number>(0)
|
||||||
|
*
|
||||||
|
* @computed({isEqual: (a, b) => a === b})
|
||||||
|
* getRemaining() {
|
||||||
|
* return this.max - this.count.get()
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param name - The name of the signal.
|
||||||
|
* @param compute - The function that computes the value of the signal.
|
||||||
|
* @param options - Options for the signal.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
computed,
|
||||||
|
/**
|
||||||
|
* When writing incrementally-computed signals it is convenient (and usually more performant) to incrementally compute the diff too.
|
||||||
|
*
|
||||||
|
* 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 [[AtomOptions.computeDiff]].
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const count = atom('count', 0)
|
||||||
|
* const double = computed('double', (prevValue) => {
|
||||||
|
* const nextValue = count.get() * 2
|
||||||
|
* if (isUninitialized(prevValue)) {
|
||||||
|
* return nextValue
|
||||||
|
* }
|
||||||
|
* return withDiff(nextValue, nextValue - prevValue)
|
||||||
|
* }, { historyLength: 10 })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param value - The value.
|
||||||
|
* @param diff - The diff.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
withDiff,
|
||||||
|
/**
|
||||||
|
* An EffectScheduler is responsible for executing side effects in response to changes in state.
|
||||||
|
*
|
||||||
|
* You probably don't need to use this directly unless you're integrating this library with a framework of some kind.
|
||||||
|
*
|
||||||
|
* Instead, use the [[react]] and [[reactor]] functions.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const render = new EffectScheduler('render', drawToCanvas)
|
||||||
|
*
|
||||||
|
* render.attach()
|
||||||
|
* render.execute()
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
EffectScheduler,
|
||||||
|
/**
|
||||||
|
* Starts a new effect scheduler, scheduling the effect immediately.
|
||||||
|
*
|
||||||
|
* Returns a function that can be called to stop the scheduler.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const color = atom('color', 'red')
|
||||||
|
* const stop = react('set style', () => {
|
||||||
|
* divElem.style.color = color.get()
|
||||||
|
* })
|
||||||
|
* color.set('blue')
|
||||||
|
* // divElem.style.color === 'blue'
|
||||||
|
* stop()
|
||||||
|
* color.set('green')
|
||||||
|
* // divElem.style.color === 'blue'
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Also useful in React applications for running effects outside of the render cycle.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* useEffect(() => react('set style', () => {
|
||||||
|
* divRef.current.style.color = color.get()
|
||||||
|
* }), [])
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
react,
|
||||||
|
/**
|
||||||
|
* Creates a [[Reactor]], which is a thin wrapper around an [[EffectScheduler]].
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
reactor,
|
||||||
|
/**
|
||||||
|
* Executes the given function without capturing any parents in the current capture context.
|
||||||
|
*
|
||||||
|
* This is mainly useful if you want to run an effect only when certain signals change while also
|
||||||
|
* dereferencing other signals which should not cause the effect to rerun on their own.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const name = atom('name', 'Sam')
|
||||||
|
* const time = atom('time', () => new Date().getTime())
|
||||||
|
*
|
||||||
|
* setInterval(() => {
|
||||||
|
* time.set(new Date().getTime())
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* react('log name changes', () => {
|
||||||
|
* print(name.get(), 'was changed at', unsafe__withoutCapture(() => time.get()))
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
unsafe__withoutCapture,
|
||||||
|
/**
|
||||||
|
* A debugging tool that tells you why a computed signal or effect is running.
|
||||||
|
* Call in the body of a computed signal or effect function.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const name = atom('name', 'Bob')
|
||||||
|
* react('greeting', () => {
|
||||||
|
* whyAmIRunning()
|
||||||
|
* print('Hello', name.get())
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* name.set('Alice')
|
||||||
|
*
|
||||||
|
* // 'greeting' is running because:
|
||||||
|
* // 'name' changed => 'Alice'
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
whyAmIRunning,
|
||||||
|
/** @public */
|
||||||
|
EMPTY_ARRAY,
|
||||||
|
/**
|
||||||
|
* Returns true if the given value is a signal (either an Atom or a Computed).
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
isSignal,
|
||||||
|
/**
|
||||||
|
* Like [transaction](#transaction), but does not create a new transaction if there is already one in progress.
|
||||||
|
*
|
||||||
|
* @param fn - The function to run in a transaction.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
transact,
|
||||||
|
/**
|
||||||
|
* Batches state updates, deferring side effects until after the transaction completes.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const firstName = atom('John')
|
||||||
|
* const lastName = atom('Doe')
|
||||||
|
*
|
||||||
|
* react('greet', () => {
|
||||||
|
* print(`Hello, ${firstName.get()} ${lastName.get()}!`)
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* // Logs "Hello, John Doe!"
|
||||||
|
*
|
||||||
|
* transaction(() => {
|
||||||
|
* firstName.set('Jane')
|
||||||
|
* lastName.set('Smith')
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* // Logs "Hello, Jane Smith!"
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* If the function throws, the transaction is aborted and any signals that were updated during the transaction revert to their state before the transaction began.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const firstName = atom('John')
|
||||||
|
* const lastName = atom('Doe')
|
||||||
|
*
|
||||||
|
* react('greet', () => {
|
||||||
|
* print(`Hello, ${firstName.get()} ${lastName.get()}!`)
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* // Logs "Hello, John Doe!"
|
||||||
|
*
|
||||||
|
* transaction(() => {
|
||||||
|
* firstName.set('Jane')
|
||||||
|
* throw new Error('oops')
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* // Does not log
|
||||||
|
* // firstName.get() === 'John'
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* A `rollback` callback is passed into the function.
|
||||||
|
* Calling this will prevent the transaction from committing and will revert any signals that were updated during the transaction to their state before the transaction began.
|
||||||
|
*
|
||||||
|
* * @example
|
||||||
|
* ```ts
|
||||||
|
* const firstName = atom('John')
|
||||||
|
* const lastName = atom('Doe')
|
||||||
|
*
|
||||||
|
* react('greet', () => {
|
||||||
|
* print(`Hello, ${firstName.get()} ${lastName.get()}!`)
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* // Logs "Hello, John Doe!"
|
||||||
|
*
|
||||||
|
* transaction((rollback) => {
|
||||||
|
* firstName.set('Jane')
|
||||||
|
* lastName.set('Smith')
|
||||||
|
* rollback()
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* // Does not log
|
||||||
|
* // firstName.get() === 'John'
|
||||||
|
* // lastName.get() === 'Doe'
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param fn - The function to run in a transaction, called with a function to roll back the change.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
transaction,
|
||||||
|
} = obj
|
||||||
|
|
||||||
|
if (apiVersion !== currentApiVersion) {
|
||||||
|
throw new Error(
|
||||||
|
'@tldraw/state: Multiple versions of @tldraw/state are being used. Please ensure that there is only one version of @tldraw/state in your dependency tree.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export type { Atom, AtomOptions } from './Atom'
|
export type { Atom, AtomOptions } from './Atom'
|
||||||
export { computed, getComputedInstance, isUninitialized, withDiff } from './Computed'
|
export { getComputedInstance, isUninitialized } from './Computed'
|
||||||
export type { Computed, ComputedOptions } from './Computed'
|
export type { Computed, ComputedOptions } from './Computed'
|
||||||
export { EffectScheduler, react, reactor } from './EffectScheduler'
|
|
||||||
export type { Reactor } from './EffectScheduler'
|
export type { Reactor } from './EffectScheduler'
|
||||||
export { unsafe__withoutCapture, whyAmIRunning } from './capture'
|
|
||||||
export { EMPTY_ARRAY } from './helpers'
|
|
||||||
export { isSignal } from './isSignal'
|
|
||||||
export { transact, transaction } from './transactions'
|
|
||||||
export { RESET_VALUE } from './types'
|
export { RESET_VALUE } from './types'
|
||||||
export type { Signal } from './types'
|
export type { Signal } from './types'
|
||||||
|
export { atom, isAtom }
|
||||||
|
export { computed, withDiff }
|
||||||
|
export { EffectScheduler, react, reactor }
|
||||||
|
export { unsafe__withoutCapture, whyAmIRunning }
|
||||||
|
export { EMPTY_ARRAY }
|
||||||
|
export { isSignal }
|
||||||
|
export { transact, transaction }
|
||||||
|
|
|
@ -2,10 +2,6 @@ import { _Atom } from './Atom'
|
||||||
import { _Computed } from './Computed'
|
import { _Computed } from './Computed'
|
||||||
import { Signal } from './types'
|
import { Signal } from './types'
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the given value is a signal (either an Atom or a Computed).
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export function isSignal(value: any): value is Signal<any> {
|
export function isSignal(value: any): value is Signal<any> {
|
||||||
return value instanceof _Atom || value instanceof _Computed
|
return value instanceof _Atom || value instanceof _Computed
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,78 +135,6 @@ export function atomDidChange(atom: _Atom<any>, previousValue: any) {
|
||||||
*/
|
*/
|
||||||
export let currentTransaction = null as Transaction | null
|
export let currentTransaction = null as Transaction | null
|
||||||
|
|
||||||
/**
|
|
||||||
* Batches state updates, deferring side effects until after the transaction completes.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const firstName = atom('John')
|
|
||||||
* const lastName = atom('Doe')
|
|
||||||
*
|
|
||||||
* react('greet', () => {
|
|
||||||
* print(`Hello, ${firstName.get()} ${lastName.get()}!`)
|
|
||||||
* })
|
|
||||||
*
|
|
||||||
* // Logs "Hello, John Doe!"
|
|
||||||
*
|
|
||||||
* transaction(() => {
|
|
||||||
* firstName.set('Jane')
|
|
||||||
* lastName.set('Smith')
|
|
||||||
* })
|
|
||||||
*
|
|
||||||
* // Logs "Hello, Jane Smith!"
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* If the function throws, the transaction is aborted and any signals that were updated during the transaction revert to their state before the transaction began.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const firstName = atom('John')
|
|
||||||
* const lastName = atom('Doe')
|
|
||||||
*
|
|
||||||
* react('greet', () => {
|
|
||||||
* print(`Hello, ${firstName.get()} ${lastName.get()}!`)
|
|
||||||
* })
|
|
||||||
*
|
|
||||||
* // Logs "Hello, John Doe!"
|
|
||||||
*
|
|
||||||
* transaction(() => {
|
|
||||||
* firstName.set('Jane')
|
|
||||||
* throw new Error('oops')
|
|
||||||
* })
|
|
||||||
*
|
|
||||||
* // Does not log
|
|
||||||
* // firstName.get() === 'John'
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* A `rollback` callback is passed into the function.
|
|
||||||
* Calling this will prevent the transaction from committing and will revert any signals that were updated during the transaction to their state before the transaction began.
|
|
||||||
*
|
|
||||||
* * @example
|
|
||||||
* ```ts
|
|
||||||
* const firstName = atom('John')
|
|
||||||
* const lastName = atom('Doe')
|
|
||||||
*
|
|
||||||
* react('greet', () => {
|
|
||||||
* print(`Hello, ${firstName.get()} ${lastName.get()}!`)
|
|
||||||
* })
|
|
||||||
*
|
|
||||||
* // Logs "Hello, John Doe!"
|
|
||||||
*
|
|
||||||
* transaction((rollback) => {
|
|
||||||
* firstName.set('Jane')
|
|
||||||
* lastName.set('Smith')
|
|
||||||
* rollback()
|
|
||||||
* })
|
|
||||||
*
|
|
||||||
* // Does not log
|
|
||||||
* // firstName.get() === 'John'
|
|
||||||
* // lastName.get() === 'Doe'
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param fn - The function to run in a transaction, called with a function to roll back the change.
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export function transaction<T>(fn: (rollback: () => void) => T) {
|
export function transaction<T>(fn: (rollback: () => void) => T) {
|
||||||
const txn = new Transaction(currentTransaction)
|
const txn = new Transaction(currentTransaction)
|
||||||
|
|
||||||
|
@ -238,12 +166,6 @@ export function transaction<T>(fn: (rollback: () => void) => T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Like [transaction](#transaction), but does not create a new transaction if there is already one in progress.
|
|
||||||
*
|
|
||||||
* @param fn - The function to run in a transaction.
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export function transact<T>(fn: () => T): T {
|
export function transact<T>(fn: () => T): T {
|
||||||
if (currentTransaction) {
|
if (currentTransaction) {
|
||||||
return fn()
|
return fn()
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { _Computed } from './Computed'
|
||||||
import { EffectScheduler } from './EffectScheduler'
|
import { EffectScheduler } from './EffectScheduler'
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export const RESET_VALUE: unique symbol = Symbol('RESET_VALUE')
|
export const RESET_VALUE: unique symbol = Symbol.for('com.tldraw.state/RESET_VALUE')
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export type RESET_VALUE = typeof RESET_VALUE
|
export type RESET_VALUE = typeof RESET_VALUE
|
||||||
|
|
Loading…
Reference in a new issue