@zeix/cause-effect 0.17.2 → 0.17.3

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 (50) hide show
  1. package/.ai-context.md +11 -5
  2. package/.github/copilot-instructions.md +1 -1
  3. package/.zed/settings.json +3 -0
  4. package/CLAUDE.md +18 -79
  5. package/README.md +23 -37
  6. package/archive/benchmark.ts +0 -5
  7. package/archive/collection.ts +5 -62
  8. package/archive/composite.ts +85 -0
  9. package/archive/computed.ts +17 -20
  10. package/archive/list.ts +6 -67
  11. package/archive/memo.ts +13 -14
  12. package/archive/store.ts +7 -66
  13. package/archive/task.ts +18 -20
  14. package/index.dev.js +438 -614
  15. package/index.js +1 -1
  16. package/index.ts +8 -19
  17. package/package.json +6 -6
  18. package/src/classes/collection.ts +59 -112
  19. package/src/classes/computed.ts +146 -189
  20. package/src/classes/list.ts +138 -105
  21. package/src/classes/ref.ts +16 -42
  22. package/src/classes/state.ts +16 -45
  23. package/src/classes/store.ts +107 -72
  24. package/src/effect.ts +9 -12
  25. package/src/errors.ts +12 -8
  26. package/src/signal.ts +3 -1
  27. package/src/system.ts +136 -154
  28. package/test/batch.test.ts +4 -11
  29. package/test/benchmark.test.ts +4 -2
  30. package/test/collection.test.ts +46 -306
  31. package/test/computed.test.ts +205 -223
  32. package/test/list.test.ts +35 -303
  33. package/test/ref.test.ts +38 -66
  34. package/test/state.test.ts +6 -12
  35. package/test/store.test.ts +37 -489
  36. package/test/util/dependency-graph.ts +2 -2
  37. package/tsconfig.build.json +11 -0
  38. package/tsconfig.json +5 -7
  39. package/types/index.d.ts +2 -2
  40. package/types/src/classes/collection.d.ts +4 -6
  41. package/types/src/classes/computed.d.ts +17 -37
  42. package/types/src/classes/list.d.ts +8 -6
  43. package/types/src/classes/ref.d.ts +7 -20
  44. package/types/src/classes/state.d.ts +5 -17
  45. package/types/src/classes/store.d.ts +12 -11
  46. package/types/src/errors.d.ts +2 -4
  47. package/types/src/signal.d.ts +3 -2
  48. package/types/src/system.d.ts +41 -44
  49. package/src/classes/composite.ts +0 -171
  50. package/types/src/classes/composite.d.ts +0 -15
@@ -8,7 +8,6 @@ import {
8
8
  State,
9
9
  UNSET,
10
10
  } from '../index.ts'
11
- import { HOOK_WATCH } from '../src/system'
12
11
 
13
12
  describe('store', () => {
14
13
  describe('creation and basic operations', () => {
@@ -226,114 +225,6 @@ describe('store', () => {
226
225
  })
227
226
  })
228
227
 
229
- describe('Hooks', () => {
230
- test('triggers HOOK_ADD when properties are added', () => {
231
- let addedKeys: readonly string[] | undefined
232
- const user = createStore<{ name: string; email?: string }>({
233
- name: 'John',
234
- })
235
- user.on('add', add => {
236
- addedKeys = add
237
- })
238
- user.add('email', 'john@example.com')
239
- expect(addedKeys).toContain('email')
240
- })
241
-
242
- test('triggers HOOK_CHANGE when properties are modified', () => {
243
- const user = createStore({ name: 'John' })
244
- let changedKeys: readonly string[] | undefined
245
- user.on('change', change => {
246
- changedKeys = change
247
- })
248
- user.name.set('Jane')
249
- expect(changedKeys).toContain('name')
250
- })
251
-
252
- test('triggers HOOK_CHANGE for nested property changes', () => {
253
- const user = createStore({
254
- preferences: {
255
- theme: 'light',
256
- },
257
- })
258
- let changedKeys: readonly string[] | undefined
259
- user.on('change', change => {
260
- changedKeys = change
261
- })
262
- user.preferences.theme.set('dark')
263
- expect(changedKeys).toContain('preferences')
264
- })
265
-
266
- test('triggers HOOK_REMOVE when properties are removed', () => {
267
- const user = createStore({
268
- name: 'John',
269
- email: 'john@example.com',
270
- })
271
- let removedKeys: readonly string[] | undefined
272
- user.on('remove', remove => {
273
- removedKeys = remove
274
- })
275
- user.remove('email')
276
- expect(removedKeys).toContain('email')
277
- })
278
-
279
- test('set() correctly handles mixed changes, additions, and removals', () => {
280
- const user = createStore<{
281
- name: string
282
- email?: string
283
- preferences: { theme?: string }
284
- age?: number
285
- }>({
286
- name: 'John',
287
- email: 'john@example.com',
288
- preferences: {
289
- theme: 'light',
290
- },
291
- })
292
-
293
- let changedKeys: readonly string[] | undefined
294
- let addedKeys: readonly string[] | undefined
295
- let removedKeys: readonly string[] | undefined
296
-
297
- user.on('change', change => {
298
- changedKeys = change
299
- })
300
- user.on('add', add => {
301
- addedKeys = add
302
- })
303
- user.on('remove', remove => {
304
- removedKeys = remove
305
- })
306
-
307
- user.set({
308
- name: 'Jane',
309
- preferences: {
310
- theme: 'dark',
311
- },
312
- age: 30,
313
- })
314
-
315
- expect(changedKeys).toContain('name')
316
- expect(changedKeys).toContain('preferences')
317
- expect(addedKeys).toContain('age')
318
- expect(removedKeys).toContain('email')
319
- })
320
-
321
- test('hooks can be removed', () => {
322
- const user = createStore({ name: 'John' })
323
- let notificationCount = 0
324
- const listener = () => {
325
- notificationCount++
326
- }
327
- const off = user.on('change', listener)
328
- user.name.set('Jane')
329
- expect(notificationCount).toBe(1)
330
-
331
- off()
332
- user.name.set('Bob')
333
- expect(notificationCount).toBe(1)
334
- })
335
- })
336
-
337
228
  describe('reactivity', () => {
338
229
  test('store-level get() is reactive', () => {
339
230
  const user = createStore({
@@ -662,173 +553,39 @@ describe('store', () => {
662
553
  })
663
554
  })
664
555
 
665
- describe('HOOK_WATCH - Store Hierarchy Resource Management', () => {
666
- test('Store HOOK_WATCH triggers for all nested stores when accessing parent', async () => {
667
- const store = createStore({
668
- app: {
669
- database: {
670
- host: 'localhost',
671
- port: 5432,
672
- },
673
- cache: {
674
- ttl: 3600,
675
- },
676
- },
677
- })
678
-
679
- let appCounter = 0
680
- let databaseCounter = 0
681
- let cacheCounter = 0
682
-
683
- const appCleanup = store.app.on(HOOK_WATCH, () => {
684
- appCounter++
685
- return () => {
686
- appCounter--
687
- }
688
- })
689
-
690
- const databaseCleanup = store.app.database.on(HOOK_WATCH, () => {
691
- databaseCounter++
692
- return () => {
693
- databaseCounter--
694
- }
695
- })
696
-
697
- const cacheCleanup = store.app.cache.on(HOOK_WATCH, () => {
698
- cacheCounter++
699
- return () => {
700
- cacheCounter--
701
- }
702
- })
703
-
704
- // Initially no watchers
705
- expect(appCounter).toBe(0)
706
- expect(databaseCounter).toBe(0)
707
- expect(cacheCounter).toBe(0)
708
-
709
- // Access app store - should trigger ALL nested HOOK_WATCH callbacks
710
- const appEffect = createEffect(() => {
711
- store.app.get()
712
- })
713
-
714
- expect(appCounter).toBe(1)
715
- expect(databaseCounter).toBe(1)
716
- expect(cacheCounter).toBe(1)
717
-
718
- // Cleanup should reset all counters
719
- appEffect()
720
- expect(appCounter).toBe(0)
721
- expect(databaseCounter).toBe(0)
722
- expect(cacheCounter).toBe(0)
723
-
724
- appCleanup()
725
- databaseCleanup()
726
- cacheCleanup()
727
- })
728
-
729
- test('Nested store cleanup only happens when all levels are unwatched', async () => {
730
- const store = createStore({
731
- user: {
732
- profile: {
733
- settings: {
734
- theme: 'dark',
556
+ describe('Watch Callbacks', () => {
557
+ test('Root store watched callback triggered only by direct store access', async () => {
558
+ let rootStoreCounter = 0
559
+ let intervalId: Timer | undefined
560
+ const store = createStore(
561
+ {
562
+ user: {
563
+ name: 'John',
564
+ profile: {
565
+ email: 'john@example.com',
735
566
  },
736
567
  },
737
568
  },
738
- })
739
-
740
- let counter = 0
741
- let intervalId: Timer | undefined
742
-
743
- // Add HOOK_WATCH to deepest nested store
744
- const settingsCleanup = store.user.profile.settings.on(
745
- HOOK_WATCH,
746
- () => {
747
- intervalId = setInterval(() => {
748
- counter++
749
- }, 10)
750
-
751
- return () => {
569
+ {
570
+ watched: () => {
571
+ intervalId = setInterval(() => {
572
+ rootStoreCounter++
573
+ }, 10)
574
+ },
575
+ unwatched: () => {
752
576
  if (intervalId) {
753
577
  clearInterval(intervalId)
754
578
  intervalId = undefined
755
579
  }
756
- }
757
- },
758
- )
759
-
760
- expect(counter).toBe(0)
761
-
762
- // Access parent store - should trigger settings HOOK_WATCH
763
- const parentEffect = createEffect(() => {
764
- store.user.get()
765
- })
766
-
767
- await new Promise(resolve => setTimeout(resolve, 50))
768
- expect(counter).toBeGreaterThan(0)
769
- expect(intervalId).toBeDefined()
770
-
771
- // Access intermediate store - settings should still be active
772
- const profileEffect = createEffect(() => {
773
- store.user.profile.get()
774
- })
775
-
776
- const counterAfterProfile = counter
777
- await new Promise(resolve => setTimeout(resolve, 50))
778
- expect(counter).toBeGreaterThan(counterAfterProfile)
779
- expect(intervalId).toBeDefined()
780
-
781
- // Remove parent watcher, but profile watcher still active
782
- parentEffect()
783
-
784
- const counterAfterParentRemoval = counter
785
- await new Promise(resolve => setTimeout(resolve, 50))
786
- expect(counter).toBeGreaterThan(counterAfterParentRemoval)
787
- expect(intervalId).toBeDefined() // Still running
788
-
789
- // Remove profile watcher - now should cleanup
790
- profileEffect()
791
-
792
- const counterAfterAllRemoval = counter
793
- await new Promise(resolve => setTimeout(resolve, 50))
794
- expect(counter).toBe(counterAfterAllRemoval) // Stopped
795
- expect(intervalId).toBeUndefined()
796
-
797
- settingsCleanup()
798
- })
799
-
800
- test('Root store HOOK_WATCH triggered only by direct store access', async () => {
801
- const store = createStore({
802
- user: {
803
- name: 'John',
804
- profile: {
805
- email: 'john@example.com',
806
580
  },
807
581
  },
808
- })
809
-
810
- let rootStoreCounter = 0
811
- let intervalId: Timer | undefined
812
-
813
- // Add HOOK_WATCH callback to root store
814
- const cleanupHookCallback = store.on(HOOK_WATCH, () => {
815
- intervalId = setInterval(() => {
816
- rootStoreCounter++
817
- }, 10)
818
-
819
- return () => {
820
- if (intervalId) {
821
- clearInterval(intervalId)
822
- intervalId = undefined
823
- }
824
- }
825
- })
582
+ )
826
583
 
827
584
  expect(rootStoreCounter).toBe(0)
828
585
  await new Promise(resolve => setTimeout(resolve, 50))
829
586
  expect(rootStoreCounter).toBe(0)
830
587
 
831
- // Access nested property directly - should NOT trigger root HOOK_WATCH
588
+ // Access nested property directly - should NOT trigger root watched callback
832
589
  const nestedEffectCleanup = createEffect(() => {
833
590
  store.user.name.get()
834
591
  })
@@ -837,7 +594,7 @@ describe('store', () => {
837
594
  expect(rootStoreCounter).toBe(0) // Still 0 - nested access doesn't trigger root
838
595
  expect(intervalId).toBeUndefined()
839
596
 
840
- // Access root store directly - should trigger HOOK_WATCH
597
+ // Access root store directly - should trigger watched callback
841
598
  const rootEffectCleanup = createEffect(() => {
842
599
  store.get()
843
600
  })
@@ -851,169 +608,37 @@ describe('store', () => {
851
608
  nestedEffectCleanup()
852
609
  await new Promise(resolve => setTimeout(resolve, 50))
853
610
  expect(intervalId).toBeUndefined()
854
-
855
- cleanupHookCallback()
856
611
  })
857
612
 
858
- test('Each store level manages its own HOOK_WATCH independently', async () => {
859
- const store = createStore({
860
- config: {
861
- database: {
862
- host: 'localhost',
863
- port: 5432,
864
- },
613
+ test('Store property addition/removal affects store watched callback', async () => {
614
+ let usersStoreCounter = 0
615
+ const store = createStore(
616
+ {
617
+ users: {} as Record<string, { name: string }>,
865
618
  },
866
- })
867
-
868
- let rootCounter = 0
869
- let configCounter = 0
870
- let databaseCounter = 0
871
-
872
- // Add HOOK_WATCH to each level
873
- const rootCleanup = store.on(HOOK_WATCH, () => {
874
- rootCounter++
875
- return () => {
876
- rootCounter--
877
- }
878
- })
879
-
880
- const configCleanup = store.config.on(HOOK_WATCH, () => {
881
- configCounter++
882
- return () => {
883
- configCounter--
884
- }
885
- })
886
-
887
- const databaseCleanup = store.config.database.on(HOOK_WATCH, () => {
888
- databaseCounter++
889
- return () => {
890
- databaseCounter--
891
- }
892
- })
893
-
894
- // All should start at 0
895
- expect(rootCounter).toBe(0)
896
- expect(configCounter).toBe(0)
897
- expect(databaseCounter).toBe(0)
898
-
899
- // Access deepest level - should NOT trigger any store HOOK_WATCH
900
- // because we're only accessing the State signal, not calling .get() on stores
901
- const deepEffectCleanup = createEffect(() => {
902
- store.config.database.host.get()
903
- })
904
-
905
- expect(rootCounter).toBe(0)
906
- expect(configCounter).toBe(0)
907
- expect(databaseCounter).toBe(0)
908
-
909
- // Access config level - should trigger config AND database HOOK_WATCH
910
- const configEffectCleanup = createEffect(() => {
911
- store.config.get()
912
- })
913
-
914
- expect(rootCounter).toBe(0)
915
- expect(configCounter).toBe(1)
916
- expect(databaseCounter).toBe(1) // Triggered by parent access
917
-
918
- // Access root level - should trigger root HOOK_WATCH (config/database already active)
919
- const rootEffectCleanup = createEffect(() => {
920
- store.get()
921
- })
922
-
923
- expect(rootCounter).toBe(1)
924
- expect(configCounter).toBe(1)
925
- expect(databaseCounter).toBe(1)
926
-
927
- // Cleanup in reverse order - database should stay active until config is cleaned up
928
- rootEffectCleanup()
929
- expect(rootCounter).toBe(0)
930
- expect(configCounter).toBe(1)
931
- expect(databaseCounter).toBe(1) // Still active due to config watcher
932
-
933
- configEffectCleanup()
934
- expect(rootCounter).toBe(0)
935
- expect(configCounter).toBe(0)
936
- expect(databaseCounter).toBe(0) // Now cleaned up
937
-
938
- deepEffectCleanup()
939
- expect(rootCounter).toBe(0)
940
- expect(configCounter).toBe(0)
941
- expect(databaseCounter).toBe(0)
942
-
943
- // Cleanup hooks
944
- rootCleanup()
945
- configCleanup()
946
- databaseCleanup()
947
- })
948
-
949
- test('Store HOOK_WATCH with multiple watchers at same level', async () => {
950
- const store = createStore({
951
- data: {
952
- items: [] as string[],
953
- count: 0,
619
+ {
620
+ watched: () => {
621
+ usersStoreCounter++
622
+ },
623
+ unwatched: () => {
624
+ usersStoreCounter--
625
+ },
954
626
  },
955
- })
956
-
957
- let dataStoreCounter = 0
958
-
959
- const dataCleanup = store.data.on(HOOK_WATCH, () => {
960
- dataStoreCounter++
961
- return () => {
962
- dataStoreCounter--
963
- }
964
- })
965
-
966
- expect(dataStoreCounter).toBe(0)
967
-
968
- // Create multiple effects watching the data store
969
- const effect1 = createEffect(() => {
970
- store.data.get()
971
- })
972
- const effect2 = createEffect(() => {
973
- store.data.get()
974
- })
975
-
976
- // Should only trigger once (shared resources)
977
- expect(dataStoreCounter).toBe(1)
978
-
979
- // Stop one effect
980
- effect1()
981
- expect(dataStoreCounter).toBe(1) // Still active
982
-
983
- // Stop second effect
984
- effect2()
985
- expect(dataStoreCounter).toBe(0) // Now cleaned up
986
-
987
- dataCleanup()
988
- })
989
-
990
- test('Store property addition/removal affects individual store HOOK_WATCH', async () => {
991
- const store = createStore({
992
- users: {} as Record<string, { name: string }>,
993
- })
994
-
995
- let usersStoreCounter = 0
996
-
997
- const usersCleanup = store.users.on(HOOK_WATCH, () => {
998
- usersStoreCounter++
999
- return () => {
1000
- usersStoreCounter--
1001
- }
1002
- })
627
+ )
1003
628
 
1004
629
  expect(usersStoreCounter).toBe(0)
1005
630
 
1006
- // Watch the users store
631
+ // Watch the entire store
1007
632
  const usersEffect = createEffect(() => {
1008
- store.users.get()
633
+ store.get()
1009
634
  })
1010
635
  expect(usersStoreCounter).toBe(1)
1011
636
 
1012
- // Add a user - this modifies the users store content but doesn't affect HOOK_WATCH
637
+ // Add a user - this modifies the users store content but doesn't affect watched callback
1013
638
  store.users.add('user1', { name: 'Alice' })
1014
639
  expect(usersStoreCounter).toBe(1) // Still 1
1015
640
 
1016
- // Watch a specific user property - this doesn't trigger users store HOOK_WATCH
641
+ // Watch a specific user property - this doesn't trigger users store watched callback
1017
642
  const userEffect = createEffect(() => {
1018
643
  store.users.user1?.name.get()
1019
644
  })
@@ -1026,83 +651,6 @@ describe('store', () => {
1026
651
  // Cleanup users effect
1027
652
  usersEffect()
1028
653
  expect(usersStoreCounter).toBe(0) // Now cleaned up
1029
-
1030
- usersCleanup()
1031
- })
1032
-
1033
- test('Exception handling in store HOOK_WATCH callbacks', async () => {
1034
- const store = createStore({
1035
- config: { theme: 'dark' },
1036
- })
1037
-
1038
- let successfulCallbackCalled = false
1039
- let throwingCallbackCalled = false
1040
-
1041
- // Add throwing callback
1042
- const cleanup1 = store.on(HOOK_WATCH, () => {
1043
- throwingCallbackCalled = true
1044
- throw new Error('Test error in store HOOK_WATCH')
1045
- })
1046
-
1047
- // Add successful callback
1048
- const cleanup2 = store.on(HOOK_WATCH, () => {
1049
- successfulCallbackCalled = true
1050
- return () => {
1051
- // cleanup
1052
- }
1053
- })
1054
-
1055
- // Trigger callbacks through direct store access - should throw
1056
- expect(() => store.get()).toThrow('Test error in store HOOK_WATCH')
1057
-
1058
- // Both callbacks should have been called
1059
- expect(throwingCallbackCalled).toBe(true)
1060
- expect(successfulCallbackCalled).toBe(true)
1061
-
1062
- cleanup1()
1063
- cleanup2()
1064
- })
1065
-
1066
- test('Nested store HOOK_WATCH with computed signals', async () => {
1067
- const store = createStore({
1068
- user: {
1069
- firstName: 'John',
1070
- lastName: 'Doe',
1071
- },
1072
- })
1073
-
1074
- let userStoreCounter = 0
1075
-
1076
- const userCleanup = store.user.on(HOOK_WATCH, () => {
1077
- userStoreCounter++
1078
- return () => {
1079
- userStoreCounter--
1080
- }
1081
- })
1082
-
1083
- expect(userStoreCounter).toBe(0)
1084
-
1085
- // Access user store directly - should trigger user store HOOK_WATCH
1086
- const userEffect = createEffect(() => {
1087
- store.user.get()
1088
- })
1089
- expect(userStoreCounter).toBe(1)
1090
-
1091
- // Access individual properties - should NOT trigger user store HOOK_WATCH again
1092
- const nameEffect = createEffect(() => {
1093
- store.user.firstName.get()
1094
- })
1095
- expect(userStoreCounter).toBe(1) // Still 1
1096
-
1097
- // Cleanup individual property effect first
1098
- nameEffect()
1099
- expect(userStoreCounter).toBe(1) // Still active due to user store effect
1100
-
1101
- // Cleanup user store effect - now should be cleaned up
1102
- userEffect()
1103
- expect(userStoreCounter).toBe(0) // Now cleaned up
1104
-
1105
- userCleanup()
1106
654
  })
1107
655
  })
1108
656
  })
@@ -124,7 +124,7 @@ function makeDependentRows(
124
124
  ): Computed<number>[][] {
125
125
  let prevRow = sources
126
126
  const rand = new Random('seed')
127
- const rows = []
127
+ const rows: Computed<number>[][] = []
128
128
  for (let l = 0; l < numRows; l++) {
129
129
  const row = makeRow(
130
130
  prevRow,
@@ -135,7 +135,7 @@ function makeDependentRows(
135
135
  l,
136
136
  rand,
137
137
  )
138
- rows.push(row as never)
138
+ rows.push(row)
139
139
  prevRow = row
140
140
  }
141
141
  return rows
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": false,
5
+ "declaration": true,
6
+ "declarationDir": "./types",
7
+ "emitDeclarationOnly": true,
8
+ },
9
+ "include": ["./index.ts", "./src/**/*.ts"],
10
+ "exclude": ["node_modules", "types", "test", "archive"],
11
+ }
package/tsconfig.json CHANGED
@@ -13,6 +13,9 @@
13
13
  "allowImportingTsExtensions": true,
14
14
  "verbatimModuleSyntax": true,
15
15
 
16
+ // Editor-only mode - no emit
17
+ "noEmit": true,
18
+
16
19
  // Best practices
17
20
  "strict": true,
18
21
  "skipLibCheck": true,
@@ -22,12 +25,7 @@
22
25
  "noUnusedLocals": false,
23
26
  "noUnusedParameters": false,
24
27
  "noPropertyAccessFromIndexSignature": false,
25
-
26
- // Declarations
27
- "declaration": true,
28
- "declarationDir": "./types",
29
- "emitDeclarationOnly": true
30
28
  },
31
- "include": ["./*.ts", "./src/*.ts"],
32
- "exclude": ["node_modules", "test", "types"]
29
+ "include": ["./**/*.ts"],
30
+ "exclude": ["node_modules", "types"],
33
31
  }
package/types/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @name Cause & Effect
3
- * @version 0.17.2
3
+ * @version 0.17.3
4
4
  * @author Esther Brunner
5
5
  */
6
6
  export { type Collection, type CollectionCallback, type CollectionSource, DerivedCollection, isCollection, TYPE_COLLECTION, } from './src/classes/collection';
@@ -15,5 +15,5 @@ export { CircularDependencyError, createError, DuplicateKeyError, type Guard, gu
15
15
  export { type MatchHandlers, match } from './src/match';
16
16
  export { type ResolveResult, resolve } from './src/resolve';
17
17
  export { createSignal, isMutableSignal, isSignal, type Signal, type SignalValues, type UnknownSignalRecord, } from './src/signal';
18
- export { batchSignalWrites, type Cleanup, createWatcher, flushPendingReactions, HOOK_ADD, HOOK_CHANGE, HOOK_CLEANUP, HOOK_REMOVE, HOOK_SORT, HOOK_WATCH, type Hook, type CleanupHook, type WatchHook, type HookCallback, type HookCallbacks, isHandledHook, notifyWatchers, subscribeActiveWatcher, trackSignalReads, triggerHook, UNSET, type Watcher, } from './src/system';
18
+ export { batch, type Cleanup, createWatcher, flush, notifyOf, type SignalOptions, subscribeTo, track, UNSET, untrack, type Watcher, } from './src/system';
19
19
  export { isAbortError, isAsyncFunction, isFunction, isNumber, isObjectOfType, isRecord, isRecordOrArray, isString, isSymbol, valueString, } from './src/util';
@@ -1,5 +1,5 @@
1
1
  import type { Signal } from '../signal';
2
- import { type Cleanup, type Hook, type HookCallback } from '../system';
2
+ import { type SignalOptions } from '../system';
3
3
  import { type Computed } from './computed';
4
4
  import { type List } from './list';
5
5
  type CollectionSource<T extends {}> = List<T> | Collection<T>;
@@ -14,14 +14,13 @@ type Collection<T extends {}> = {
14
14
  byKey: (key: string) => Signal<T> | undefined;
15
15
  keyAt: (index: number) => string | undefined;
16
16
  indexOfKey: (key: string) => number | undefined;
17
- on: <K extends Hook>(type: K, callback: HookCallback) => Cleanup;
18
17
  deriveCollection: <R extends {}>(callback: CollectionCallback<R, T>) => DerivedCollection<R, T>;
19
18
  readonly length: number;
20
19
  };
21
20
  declare const TYPE_COLLECTION: "Collection";
22
21
  declare class DerivedCollection<T extends {}, U extends {}> implements Collection<T> {
23
22
  #private;
24
- constructor(source: CollectionSource<U> | (() => CollectionSource<U>), callback: CollectionCallback<T, U>);
23
+ constructor(source: CollectionSource<U> | (() => CollectionSource<U>), callback: CollectionCallback<T, U>, options?: SignalOptions<T[]>);
25
24
  get [Symbol.toStringTag](): 'Collection';
26
25
  get [Symbol.isConcatSpreadable](): true;
27
26
  [Symbol.iterator](): IterableIterator<Computed<T>>;
@@ -31,9 +30,8 @@ declare class DerivedCollection<T extends {}, U extends {}> implements Collectio
31
30
  byKey(key: string): Computed<T> | undefined;
32
31
  keyAt(index: number): string | undefined;
33
32
  indexOfKey(key: string): number;
34
- on(type: Hook, callback: HookCallback): Cleanup;
35
- deriveCollection<R extends {}>(callback: (sourceValue: T) => R): DerivedCollection<R, T>;
36
- deriveCollection<R extends {}>(callback: (sourceValue: T, abort: AbortSignal) => Promise<R>): DerivedCollection<R, T>;
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>;
37
35
  get length(): number;
38
36
  }
39
37
  /**