@zeix/cause-effect 0.17.3 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/.ai-context.md +163 -232
  2. package/.cursorrules +41 -35
  3. package/.github/copilot-instructions.md +166 -116
  4. package/ARCHITECTURE.md +274 -0
  5. package/CLAUDE.md +199 -143
  6. package/COLLECTION_REFACTORING.md +161 -0
  7. package/GUIDE.md +298 -0
  8. package/README.md +232 -197
  9. package/REQUIREMENTS.md +100 -0
  10. package/bench/reactivity.bench.ts +577 -0
  11. package/index.dev.js +1325 -997
  12. package/index.js +1 -1
  13. package/index.ts +58 -74
  14. package/package.json +4 -1
  15. package/src/errors.ts +118 -74
  16. package/src/graph.ts +601 -0
  17. package/src/nodes/collection.ts +474 -0
  18. package/src/nodes/effect.ts +149 -0
  19. package/src/nodes/list.ts +588 -0
  20. package/src/nodes/memo.ts +120 -0
  21. package/src/nodes/sensor.ts +139 -0
  22. package/src/nodes/state.ts +135 -0
  23. package/src/nodes/store.ts +383 -0
  24. package/src/nodes/task.ts +146 -0
  25. package/src/signal.ts +112 -66
  26. package/src/util.ts +26 -57
  27. package/test/batch.test.ts +96 -62
  28. package/test/benchmark.test.ts +473 -487
  29. package/test/collection.test.ts +466 -706
  30. package/test/effect.test.ts +293 -696
  31. package/test/list.test.ts +335 -592
  32. package/test/memo.test.ts +380 -0
  33. package/test/regression.test.ts +156 -0
  34. package/test/scope.test.ts +191 -0
  35. package/test/sensor.test.ts +454 -0
  36. package/test/signal.test.ts +220 -213
  37. package/test/state.test.ts +217 -265
  38. package/test/store.test.ts +346 -446
  39. package/test/task.test.ts +395 -0
  40. package/test/untrack.test.ts +167 -0
  41. package/types/index.d.ts +13 -15
  42. package/types/src/errors.d.ts +73 -17
  43. package/types/src/graph.d.ts +208 -0
  44. package/types/src/nodes/collection.d.ts +64 -0
  45. package/types/src/nodes/effect.d.ts +48 -0
  46. package/types/src/nodes/list.d.ts +65 -0
  47. package/types/src/nodes/memo.d.ts +57 -0
  48. package/types/src/nodes/sensor.d.ts +75 -0
  49. package/types/src/nodes/state.d.ts +78 -0
  50. package/types/src/nodes/store.d.ts +51 -0
  51. package/types/src/nodes/task.d.ts +73 -0
  52. package/types/src/signal.d.ts +43 -29
  53. package/types/src/util.d.ts +9 -16
  54. package/archive/benchmark.ts +0 -683
  55. package/archive/collection.ts +0 -253
  56. package/archive/composite.ts +0 -85
  57. package/archive/computed.ts +0 -195
  58. package/archive/list.ts +0 -483
  59. package/archive/memo.ts +0 -139
  60. package/archive/state.ts +0 -90
  61. package/archive/store.ts +0 -298
  62. package/archive/task.ts +0 -189
  63. package/src/classes/collection.ts +0 -245
  64. package/src/classes/computed.ts +0 -349
  65. package/src/classes/list.ts +0 -343
  66. package/src/classes/ref.ts +0 -70
  67. package/src/classes/state.ts +0 -102
  68. package/src/classes/store.ts +0 -262
  69. package/src/diff.ts +0 -138
  70. package/src/effect.ts +0 -93
  71. package/src/match.ts +0 -45
  72. package/src/resolve.ts +0 -49
  73. package/src/system.ts +0 -257
  74. package/test/computed.test.ts +0 -1108
  75. package/test/diff.test.ts +0 -955
  76. package/test/match.test.ts +0 -388
  77. package/test/ref.test.ts +0 -353
  78. package/test/resolve.test.ts +0 -154
  79. package/types/src/classes/collection.d.ts +0 -45
  80. package/types/src/classes/computed.d.ts +0 -94
  81. package/types/src/classes/list.d.ts +0 -43
  82. package/types/src/classes/ref.d.ts +0 -35
  83. package/types/src/classes/state.d.ts +0 -49
  84. package/types/src/classes/store.d.ts +0 -52
  85. package/types/src/diff.d.ts +0 -28
  86. package/types/src/effect.d.ts +0 -15
  87. package/types/src/match.d.ts +0 -21
  88. package/types/src/resolve.d.ts +0 -29
  89. package/types/src/system.d.ts +0 -78
package/test/ref.test.ts DELETED
@@ -1,353 +0,0 @@
1
- import { expect, mock, test } from 'bun:test'
2
- import { isRef, Ref } from '../src/classes/ref'
3
- import { createEffect } from '../src/effect'
4
-
5
- test('Ref - basic functionality', () => {
6
- const obj = { name: 'test', value: 42 }
7
- const ref = new Ref(obj)
8
-
9
- expect(ref.get()).toBe(obj)
10
- expect(ref[Symbol.toStringTag]).toBe('Ref')
11
- })
12
-
13
- test('Ref - isRef type guard', () => {
14
- const ref = new Ref({ test: true })
15
- const notRef = { test: true }
16
-
17
- expect(isRef(ref)).toBe(true)
18
- expect(isRef(notRef)).toBe(false)
19
- expect(isRef(null)).toBe(false)
20
- expect(isRef(undefined)).toBe(false)
21
- })
22
-
23
- test('Ref - validation with guard function', () => {
24
- const isConfig = (
25
- value: unknown,
26
- ): value is { host: string; port: number } =>
27
- typeof value === 'object' &&
28
- value !== null &&
29
- 'host' in value &&
30
- 'port' in value &&
31
- typeof value.host === 'string' &&
32
- typeof value.port === 'number'
33
-
34
- const validConfig = { host: 'localhost', port: 3000 }
35
- const invalidConfig = { host: 'localhost' } // missing port
36
-
37
- expect(() => new Ref(validConfig, { guard: isConfig })).not.toThrow()
38
- expect(() => new Ref(invalidConfig, { guard: isConfig })).toThrow()
39
- })
40
-
41
- test('Ref - reactive subscriptions', () => {
42
- const server = { status: 'offline', connections: 0 }
43
- const ref = new Ref(server)
44
-
45
- let effectRunCount = 0
46
- let lastStatus: string = ''
47
-
48
- createEffect(() => {
49
- const current = ref.get()
50
- lastStatus = current.status
51
- effectRunCount++
52
- })
53
-
54
- expect(effectRunCount).toBe(1)
55
- expect(lastStatus).toBe('offline')
56
-
57
- // Simulate external change without going through reactive system
58
- server.status = 'online'
59
- server.connections = 5
60
-
61
- // Effect shouldn't re-run yet (reference hasn't changed)
62
- expect(effectRunCount).toBe(1)
63
-
64
- // Notify that the external object has changed
65
- ref.notify()
66
-
67
- expect(effectRunCount).toBe(2)
68
- expect(lastStatus).toBe('online')
69
- })
70
-
71
- test('Ref - notify triggers watchers even with same reference', () => {
72
- const fileObj = { path: '/test.txt', size: 100, modified: Date.now() }
73
- const ref = new Ref(fileObj)
74
-
75
- const mockCallback = mock(() => {})
76
-
77
- createEffect(() => {
78
- ref.get()
79
- mockCallback()
80
- })
81
-
82
- expect(mockCallback).toHaveBeenCalledTimes(1)
83
-
84
- // Simulate file modification (same object reference, different content)
85
- fileObj.size = 200
86
- fileObj.modified = Date.now()
87
-
88
- // Notify about external change
89
- ref.notify()
90
-
91
- expect(mockCallback).toHaveBeenCalledTimes(2)
92
-
93
- // Multiple notifies should trigger multiple times
94
- ref.notify()
95
- expect(mockCallback).toHaveBeenCalledTimes(3)
96
- })
97
-
98
- test('Ref - multiple effects with same ref', () => {
99
- const database = { connected: false, queries: 0 }
100
- const ref = new Ref(database)
101
-
102
- const effect1Mock = mock(() => {})
103
- const effect2Mock = mock((_connected: boolean) => {})
104
-
105
- createEffect(() => {
106
- ref.get()
107
- effect1Mock()
108
- })
109
-
110
- createEffect(() => {
111
- const db = ref.get()
112
- effect2Mock(db.connected)
113
- })
114
-
115
- expect(effect1Mock).toHaveBeenCalledTimes(1)
116
- expect(effect2Mock).toHaveBeenCalledTimes(1)
117
- expect(effect2Mock).toHaveBeenCalledWith(false)
118
-
119
- // Simulate database connection change
120
- database.connected = true
121
- database.queries = 10
122
- ref.notify()
123
-
124
- expect(effect1Mock).toHaveBeenCalledTimes(2)
125
- expect(effect2Mock).toHaveBeenCalledTimes(2)
126
- expect(effect2Mock).toHaveBeenLastCalledWith(true)
127
- })
128
-
129
- test('Ref - with Bun.file() scenario', () => {
130
- // Mock a file-like object that could change externally
131
- const fileRef = {
132
- name: 'config.json',
133
- size: 1024,
134
- lastModified: Date.now(),
135
- // Simulate file methods
136
- exists: () => true,
137
- text: () => Promise.resolve('{"version": "1.0"}'),
138
- }
139
-
140
- const ref = new Ref(fileRef)
141
-
142
- let sizeChanges = 0
143
- createEffect(() => {
144
- const file = ref.get()
145
- if (file.size > 1000) sizeChanges++
146
- })
147
-
148
- expect(sizeChanges).toBe(1) // Initial run
149
-
150
- // Simulate file growing (external change)
151
- fileRef.size = 2048
152
- fileRef.lastModified = Date.now()
153
- ref.notify()
154
-
155
- expect(sizeChanges).toBe(2) // Effect re-ran and condition still met
156
-
157
- // Simulate file shrinking
158
- fileRef.size = 500
159
- ref.notify()
160
-
161
- expect(sizeChanges).toBe(2) // Effect re-ran but condition no longer met
162
- })
163
-
164
- test('Ref - validation errors', () => {
165
- // @ts-expect-error deliberatly provoked error
166
- expect(() => new Ref(null)).toThrow()
167
- // @ts-expect-error deliberatly provoked error
168
- expect(() => new Ref(undefined)).toThrow()
169
- })
170
-
171
- test('Ref - server config object scenario', () => {
172
- const config = {
173
- host: 'localhost',
174
- port: 3000,
175
- ssl: false,
176
- maxConnections: 100,
177
- }
178
-
179
- const configRef = new Ref(config)
180
- const connectionAttempts: string[] = []
181
-
182
- createEffect(() => {
183
- const cfg = configRef.get()
184
- const protocol = cfg.ssl ? 'https' : 'http'
185
- connectionAttempts.push(`${protocol}://${cfg.host}:${cfg.port}`)
186
- })
187
-
188
- expect(connectionAttempts).toEqual(['http://localhost:3000'])
189
-
190
- // Simulate config reload from file/environment
191
- config.ssl = true
192
- config.port = 8443
193
- configRef.notify()
194
-
195
- expect(connectionAttempts).toEqual([
196
- 'http://localhost:3000',
197
- 'https://localhost:8443',
198
- ])
199
- })
200
-
201
- test('Ref - handles complex nested objects', () => {
202
- const apiResponse = {
203
- status: 200,
204
- data: {
205
- users: [{ id: 1, name: 'Alice' }],
206
- pagination: { page: 1, total: 1 },
207
- },
208
- headers: { 'content-type': 'application/json' },
209
- }
210
-
211
- const ref = new Ref(apiResponse)
212
- let userCount = 0
213
-
214
- createEffect(() => {
215
- const response = ref.get()
216
- userCount = response.data.users.length
217
- })
218
-
219
- expect(userCount).toBe(1)
220
-
221
- // Simulate API response update
222
- apiResponse.data.users.push({ id: 2, name: 'Bob' })
223
- apiResponse.data.pagination.total = 2
224
- ref.notify()
225
-
226
- expect(userCount).toBe(2)
227
- })
228
-
229
- test('Ref - options.watched lazy resource management', async () => {
230
- // 1. Create Ref with current Date
231
- let counter = 0
232
- let intervalId: Timer | undefined
233
- const ref = new Ref(new Date(), {
234
- watched: () => {
235
- intervalId = setInterval(() => {
236
- counter++
237
- }, 10)
238
- },
239
- unwatched: () => {
240
- if (intervalId) {
241
- clearInterval(intervalId)
242
- intervalId = undefined
243
- }
244
- },
245
- })
246
-
247
- // 2. Counter should not be running yet
248
- expect(counter).toBe(0)
249
-
250
- // Wait a bit to ensure counter doesn't increment
251
- await new Promise(resolve => setTimeout(resolve, 50))
252
- expect(counter).toBe(0)
253
- expect(intervalId).toBeUndefined()
254
-
255
- // 3. Effect subscribes by .get()ting the signal value
256
- const effectCleanup = createEffect(() => {
257
- ref.get()
258
- })
259
-
260
- // 4. Counter should now be running
261
- await new Promise(resolve => setTimeout(resolve, 50))
262
- expect(counter).toBeGreaterThan(0)
263
- expect(intervalId).toBeDefined()
264
-
265
- // 5. Call effect cleanup, which should stop internal watcher and unsubscribe
266
- effectCleanup()
267
- const counterAfterStop = counter
268
-
269
- // 6. Ref signal should call #unwatch() and counter should stop incrementing
270
- await new Promise(resolve => setTimeout(resolve, 50))
271
- expect(counter).toBe(counterAfterStop) // Counter should not have incremented
272
- expect(intervalId).toBeUndefined() // Interval should be cleared
273
- })
274
-
275
- test('Ref - options.watched exception handling', async () => {
276
- const ref = new Ref(
277
- { test: 'value' },
278
- {
279
- watched: () => {
280
- throwingCallbackCalled = true
281
- throw new Error('Test error in watched callback')
282
- },
283
- },
284
- )
285
-
286
- // Mock console.error to capture error logs
287
- const originalError = console.error
288
- const errorSpy = mock(() => {})
289
- console.error = errorSpy
290
-
291
- let throwingCallbackCalled = false
292
-
293
- // Subscribe to trigger watched callback
294
- const effectCleanup = createEffect(() => {
295
- ref.get()
296
- })
297
-
298
- // Both callbacks should have been called despite the exception
299
- expect(throwingCallbackCalled).toBe(true)
300
-
301
- // Error should have been logged
302
- expect(errorSpy).toHaveBeenCalledWith(
303
- 'Error in effect callback:',
304
- expect.any(Error),
305
- )
306
-
307
- // Cleanup
308
- effectCleanup()
309
- console.error = originalError
310
- })
311
-
312
- test('Ref - options.unwatched exception handling', async () => {
313
- const ref = new Ref(
314
- { test: 'value' },
315
- {
316
- watched: () => {},
317
- unwatched: () => {
318
- cleanup1Called = true
319
- throw new Error('Test error in cleanup function')
320
- },
321
- },
322
- )
323
-
324
- // Mock console.error to capture error logs
325
- const originalError = console.error
326
- const errorSpy = mock(() => {})
327
- console.error = errorSpy
328
-
329
- let cleanup1Called = false
330
-
331
- // Subscribe and then unsubscribe to trigger cleanup
332
- const effectCleanup = createEffect(() => {
333
- ref.get()
334
- })
335
-
336
- // Unsubscribe to trigger cleanup functions
337
- effectCleanup()
338
-
339
- // Wait a bit for cleanup to complete
340
- await new Promise(resolve => setTimeout(resolve, 10))
341
-
342
- // Both cleanup functions should have been called despite the exception
343
- expect(cleanup1Called).toBe(true)
344
-
345
- // Error should have been logged
346
- expect(errorSpy).toHaveBeenCalledWith(
347
- 'Error in effect cleanup:',
348
- expect.any(Error),
349
- )
350
-
351
- // Cleanup
352
- console.error = originalError
353
- })
@@ -1,154 +0,0 @@
1
- import { describe, expect, test } from 'bun:test'
2
- import { Memo, resolve, State, Task, UNSET } from '../index.ts'
3
-
4
- /* === Tests === */
5
-
6
- describe('Resolve Function', () => {
7
- test('should return discriminated union for successful resolution', () => {
8
- const a = new State(10)
9
- const b = new State('hello')
10
-
11
- const result = resolve({ a, b })
12
-
13
- expect(result.ok).toBe(true)
14
- if (result.ok) {
15
- expect(result.values.a).toBe(10)
16
- expect(result.values.b).toBe('hello')
17
- expect(result.errors).toBeUndefined()
18
- expect(result.pending).toBeUndefined()
19
- }
20
- })
21
-
22
- test('should return discriminated union for pending signals', () => {
23
- const a = new State(10)
24
- const b = new State(UNSET)
25
-
26
- const result = resolve({ a, b })
27
-
28
- expect(result.ok).toBe(false)
29
- expect(result.pending).toBe(true)
30
- expect(result.values).toBeUndefined()
31
- expect(result.errors).toBeUndefined()
32
- })
33
-
34
- test('should return discriminated union for error signals', () => {
35
- const a = new State(10)
36
- const b = new Memo(() => {
37
- throw new Error('Test error')
38
- })
39
-
40
- const result = resolve({ a, b })
41
-
42
- expect(result.ok).toBe(false)
43
- expect(result.pending).toBeUndefined()
44
- expect(result.values).toBeUndefined()
45
- expect(result.errors).toBeDefined()
46
- if (result.errors) {
47
- expect(result.errors[0].message).toBe('Test error')
48
- }
49
- })
50
-
51
- test('should handle mixed error and valid signals', () => {
52
- const valid = new State('valid')
53
- const error1 = new Memo(() => {
54
- throw new Error('Error 1')
55
- })
56
- const error2 = new Memo(() => {
57
- throw new Error('Error 2')
58
- })
59
-
60
- const result = resolve({ valid, error1, error2 })
61
-
62
- expect(result.ok).toBe(false)
63
- expect(result.errors).toBeDefined()
64
- if (result.errors) {
65
- expect(result.errors).toHaveLength(2)
66
- expect(result.errors[0].message).toBe('Error 1')
67
- expect(result.errors[1].message).toBe('Error 2')
68
- }
69
- })
70
-
71
- test('should prioritize pending over errors', () => {
72
- const pending = new State(UNSET)
73
- const error = new Memo(() => {
74
- throw new Error('Test error')
75
- })
76
-
77
- const result = resolve({ pending, error })
78
-
79
- expect(result.ok).toBe(false)
80
- expect(result.pending).toBe(true)
81
- expect(result.errors).toBeUndefined()
82
- })
83
-
84
- test('should handle empty signals object', () => {
85
- const result = resolve({})
86
-
87
- expect(result.ok).toBe(true)
88
- if (result.ok) {
89
- expect(result.values).toEqual({})
90
- }
91
- })
92
-
93
- test('should handle complex nested object signals', () => {
94
- const user = new State({ name: 'Alice', age: 25 })
95
- const settings = new State({ theme: 'dark', lang: 'en' })
96
-
97
- const result = resolve({ user, settings })
98
-
99
- expect(result.ok).toBe(true)
100
- if (result.ok) {
101
- expect(result.values.user.name).toBe('Alice')
102
- expect(result.values.user.age).toBe(25)
103
- expect(result.values.settings.theme).toBe('dark')
104
- expect(result.values.settings.lang).toBe('en')
105
- }
106
- })
107
-
108
- test('should handle async computed signals that resolve', async () => {
109
- const wait = (ms: number) =>
110
- new Promise(resolve => setTimeout(resolve, ms))
111
-
112
- const asyncSignal = new Task(async () => {
113
- await wait(10)
114
- return 'async result'
115
- })
116
-
117
- // Initially should be pending
118
- let result = resolve({ asyncSignal })
119
- expect(result.ok).toBe(false)
120
- expect(result.pending).toBe(true)
121
-
122
- // Wait for resolution
123
- await wait(20)
124
-
125
- result = resolve({ asyncSignal })
126
- expect(result.ok).toBe(true)
127
- if (result.ok) expect(result.values.asyncSignal).toBe('async result')
128
- })
129
-
130
- test('should handle async computed signals that error', async () => {
131
- const wait = (ms: number) =>
132
- new Promise(resolve => setTimeout(resolve, ms))
133
-
134
- const asyncError = new Task(async () => {
135
- await wait(10)
136
- throw new Error('Async error')
137
- })
138
-
139
- // Initially should be pending
140
- let result = resolve({ asyncError })
141
- expect(result.ok).toBe(false)
142
- expect(result.pending).toBe(true)
143
-
144
- // Wait for error
145
- await wait(20)
146
-
147
- result = resolve({ asyncError })
148
- expect(result.ok).toBe(false)
149
- expect(result.errors).toBeDefined()
150
- if (result.errors) {
151
- expect(result.errors[0].message).toBe('Async error')
152
- }
153
- })
154
- })
@@ -1,45 +0,0 @@
1
- import type { Signal } from '../signal';
2
- import { type SignalOptions } from '../system';
3
- import { type Computed } from './computed';
4
- import { type List } from './list';
5
- type CollectionSource<T extends {}> = List<T> | Collection<T>;
6
- type CollectionCallback<T extends {}, U extends {}> = ((sourceValue: U) => T) | ((sourceValue: U, abort: AbortSignal) => Promise<T>);
7
- type Collection<T extends {}> = {
8
- readonly [Symbol.toStringTag]: 'Collection';
9
- readonly [Symbol.isConcatSpreadable]: true;
10
- [Symbol.iterator](): IterableIterator<Signal<T>>;
11
- keys(): IterableIterator<string>;
12
- get: () => T[];
13
- at: (index: number) => Signal<T> | undefined;
14
- byKey: (key: string) => Signal<T> | undefined;
15
- keyAt: (index: number) => string | undefined;
16
- indexOfKey: (key: string) => number | undefined;
17
- deriveCollection: <R extends {}>(callback: CollectionCallback<R, T>) => DerivedCollection<R, T>;
18
- readonly length: number;
19
- };
20
- declare const TYPE_COLLECTION: "Collection";
21
- declare class DerivedCollection<T extends {}, U extends {}> implements Collection<T> {
22
- #private;
23
- constructor(source: CollectionSource<U> | (() => CollectionSource<U>), callback: CollectionCallback<T, U>, options?: SignalOptions<T[]>);
24
- get [Symbol.toStringTag](): 'Collection';
25
- get [Symbol.isConcatSpreadable](): true;
26
- [Symbol.iterator](): IterableIterator<Computed<T>>;
27
- keys(): IterableIterator<string>;
28
- get(): T[];
29
- at(index: number): Computed<T> | undefined;
30
- byKey(key: string): Computed<T> | undefined;
31
- keyAt(index: number): string | undefined;
32
- indexOfKey(key: string): number;
33
- deriveCollection<R extends {}>(callback: (sourceValue: T) => R, options?: SignalOptions<R[]>): DerivedCollection<R, T>;
34
- deriveCollection<R extends {}>(callback: (sourceValue: T, abort: AbortSignal) => Promise<R>, options?: SignalOptions<R[]>): DerivedCollection<R, T>;
35
- get length(): number;
36
- }
37
- /**
38
- * Check if a value is a collection signal
39
- *
40
- * @since 0.17.2
41
- * @param {unknown} value - Value to check
42
- * @returns {boolean} - True if value is a collection signal, false otherwise
43
- */
44
- declare const isCollection: <T extends {}>(value: unknown) => value is Collection<T>;
45
- export { type Collection, type CollectionSource, type CollectionCallback, DerivedCollection, isCollection, TYPE_COLLECTION, };
@@ -1,94 +0,0 @@
1
- import { type SignalOptions } from '../system';
2
- type Computed<T extends {}> = {
3
- readonly [Symbol.toStringTag]: 'Computed';
4
- get(): T;
5
- };
6
- type ComputedOptions<T extends {}> = SignalOptions<T> & {
7
- initialValue?: T;
8
- };
9
- type MemoCallback<T extends {} & {
10
- then?: undefined;
11
- }> = (oldValue: T) => T;
12
- type TaskCallback<T extends {} & {
13
- then?: undefined;
14
- }> = (oldValue: T, abort: AbortSignal) => Promise<T>;
15
- declare const TYPE_COMPUTED: "Computed";
16
- /**
17
- * Create a new memoized signal for a synchronous function.
18
- *
19
- * @since 0.17.0
20
- * @param {MemoCallback<T>} callback - Callback function to compute the memoized value
21
- * @param {T} [initialValue = UNSET] - Initial value of the signal
22
- * @throws {InvalidCallbackError} If the callback is not an sync function
23
- * @throws {InvalidSignalValueError} If the initial value is not valid
24
- */
25
- declare class Memo<T extends {}> {
26
- #private;
27
- constructor(callback: MemoCallback<T>, options?: ComputedOptions<T>);
28
- get [Symbol.toStringTag](): 'Computed';
29
- /**
30
- * Return the memoized value after computing it if necessary.
31
- *
32
- * @returns {T}
33
- * @throws {CircularDependencyError} If a circular dependency is detected
34
- * @throws {Error} If an error occurs during computation
35
- */
36
- get(): T;
37
- }
38
- /**
39
- * Create a new task signals that memoizes the result of an asynchronous function.
40
- *
41
- * @since 0.17.0
42
- * @param {TaskCallback<T>} callback - The asynchronous function to compute the memoized value
43
- * @param {T} [initialValue = UNSET] - Initial value of the signal
44
- * @throws {InvalidCallbackError} If the callback is not an async function
45
- * @throws {InvalidSignalValueError} If the initial value is not valid
46
- */
47
- declare class Task<T extends {}> {
48
- #private;
49
- constructor(callback: TaskCallback<T>, options?: ComputedOptions<T>);
50
- get [Symbol.toStringTag](): 'Computed';
51
- /**
52
- * Return the memoized value after executing the async function if necessary.
53
- *
54
- * @returns {T}
55
- * @throws {CircularDependencyError} If a circular dependency is detected
56
- * @throws {Error} If an error occurs during computation
57
- */
58
- get(): T;
59
- }
60
- /**
61
- * Create a derived signal from existing signals
62
- *
63
- * @since 0.9.0
64
- * @param {MemoCallback<T> | TaskCallback<T>} callback - Computation callback function
65
- * @param {ComputedOptions<T>} options - Optional configuration
66
- */
67
- declare const createComputed: <T extends {}>(callback: TaskCallback<T> | MemoCallback<T>, options?: ComputedOptions<T>) => Task<T> | Memo<T>;
68
- /**
69
- * Check if a value is a computed signal
70
- *
71
- * @since 0.9.0
72
- * @param {unknown} value - Value to check
73
- * @returns {boolean} - True if value is a computed signal, false otherwise
74
- */
75
- declare const isComputed: <T extends {}>(value: unknown) => value is Memo<T>;
76
- /**
77
- * Check if the provided value is a callback that may be used as input for createSignal() to derive a computed state
78
- *
79
- * @since 0.12.0
80
- * @param {unknown} value - Value to check
81
- * @returns {boolean} - True if value is a sync callback, false otherwise
82
- */
83
- declare const isMemoCallback: <T extends {} & {
84
- then?: undefined;
85
- }>(value: unknown) => value is MemoCallback<T>;
86
- /**
87
- * Check if the provided value is a callback that may be used as input for createSignal() to derive a computed state
88
- *
89
- * @since 0.17.0
90
- * @param {unknown} value - Value to check
91
- * @returns {boolean} - True if value is an async callback, false otherwise
92
- */
93
- declare const isTaskCallback: <T extends {}>(value: unknown) => value is TaskCallback<T>;
94
- export { TYPE_COMPUTED, createComputed, isComputed, isMemoCallback, isTaskCallback, Memo, Task, type Computed, type ComputedOptions, type MemoCallback, type TaskCallback, };
@@ -1,43 +0,0 @@
1
- import { type UnknownArray } from '../diff';
2
- import { type SignalOptions } from '../system';
3
- import { DerivedCollection } from './collection';
4
- import { State } from './state';
5
- type ArrayToRecord<T extends UnknownArray> = {
6
- [key: string]: T extends Array<infer U extends {}> ? U : never;
7
- };
8
- type KeyConfig<T> = string | ((item: T) => string);
9
- type ListOptions<T extends {}> = SignalOptions<T> & {
10
- keyConfig?: KeyConfig<T>;
11
- };
12
- declare const TYPE_LIST: "List";
13
- declare class List<T extends {}> {
14
- #private;
15
- constructor(initialValue: T[], options?: ListOptions<T>);
16
- get [Symbol.toStringTag](): 'List';
17
- get [Symbol.isConcatSpreadable](): true;
18
- [Symbol.iterator](): IterableIterator<State<T>>;
19
- get length(): number;
20
- get(): T[];
21
- set(newValue: T[]): void;
22
- update(fn: (oldValue: T[]) => T[]): void;
23
- at(index: number): State<T> | undefined;
24
- keys(): IterableIterator<string>;
25
- byKey(key: string): State<T> | undefined;
26
- keyAt(index: number): string | undefined;
27
- indexOfKey(key: string): number;
28
- add(value: T): string;
29
- remove(keyOrIndex: string | number): void;
30
- sort(compareFn?: (a: T, b: T) => number): void;
31
- splice(start: number, deleteCount?: number, ...items: T[]): T[];
32
- deriveCollection<R extends {}>(callback: (sourceValue: T) => R, options?: SignalOptions<R[]>): DerivedCollection<R, T>;
33
- deriveCollection<R extends {}>(callback: (sourceValue: T, abort: AbortSignal) => Promise<R>, options?: SignalOptions<R[]>): DerivedCollection<R, T>;
34
- }
35
- /**
36
- * Check if the provided value is a List instance
37
- *
38
- * @since 0.15.0
39
- * @param {unknown} value - Value to check
40
- * @returns {boolean} - True if the value is a List instance, false otherwise
41
- */
42
- declare const isList: <T extends {}>(value: unknown) => value is List<T>;
43
- export { isList, List, TYPE_LIST, type ArrayToRecord, type KeyConfig, type ListOptions, };