@zeix/cause-effect 0.18.2 → 0.18.4

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/src/graph.ts CHANGED
@@ -156,6 +156,7 @@ const TYPE_SENSOR = 'Sensor'
156
156
  const TYPE_LIST = 'List'
157
157
  const TYPE_COLLECTION = 'Collection'
158
158
  const TYPE_STORE = 'Store'
159
+ const TYPE_SLOT = 'Slot'
159
160
 
160
161
  const FLAG_CLEAN = 0
161
162
  const FLAG_CHECK = 1 << 0
@@ -291,11 +292,12 @@ function propagate(node: SinkNode, newFlag = FLAG_DIRTY): void {
291
292
  for (let e = node.sinks; e; e = e.nextSink)
292
293
  propagate(e.sink, FLAG_CHECK)
293
294
  } else {
294
- if (flags & FLAG_DIRTY) return
295
+ if ((flags & (FLAG_DIRTY | FLAG_CHECK)) >= newFlag) return
295
296
 
296
297
  // Enqueue effect for later execution
297
- node.flags = FLAG_DIRTY
298
- queuedEffects.push(node as EffectNode)
298
+ const wasQueued = flags & (FLAG_DIRTY | FLAG_CHECK)
299
+ node.flags = newFlag
300
+ if (!wasQueued) queuedEffects.push(node as EffectNode)
299
301
  }
300
302
  }
301
303
 
@@ -470,7 +472,7 @@ function flush(): void {
470
472
  try {
471
473
  for (let i = 0; i < queuedEffects.length; i++) {
472
474
  const effect = queuedEffects[i]
473
- if (effect.flags & FLAG_DIRTY) refresh(effect)
475
+ if (effect.flags & (FLAG_DIRTY | FLAG_CHECK)) refresh(effect)
474
476
  }
475
477
  queuedEffects.length = 0
476
478
  } finally {
@@ -600,6 +602,7 @@ export {
600
602
  createScope,
601
603
  DEFAULT_EQUALITY,
602
604
  SKIP_EQUALITY,
605
+ FLAG_CHECK,
603
606
  FLAG_CLEAN,
604
607
  FLAG_DIRTY,
605
608
  FLAG_RELINK,
@@ -617,6 +620,7 @@ export {
617
620
  TYPE_MEMO,
618
621
  TYPE_SENSOR,
619
622
  TYPE_STATE,
623
+ TYPE_SLOT,
620
624
  TYPE_STORE,
621
625
  TYPE_TASK,
622
626
  unlink,
@@ -100,10 +100,6 @@ function deriveCollection<T extends {}, U extends {}>(
100
100
  callback: DeriveCollectionCallback<T, U>,
101
101
  ): Collection<T> {
102
102
  validateCallback(TYPE_COLLECTION, callback)
103
- if (!isCollectionSource(source))
104
- throw new TypeError(
105
- `[${TYPE_COLLECTION}] Invalid collection source: expected a List or Collection`,
106
- )
107
103
 
108
104
  const isAsync = isAsyncFunction(callback)
109
105
  const signals = new Map<string, Memo<T>>()
@@ -22,7 +22,7 @@ import {
22
22
 
23
23
  type MaybePromise<T> = T | Promise<T>
24
24
 
25
- type MatchHandlers<T extends Signal<unknown & {}>[]> = {
25
+ type MatchHandlers<T extends readonly Signal<unknown & {}>[]> = {
26
26
  ok: (values: {
27
27
  [K in keyof T]: T[K] extends Signal<infer V> ? V : never
28
28
  }) => MaybePromise<MaybeCleanup>
@@ -94,8 +94,8 @@ function createEffect(fn: EffectCallback): Cleanup {
94
94
  * @since 0.15.0
95
95
  * @throws RequiredOwnerError If called without an active owner.
96
96
  */
97
- function match<T extends Signal<unknown & {}>[]>(
98
- signals: T,
97
+ function match<T extends readonly Signal<unknown & {}>[]>(
98
+ signals: readonly [...T],
99
99
  handlers: MatchHandlers<T>,
100
100
  ): MaybeCleanup {
101
101
  if (!activeOwner) throw new RequiredOwnerError('match')
package/src/nodes/memo.ts CHANGED
@@ -110,9 +110,7 @@ function createMemo<T extends {}>(
110
110
  if (activeSink) {
111
111
  if (!node.sinks)
112
112
  node.stop = watched(() => {
113
- node.flags |= FLAG_DIRTY
114
- for (let e = node.sinks; e; e = e.nextSink)
115
- propagate(e.sink)
113
+ propagate(node as unknown as SinkNode)
116
114
  if (batchDepth === 0) flush()
117
115
  })
118
116
  link(node, activeSink)
@@ -0,0 +1,134 @@
1
+ import { ReadonlySignalError, validateSignalValue } from '../errors'
2
+ import {
3
+ activeSink,
4
+ batchDepth,
5
+ DEFAULT_EQUALITY,
6
+ FLAG_DIRTY,
7
+ flush,
8
+ link,
9
+ type MemoNode,
10
+ propagate,
11
+ refresh,
12
+ type Signal,
13
+ type SignalOptions,
14
+ type SinkNode,
15
+ TYPE_SLOT,
16
+ } from '../graph'
17
+ import { isMutableSignal, isSignal } from '../signal'
18
+ import { isObjectOfType } from '../util'
19
+
20
+ /* === Types === */
21
+
22
+ /**
23
+ * A signal that delegates its value to a swappable backing signal.
24
+ *
25
+ * Slots provide a stable reactive source at a fixed position (e.g. an object property)
26
+ * while allowing the backing signal to be replaced without breaking subscribers.
27
+ * The object shape is compatible with `Object.defineProperty()` descriptors:
28
+ * `get`, `set`, `configurable`, and `enumerable` are used by the property definition;
29
+ * `replace()` and `current()` are kept on the slot object for integration-layer control.
30
+ *
31
+ * @template T - The type of value held by the delegated signal.
32
+ */
33
+ type Slot<T extends {}> = {
34
+ readonly [Symbol.toStringTag]: 'Slot'
35
+ /** Descriptor field: allows the property to be redefined or deleted. */
36
+ configurable: true
37
+ /** Descriptor field: the property shows up during enumeration. */
38
+ enumerable: true
39
+ /** Reads the current value from the delegated signal, tracking dependencies. */
40
+ get(): T
41
+ /** Writes a value to the delegated signal. Throws `ReadonlySignalError` if the delegated signal is read-only. */
42
+ set(next: T): void
43
+ /** Swaps the backing signal, invalidating all downstream subscribers. Narrowing (`U extends T`) is allowed. */
44
+ replace<U extends T>(next: Signal<U>): void
45
+ /** Returns the currently delegated signal. */
46
+ current(): Signal<T>
47
+ }
48
+
49
+ /* === Exported Functions === */
50
+
51
+ /**
52
+ * Creates a slot signal that delegates its value to a swappable backing signal.
53
+ *
54
+ * A slot acts as a stable reactive source that can be used as a property descriptor
55
+ * via `Object.defineProperty(target, key, slot)`. Subscribers link to the slot itself,
56
+ * so replacing the backing signal with `replace()` invalidates them without breaking
57
+ * existing edges. Setter calls forward to the current backing signal when it is writable.
58
+ *
59
+ * @since 0.18.3
60
+ * @template T - The type of value held by the delegated signal.
61
+ * @param initialSignal - The initial signal to delegate to.
62
+ * @param options - Optional configuration for the slot.
63
+ * @param options.equals - Custom equality function. Defaults to strict equality (`===`).
64
+ * @param options.guard - Type guard to validate values passed to `set()`.
65
+ * @returns A `Slot<T>` object usable both as a property descriptor and as a reactive signal.
66
+ */
67
+ function createSlot<T extends {}>(
68
+ initialSignal: Signal<T>,
69
+ options?: SignalOptions<T>,
70
+ ): Slot<T> {
71
+ validateSignalValue(TYPE_SLOT, initialSignal, isSignal)
72
+
73
+ let delegated = initialSignal as Signal<T>
74
+ const guard = options?.guard
75
+
76
+ const node: MemoNode<T> = {
77
+ fn: () => delegated.get(),
78
+ value: undefined as unknown as T,
79
+ flags: FLAG_DIRTY,
80
+ sources: null,
81
+ sourcesTail: null,
82
+ sinks: null,
83
+ sinksTail: null,
84
+ equals: options?.equals ?? DEFAULT_EQUALITY,
85
+ error: undefined,
86
+ }
87
+
88
+ const get = (): T => {
89
+ if (activeSink) link(node, activeSink)
90
+ refresh(node as unknown as SinkNode)
91
+ if (node.error) throw node.error
92
+ return node.value
93
+ }
94
+
95
+ const set = (next: T): void => {
96
+ if (!isMutableSignal(delegated))
97
+ throw new ReadonlySignalError(TYPE_SLOT)
98
+ validateSignalValue(TYPE_SLOT, next, guard)
99
+
100
+ delegated.set(next)
101
+ }
102
+
103
+ const replace = <U extends T>(next: Signal<U>): void => {
104
+ validateSignalValue(TYPE_SLOT, next, isSignal)
105
+
106
+ delegated = next
107
+ node.flags |= FLAG_DIRTY
108
+ for (let e = node.sinks; e; e = e.nextSink) propagate(e.sink)
109
+ if (batchDepth === 0) flush()
110
+ }
111
+
112
+ return {
113
+ [Symbol.toStringTag]: TYPE_SLOT,
114
+ configurable: true,
115
+ enumerable: true,
116
+ get,
117
+ set,
118
+ replace,
119
+ current: () => delegated,
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Checks if a value is a Slot signal.
125
+ *
126
+ * @since 0.18.3
127
+ * @param value - The value to check
128
+ * @returns True if the value is a Slot
129
+ */
130
+ function isSlot<T extends {} = unknown & {}>(value: unknown): value is Slot<T> {
131
+ return isObjectOfType(value, TYPE_SLOT)
132
+ }
133
+
134
+ export { createSlot, isSlot, type Slot }
package/src/nodes/task.ts CHANGED
@@ -129,9 +129,7 @@ function createTask<T extends {}>(
129
129
  if (activeSink) {
130
130
  if (!node.sinks)
131
131
  node.stop = watched(() => {
132
- node.flags |= FLAG_DIRTY
133
- for (let e = node.sinks; e; e = e.nextSink)
134
- propagate(e.sink)
132
+ propagate(node as unknown as SinkNode)
135
133
  if (batchDepth === 0) flush()
136
134
  })
137
135
  link(node, activeSink)
package/src/signal.ts CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  TYPE_LIST,
9
9
  TYPE_MEMO,
10
10
  TYPE_SENSOR,
11
+ TYPE_SLOT,
11
12
  TYPE_STATE,
12
13
  TYPE_STORE,
13
14
  TYPE_TASK,
@@ -122,6 +123,7 @@ function isSignal<T extends {}>(value: unknown): value is Signal<T> {
122
123
  TYPE_MEMO,
123
124
  TYPE_TASK,
124
125
  TYPE_SENSOR,
126
+ TYPE_SLOT,
125
127
  TYPE_LIST,
126
128
  TYPE_COLLECTION,
127
129
  TYPE_STORE,
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, mock, test } from 'bun:test'
2
2
  import {
3
+ batch,
3
4
  createEffect,
4
5
  createMemo,
5
6
  createScope,
@@ -140,6 +141,209 @@ describe('createEffect', () => {
140
141
  })
141
142
  })
142
143
 
144
+ describe('Watched memo equality', () => {
145
+ test('should skip effect re-run when watched memo recomputes to same value', () => {
146
+ let invalidate!: () => void
147
+ let effectCount = 0
148
+
149
+ // Memo whose computed value does not change on invalidation
150
+ const memo = createMemo(() => 42, {
151
+ value: 42,
152
+ watched: inv => {
153
+ invalidate = inv
154
+ return () => {}
155
+ },
156
+ })
157
+
158
+ const dispose = createScope(() => {
159
+ createEffect(() => {
160
+ void memo.get()
161
+ effectCount++
162
+ })
163
+ })
164
+
165
+ expect(effectCount).toBe(1)
166
+
167
+ // Invalidate — memo recomputes but returns same value (42)
168
+ invalidate()
169
+
170
+ // Because equals(42, 42) is true, the effect should NOT re-run
171
+ expect(effectCount).toBe(1)
172
+
173
+ dispose()
174
+ })
175
+
176
+ test('should re-run effect when watched memo recomputes to different value', () => {
177
+ let invalidate!: () => void
178
+ let effectCount = 0
179
+ let externalValue = 1
180
+
181
+ const memo = createMemo(() => externalValue, {
182
+ value: 0,
183
+ watched: inv => {
184
+ invalidate = inv
185
+ return () => {}
186
+ },
187
+ })
188
+
189
+ let observed = 0
190
+ const dispose = createScope(() => {
191
+ createEffect(() => {
192
+ observed = memo.get()
193
+ effectCount++
194
+ })
195
+ })
196
+
197
+ expect(effectCount).toBe(1)
198
+ expect(observed).toBe(1)
199
+
200
+ // Change external value and invalidate — memo returns a new value
201
+ externalValue = 99
202
+ invalidate()
203
+
204
+ expect(effectCount).toBe(2)
205
+ expect(observed).toBe(99)
206
+
207
+ dispose()
208
+ })
209
+
210
+ test('should respect custom equals to skip effect re-run', () => {
211
+ let invalidate!: () => void
212
+ let effectCount = 0
213
+ let externalValue = 3
214
+
215
+ // Custom equals: treat values as equal when they round to the same integer
216
+ const memo = createMemo(() => externalValue, {
217
+ value: 0,
218
+ equals: (a, b) => Math.floor(a) === Math.floor(b),
219
+ watched: inv => {
220
+ invalidate = inv
221
+ return () => {}
222
+ },
223
+ })
224
+
225
+ const dispose = createScope(() => {
226
+ createEffect(() => {
227
+ void memo.get()
228
+ effectCount++
229
+ })
230
+ })
231
+
232
+ expect(effectCount).toBe(1)
233
+
234
+ // External value changes slightly but rounds to same integer
235
+ externalValue = 3.7
236
+ invalidate()
237
+ expect(effectCount).toBe(1) // equals says same → effect skipped
238
+
239
+ // External value changes to a different integer
240
+ externalValue = 4.1
241
+ invalidate()
242
+ expect(effectCount).toBe(2) // equals says different → effect runs
243
+
244
+ dispose()
245
+ })
246
+
247
+ test('should skip effect re-run through memo chain when watched memo value unchanged', () => {
248
+ let invalidate!: () => void
249
+ let effectCount = 0
250
+
251
+ const watchedMemo = createMemo(() => 42, {
252
+ value: 42,
253
+ watched: inv => {
254
+ invalidate = inv
255
+ return () => {}
256
+ },
257
+ })
258
+
259
+ // Downstream memo that doubles the watched memo value
260
+ const doubled = createMemo(() => watchedMemo.get() * 2)
261
+
262
+ const dispose = createScope(() => {
263
+ createEffect(() => {
264
+ void doubled.get()
265
+ effectCount++
266
+ })
267
+ })
268
+
269
+ expect(effectCount).toBe(1)
270
+
271
+ // Invalidate — watchedMemo recomputes to same value, so doubled
272
+ // should also remain unchanged, and the effect should not re-run
273
+ invalidate()
274
+ expect(effectCount).toBe(1)
275
+
276
+ dispose()
277
+ })
278
+
279
+ test('should skip effect when invalidate is called inside batch and value unchanged', () => {
280
+ let invalidate!: () => void
281
+ let effectCount = 0
282
+
283
+ const memo = createMemo(() => 42, {
284
+ value: 42,
285
+ watched: inv => {
286
+ invalidate = inv
287
+ return () => {}
288
+ },
289
+ })
290
+
291
+ const dispose = createScope(() => {
292
+ createEffect(() => {
293
+ void memo.get()
294
+ effectCount++
295
+ })
296
+ })
297
+
298
+ expect(effectCount).toBe(1)
299
+
300
+ batch(() => {
301
+ invalidate()
302
+ })
303
+
304
+ // Value didn't change so effect should still be at 1
305
+ expect(effectCount).toBe(1)
306
+
307
+ dispose()
308
+ })
309
+
310
+ test('should still run effect for dirty state even when watched memo unchanged', () => {
311
+ let invalidate!: () => void
312
+ let effectCount = 0
313
+ const state = createState(1)
314
+
315
+ const memo = createMemo(() => 42, {
316
+ value: 42,
317
+ watched: inv => {
318
+ invalidate = inv
319
+ return () => {}
320
+ },
321
+ })
322
+
323
+ let observedState = 0
324
+ const dispose = createScope(() => {
325
+ createEffect(() => {
326
+ observedState = state.get()
327
+ void memo.get()
328
+ effectCount++
329
+ })
330
+ })
331
+
332
+ expect(effectCount).toBe(1)
333
+
334
+ // Change the state AND invalidate — effect must run because state changed
335
+ batch(() => {
336
+ state.set(2)
337
+ invalidate()
338
+ })
339
+
340
+ expect(effectCount).toBe(2)
341
+ expect(observedState).toBe(2)
342
+
343
+ dispose()
344
+ })
345
+ })
346
+
143
347
  describe('Input Validation', () => {
144
348
  test('should throw InvalidCallbackError for non-function', () => {
145
349
  // @ts-expect-error - Testing invalid input
@@ -249,6 +453,23 @@ describe('match', () => {
249
453
  }
250
454
  })
251
455
 
456
+ test('should preserve tuple types in ok handler', () => {
457
+ const a = createState(1)
458
+ const b = createState('hello')
459
+ createEffect(() =>
460
+ match([a, b], {
461
+ ok: ([aVal, bVal]) => {
462
+ // If tuple types are preserved, aVal is number and bVal is string
463
+ // If widened, both would be string | number
464
+ const num: number = aVal
465
+ const str: string = bVal
466
+ expect(num).toBe(1)
467
+ expect(str).toBe('hello')
468
+ },
469
+ }),
470
+ )
471
+ })
472
+
252
473
  test('should throw RequiredOwnerError when called outside an owner', () => {
253
474
  expect(() => match([], { ok: () => {} })).toThrow(RequiredOwnerError)
254
475
  })
@@ -6,6 +6,7 @@ import {
6
6
  createMutableSignal,
7
7
  createScope,
8
8
  createSignal,
9
+ createSlot,
9
10
  createState,
10
11
  createStore,
11
12
  createTask,
@@ -229,6 +230,7 @@ describe('isSignal', () => {
229
230
  expect(isSignal(createTask(async () => 42))).toBe(true)
230
231
  expect(isSignal(createStore({ a: 1 }))).toBe(true)
231
232
  expect(isSignal(createList([1, 2, 3]))).toBe(true)
233
+ expect(isSignal(createSlot(createState(1)))).toBe(true)
232
234
  })
233
235
  cleanup()
234
236
  })
@@ -0,0 +1,118 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import {
3
+ batch,
4
+ createEffect,
5
+ createMemo,
6
+ createSlot,
7
+ createState,
8
+ } from '../index.ts'
9
+ import { InvalidSignalValueError, NullishSignalValueError } from '../src/errors'
10
+
11
+ describe('Slot', () => {
12
+ test('should replace delegated signal and re-subscribe sinks', () => {
13
+ const local = createState(1)
14
+ const parent = createState(10)
15
+ const derived = createMemo(() => parent.get())
16
+ const slot = createSlot(local)
17
+
18
+ const target = {}
19
+ Object.defineProperty(target, 'value', slot)
20
+
21
+ let runs = 0
22
+ let seen = 0
23
+ createEffect(() => {
24
+ seen = (target as { value: number }).value
25
+ runs++
26
+ })
27
+
28
+ expect(runs).toBe(1)
29
+ expect(seen).toBe(1)
30
+
31
+ slot.replace(derived)
32
+ expect(runs).toBe(2)
33
+ expect(seen).toBe(10)
34
+
35
+ // Old delegated signal should no longer trigger downstream sinks
36
+ local.set(2)
37
+ expect(runs).toBe(2)
38
+
39
+ parent.set(11)
40
+ expect(runs).toBe(3)
41
+ expect(seen).toBe(11)
42
+ })
43
+
44
+ test('should forward property set to writable delegated signal', () => {
45
+ const source = createState(2)
46
+ const slot = createSlot(source)
47
+ const target = {}
48
+ Object.defineProperty(target, 'value', slot)
49
+ ;(target as { value: number }).value = 3
50
+
51
+ expect(source.get()).toBe(3)
52
+ expect((target as { value: number }).value).toBe(3)
53
+ })
54
+
55
+ test('should throw on set when delegated signal is read-only', () => {
56
+ const source = createState(2)
57
+ const readonly = createMemo(() => source.get() * 2)
58
+ const slot = createSlot(source)
59
+ const target = {}
60
+ Object.defineProperty(target, 'value', slot)
61
+ slot.replace(readonly)
62
+
63
+ expect(() => {
64
+ ;(target as { value: number }).value = 7
65
+ }).toThrow('[Slot] Signal is read-only')
66
+ })
67
+
68
+ test('should keep replace handle outside property descriptor', () => {
69
+ const source = createState(1)
70
+ const slot = createSlot(source)
71
+ const target = {}
72
+ Object.defineProperty(target, 'value', slot)
73
+
74
+ const descriptor = Object.getOwnPropertyDescriptor(target, 'value')
75
+ expect(descriptor).toBeDefined()
76
+ expect(typeof descriptor?.get).toBe('function')
77
+ expect(typeof descriptor?.set).toBe('function')
78
+ expect((descriptor as unknown as { replace?: unknown }).replace).toBe(
79
+ undefined,
80
+ )
81
+ expect(typeof slot.replace).toBe('function')
82
+ })
83
+
84
+ test('should batch multiple replacements into one downstream rerun', () => {
85
+ const a = createState(1)
86
+ const b = createState(2)
87
+ const c = createState(3)
88
+ const slot = createSlot(a)
89
+ const target = {}
90
+ Object.defineProperty(target, 'value', slot)
91
+
92
+ let runs = 0
93
+ createEffect(() => {
94
+ void (target as { value: number }).value
95
+ runs++
96
+ })
97
+ expect(runs).toBe(1)
98
+
99
+ batch(() => {
100
+ slot.replace(b)
101
+ slot.replace(c)
102
+ })
103
+ expect(runs).toBe(2)
104
+ })
105
+
106
+ test('should validate initial signal and replacement signal', () => {
107
+ expect(() => {
108
+ // @ts-expect-error: deliberate error test
109
+ createSlot(null)
110
+ }).toThrow(NullishSignalValueError)
111
+
112
+ const slot = createSlot(createState(1))
113
+ expect(() => {
114
+ // @ts-expect-error: deliberate error test
115
+ slot.replace(42)
116
+ }).toThrow(InvalidSignalValueError)
117
+ })
118
+ })
package/types/index.d.ts CHANGED
@@ -1,15 +1,16 @@
1
1
  /**
2
2
  * @name Cause & Effect
3
- * @version 0.18.1
3
+ * @version 0.18.3
4
4
  * @author Esther Brunner
5
5
  */
6
- export { CircularDependencyError, type Guard, InvalidCallbackError, InvalidSignalValueError, NullishSignalValueError, RequiredOwnerError, UnsetSignalValueError, } from './src/errors';
6
+ export { CircularDependencyError, type Guard, InvalidCallbackError, InvalidSignalValueError, NullishSignalValueError, ReadonlySignalError, RequiredOwnerError, UnsetSignalValueError, } from './src/errors';
7
7
  export { batch, type Cleanup, type ComputedOptions, createScope, type EffectCallback, type MaybeCleanup, type MemoCallback, type Signal, type SignalOptions, SKIP_EQUALITY, type TaskCallback, untrack, } from './src/graph';
8
8
  export { type Collection, type CollectionCallback, type CollectionChanges, type CollectionOptions, createCollection, type DeriveCollectionCallback, isCollection, } from './src/nodes/collection';
9
9
  export { createEffect, type MatchHandlers, type MaybePromise, match, } from './src/nodes/effect';
10
10
  export { createList, isEqual, isList, type KeyConfig, type List, type ListOptions, } from './src/nodes/list';
11
11
  export { createMemo, isMemo, type Memo } from './src/nodes/memo';
12
12
  export { createSensor, isSensor, type Sensor, type SensorCallback, type SensorOptions, } from './src/nodes/sensor';
13
+ export { createSlot, isSlot, type Slot } from './src/nodes/slot';
13
14
  export { createState, isState, type State, type UpdateCallback, } from './src/nodes/state';
14
15
  export { createStore, isStore, type Store, type StoreOptions, } from './src/nodes/store';
15
16
  export { createTask, isTask, type Task } from './src/nodes/task';
@@ -64,6 +64,14 @@ declare class InvalidCallbackError extends TypeError {
64
64
  */
65
65
  constructor(where: string, value: unknown);
66
66
  }
67
+ declare class ReadonlySignalError extends Error {
68
+ /**
69
+ * Constructs a new ReadonlySignalError.
70
+ *
71
+ * @param where - The location where the error occurred.
72
+ */
73
+ constructor(where: string);
74
+ }
67
75
  /**
68
76
  * Error thrown when an API requiring an owner is called without one.
69
77
  */
@@ -82,4 +90,4 @@ declare function validateSignalValue<T extends {}>(where: string, value: unknown
82
90
  declare function validateReadValue<T extends {}>(where: string, value: T | null | undefined): asserts value is T;
83
91
  declare function validateCallback(where: string, value: unknown): asserts value is (...args: unknown[]) => unknown;
84
92
  declare function validateCallback<T>(where: string, value: unknown, guard: (value: unknown) => value is T): asserts value is T;
85
- export { type Guard, CircularDependencyError, NullishSignalValueError, InvalidSignalValueError, UnsetSignalValueError, InvalidCallbackError, RequiredOwnerError, DuplicateKeyError, validateSignalValue, validateReadValue, validateCallback, };
93
+ export { type Guard, CircularDependencyError, NullishSignalValueError, InvalidSignalValueError, UnsetSignalValueError, InvalidCallbackError, ReadonlySignalError, RequiredOwnerError, DuplicateKeyError, validateSignalValue, validateReadValue, validateCallback, };
@@ -117,8 +117,11 @@ declare const TYPE_SENSOR = "Sensor";
117
117
  declare const TYPE_LIST = "List";
118
118
  declare const TYPE_COLLECTION = "Collection";
119
119
  declare const TYPE_STORE = "Store";
120
+ declare const TYPE_SLOT = "Slot";
120
121
  declare const FLAG_CLEAN = 0;
122
+ declare const FLAG_CHECK: number;
121
123
  declare const FLAG_DIRTY: number;
124
+ declare const FLAG_RELINK: number;
122
125
  declare let activeSink: SinkNode | null;
123
126
  declare let activeOwner: OwnerNode | null;
124
127
  declare let batchDepth: number;
@@ -215,4 +218,4 @@ declare function untrack<T>(fn: () => T): T;
215
218
  * ```
216
219
  */
217
220
  declare function createScope(fn: () => MaybeCleanup): Cleanup;
218
- export { type Cleanup, type ComputedOptions, type EffectCallback, type EffectNode, type MaybeCleanup, type MemoCallback, type MemoNode, type Scope, type Signal, type SignalOptions, type SinkNode, type StateNode, type TaskCallback, type TaskNode, activeOwner, activeSink, batch, batchDepth, createScope, DEFAULT_EQUALITY, SKIP_EQUALITY, FLAG_CLEAN, FLAG_DIRTY, flush, link, propagate, refresh, registerCleanup, runCleanup, runEffect, setState, trimSources, TYPE_COLLECTION, TYPE_LIST, TYPE_MEMO, TYPE_SENSOR, TYPE_STATE, TYPE_STORE, TYPE_TASK, unlink, untrack, };
221
+ export { type Cleanup, type ComputedOptions, type EffectCallback, type EffectNode, type MaybeCleanup, type MemoCallback, type MemoNode, type Scope, type Signal, type SignalOptions, type SinkNode, type StateNode, type TaskCallback, type TaskNode, activeOwner, activeSink, batch, batchDepth, createScope, DEFAULT_EQUALITY, SKIP_EQUALITY, FLAG_CHECK, FLAG_CLEAN, FLAG_DIRTY, FLAG_RELINK, flush, link, propagate, refresh, registerCleanup, runCleanup, runEffect, setState, trimSources, TYPE_COLLECTION, TYPE_LIST, TYPE_MEMO, TYPE_SENSOR, TYPE_STATE, TYPE_SLOT, TYPE_STORE, TYPE_TASK, unlink, untrack, };