@zeix/cause-effect 0.14.0 → 0.14.2

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/package.json CHANGED
@@ -1,14 +1,13 @@
1
1
  {
2
2
  "name": "@zeix/cause-effect",
3
- "version": "0.14.0",
3
+ "version": "0.14.2",
4
4
  "author": "Esther Brunner",
5
5
  "main": "index.js",
6
6
  "module": "index.ts",
7
7
  "devDependencies": {
8
+ "@biomejs/biome": "2.1.4",
8
9
  "@types/bun": "latest",
9
- "eslint": "^9.27.0",
10
- "random": "^5.4.0",
11
- "typescript-eslint": "^8.32.1"
10
+ "random": "^5.4.1"
12
11
  },
13
12
  "peerDependencies": {
14
13
  "typescript": "^5.6.3"
@@ -25,9 +24,9 @@
25
24
  "access": "public"
26
25
  },
27
26
  "scripts": {
28
- "build": "bun build index.ts --outdir ./ --minify && bunx tsc",
27
+ "build": "bunx tsc && bun build index.ts --outdir ./ --minify && bun build index.ts --outfile index.dev.js",
29
28
  "test": "bun test",
30
- "lint": "bunx eslint src/"
29
+ "lint": "bunx biome lint --write"
31
30
  },
32
31
  "type": "module",
33
32
  "types": "index.d.ts"
package/src/computed.d.ts CHANGED
@@ -1,20 +1,14 @@
1
- import { type MemoCallback } from './memo';
2
- import { type TaskCallback } from './task';
3
1
  type Computed<T extends {}> = {
4
2
  [Symbol.toStringTag]: 'Computed';
5
3
  get(): T;
6
4
  };
7
5
  type ComputedCallback<T extends {} & {
8
- then?: void;
9
- }> = TaskCallback<T> | MemoCallback<T>;
6
+ then?: undefined;
7
+ }> = ((abort: AbortSignal) => Promise<T>) | (() => T);
10
8
  declare const TYPE_COMPUTED = "Computed";
11
9
  /**
12
10
  * Create a derived signal from existing signals
13
11
  *
14
- * This function delegates to either memo() for synchronous computations
15
- * or task() for asynchronous computations, providing better performance
16
- * for each case.
17
- *
18
12
  * @since 0.9.0
19
13
  * @param {ComputedCallback<T>} fn - computation callback function
20
14
  * @returns {Computed<T>} - Computed signal
@@ -28,4 +22,12 @@ declare const computed: <T extends {}>(fn: ComputedCallback<T>) => Computed<T>;
28
22
  * @returns {boolean} - true if value is a computed state, false otherwise
29
23
  */
30
24
  declare const isComputed: <T extends {}>(value: unknown) => value is Computed<T>;
31
- export { type Computed, type ComputedCallback, TYPE_COMPUTED, computed, isComputed, };
25
+ /**
26
+ * Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
27
+ *
28
+ * @since 0.12.0
29
+ * @param {unknown} value - value to check
30
+ * @returns {boolean} - true if value is a callback or callbacks object, false otherwise
31
+ */
32
+ declare const isComputedCallback: <T extends {}>(value: unknown) => value is ComputedCallback<T>;
33
+ export { TYPE_COMPUTED, computed, isComputed, isComputedCallback, type Computed, type ComputedCallback, };
package/src/computed.ts CHANGED
@@ -1,6 +1,18 @@
1
- import { isAsyncFunction, isObjectOfType } from './util'
2
- import { type MemoCallback, memo } from './memo'
3
- import { type TaskCallback, task } from './task'
1
+ import {
2
+ flush,
3
+ notify,
4
+ observe,
5
+ subscribe,
6
+ type Watcher,
7
+ watch,
8
+ } from './scheduler'
9
+ import { UNSET } from './signal'
10
+ import {
11
+ CircularDependencyError,
12
+ isFunction,
13
+ isObjectOfType,
14
+ toError,
15
+ } from './util'
4
16
 
5
17
  /* === Types === */
6
18
 
@@ -8,9 +20,9 @@ type Computed<T extends {}> = {
8
20
  [Symbol.toStringTag]: 'Computed'
9
21
  get(): T
10
22
  }
11
- type ComputedCallback<T extends {} & { then?: void }> =
12
- | TaskCallback<T>
13
- | MemoCallback<T>
23
+ type ComputedCallback<T extends {} & { then?: undefined }> =
24
+ | ((abort: AbortSignal) => Promise<T>)
25
+ | (() => T)
14
26
 
15
27
  /* === Constants === */
16
28
 
@@ -21,16 +33,116 @@ const TYPE_COMPUTED = 'Computed'
21
33
  /**
22
34
  * Create a derived signal from existing signals
23
35
  *
24
- * This function delegates to either memo() for synchronous computations
25
- * or task() for asynchronous computations, providing better performance
26
- * for each case.
27
- *
28
36
  * @since 0.9.0
29
37
  * @param {ComputedCallback<T>} fn - computation callback function
30
38
  * @returns {Computed<T>} - Computed signal
31
39
  */
32
- const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> =>
33
- isAsyncFunction<T>(fn) ? task<T>(fn) : memo<T>(fn as MemoCallback<T>)
40
+ const computed = <T extends {}>(fn: ComputedCallback<T>): Computed<T> => {
41
+ const watchers: Set<Watcher> = new Set()
42
+
43
+ // Internal state
44
+ let value: T = UNSET
45
+ let error: Error | undefined
46
+ let controller: AbortController | undefined
47
+ let dirty = true
48
+ let changed = false
49
+ let computing = false
50
+
51
+ // Functions to update internal state
52
+ const ok = (v: T): undefined => {
53
+ if (!Object.is(v, value)) {
54
+ value = v
55
+ changed = true
56
+ }
57
+ error = undefined
58
+ dirty = false
59
+ }
60
+ const nil = (): undefined => {
61
+ changed = UNSET !== value
62
+ value = UNSET
63
+ error = undefined
64
+ }
65
+ const err = (e: unknown): undefined => {
66
+ const newError = toError(e)
67
+ changed =
68
+ !error ||
69
+ newError.name !== error.name ||
70
+ newError.message !== error.message
71
+ value = UNSET
72
+ error = newError
73
+ }
74
+ const settle =
75
+ <T>(settleFn: (arg: T) => void) =>
76
+ (arg: T) => {
77
+ computing = false
78
+ controller = undefined
79
+ settleFn(arg)
80
+ if (changed) notify(watchers)
81
+ }
82
+
83
+ // Own watcher: called when notified from sources (push)
84
+ const mark = watch(() => {
85
+ dirty = true
86
+ controller?.abort('Aborted because source signal changed')
87
+ if (watchers.size) notify(watchers)
88
+ else mark.cleanup()
89
+ })
90
+
91
+ // Called when requested by dependencies (pull)
92
+ const compute = () =>
93
+ observe(() => {
94
+ if (computing) throw new CircularDependencyError('computed')
95
+ changed = false
96
+ if (isFunction(fn) && fn.constructor.name === 'AsyncFunction') {
97
+ if (controller) return value // return current value until promise resolves
98
+ controller = new AbortController()
99
+ controller.signal.addEventListener(
100
+ 'abort',
101
+ () => {
102
+ computing = false
103
+ controller = undefined
104
+ compute() // retry
105
+ },
106
+ {
107
+ once: true,
108
+ },
109
+ )
110
+ }
111
+ let result: T | Promise<T>
112
+ computing = true
113
+ try {
114
+ result = controller ? fn(controller.signal) : (fn as () => T)()
115
+ } catch (e) {
116
+ if (e instanceof DOMException && e.name === 'AbortError') nil()
117
+ else err(e)
118
+ computing = false
119
+ return
120
+ }
121
+ if (result instanceof Promise) result.then(settle(ok), settle(err))
122
+ else if (null == result || UNSET === result) nil()
123
+ else ok(result)
124
+ computing = false
125
+ }, mark)
126
+
127
+ const c: Computed<T> = {
128
+ [Symbol.toStringTag]: TYPE_COMPUTED,
129
+
130
+ /**
131
+ * Get the current value of the computed
132
+ *
133
+ * @since 0.9.0
134
+ * @returns {T} - current value of the computed
135
+ */
136
+ get: (): T => {
137
+ subscribe(watchers)
138
+ flush()
139
+ if (dirty) compute()
140
+ if (error) throw error
141
+ return value
142
+ },
143
+ }
144
+ return c
145
+ }
34
146
 
35
147
  /**
36
148
  * Check if a value is a computed state
@@ -43,12 +155,24 @@ const isComputed = /*#__PURE__*/ <T extends {}>(
43
155
  value: unknown,
44
156
  ): value is Computed<T> => isObjectOfType(value, TYPE_COMPUTED)
45
157
 
158
+ /**
159
+ * Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
160
+ *
161
+ * @since 0.12.0
162
+ * @param {unknown} value - value to check
163
+ * @returns {boolean} - true if value is a callback or callbacks object, false otherwise
164
+ */
165
+ const isComputedCallback = /*#__PURE__*/ <T extends {}>(
166
+ value: unknown,
167
+ ): value is ComputedCallback<T> => isFunction(value) && value.length < 2
168
+
46
169
  /* === Exports === */
47
170
 
48
171
  export {
49
- type Computed,
50
- type ComputedCallback,
51
172
  TYPE_COMPUTED,
52
173
  computed,
53
174
  isComputed,
175
+ isComputedCallback,
176
+ type Computed,
177
+ type ComputedCallback,
54
178
  }
package/src/effect.d.ts CHANGED
@@ -1,19 +1,17 @@
1
- import { type Signal } from './signal';
2
1
  import { type Cleanup } from './scheduler';
3
- type EffectMatcher<S extends Signal<{}>[]> = {
2
+ import { type Signal, type SignalValues } from './signal';
3
+ type EffectMatcher<S extends Signal<unknown & {}>[]> = {
4
4
  signals: S;
5
- ok: (...values: {
6
- [K in keyof S]: S[K] extends Signal<infer T> ? T : never;
7
- }) => void | Cleanup;
8
- err?: (...errors: Error[]) => void | Cleanup;
9
- nil?: () => void | Cleanup;
5
+ ok: (...values: SignalValues<S>) => Cleanup | undefined;
6
+ err?: (...errors: Error[]) => Cleanup | undefined;
7
+ nil?: () => Cleanup | undefined;
10
8
  };
11
9
  /**
12
10
  * Define what happens when a reactive state changes
13
11
  *
14
12
  * @since 0.1.0
15
- * @param {EffectMatcher<S> | (() => void | Cleanup)} matcher - effect matcher or callback
13
+ * @param {EffectMatcher<S> | (() => Cleanup | undefined)} matcher - effect matcher or callback
16
14
  * @returns {Cleanup} - cleanup function for the effect
17
15
  */
18
- declare function effect<S extends Signal<{}>[]>(matcher: EffectMatcher<S> | (() => void | Cleanup)): Cleanup;
16
+ declare function effect<S extends Signal<unknown & {}>[]>(matcher: EffectMatcher<S> | (() => Cleanup | undefined)): Cleanup;
19
17
  export { type EffectMatcher, effect };
package/src/effect.ts CHANGED
@@ -1,23 +1,14 @@
1
- import { type Signal, UNSET } from './signal'
2
- import {
3
- CircularDependencyError,
4
- isFunction,
5
- toError,
6
- isAbortError,
7
- } from './util'
8
- import { watch, type Cleanup, type Watcher } from './scheduler'
1
+ import { type Cleanup, observe, watch } from './scheduler'
2
+ import { type Signal, type SignalValues, UNSET } from './signal'
3
+ import { CircularDependencyError, isFunction, toError } from './util'
9
4
 
10
5
  /* === Types === */
11
6
 
12
- type EffectMatcher<S extends Signal<{}>[]> = {
7
+ type EffectMatcher<S extends Signal<unknown & {}>[]> = {
13
8
  signals: S
14
- ok: (
15
- ...values: {
16
- [K in keyof S]: S[K] extends Signal<infer T> ? T : never
17
- }
18
- ) => void | Cleanup
19
- err?: (...errors: Error[]) => void | Cleanup
20
- nil?: () => void | Cleanup
9
+ ok: (...values: SignalValues<S>) => Cleanup | undefined
10
+ err?: (...errors: Error[]) => Cleanup | undefined
11
+ nil?: () => Cleanup | undefined
21
12
  }
22
13
 
23
14
  /* === Functions === */
@@ -26,68 +17,62 @@ type EffectMatcher<S extends Signal<{}>[]> = {
26
17
  * Define what happens when a reactive state changes
27
18
  *
28
19
  * @since 0.1.0
29
- * @param {EffectMatcher<S> | (() => void | Cleanup)} matcher - effect matcher or callback
20
+ * @param {EffectMatcher<S> | (() => Cleanup | undefined)} matcher - effect matcher or callback
30
21
  * @returns {Cleanup} - cleanup function for the effect
31
22
  */
32
- function effect<S extends Signal<{}>[]>(
33
- matcher: EffectMatcher<S> | (() => void | Cleanup),
23
+ function effect<S extends Signal<unknown & {}>[]>(
24
+ matcher: EffectMatcher<S> | (() => Cleanup | undefined),
34
25
  ): Cleanup {
35
26
  const {
36
27
  signals,
37
28
  ok,
38
- err = console.error,
39
- nil = () => {},
29
+ err = (error: Error): undefined => {
30
+ console.error(error)
31
+ },
32
+ nil = (): undefined => {},
40
33
  } = isFunction(matcher)
41
34
  ? { signals: [] as unknown as S, ok: matcher }
42
35
  : matcher
36
+
43
37
  let running = false
44
- const run = (() =>
45
- watch(() => {
38
+ const run = watch(() =>
39
+ observe(() => {
46
40
  if (running) throw new CircularDependencyError('effect')
47
41
  running = true
48
- let cleanup: void | Cleanup = undefined
49
- try {
50
- const errors: Error[] = []
51
- let suspense = false
52
- const values = signals.map(signal => {
53
- try {
54
- const value = signal.get()
55
- if (value === UNSET) suspense = true
56
- return value
57
- } catch (e) {
58
- if (isAbortError(e)) throw e
59
- errors.push(toError(e))
60
- return UNSET
61
- }
62
- }) as {
63
- [K in keyof S]: S[K] extends Signal<infer T extends {}>
64
- ? T
65
- : never
66
- }
67
42
 
43
+ // Pure part
44
+ const errors: Error[] = []
45
+ let pending = false
46
+ const values = signals.map(signal => {
68
47
  try {
69
- cleanup = suspense
70
- ? nil()
71
- : errors.length
72
- ? err(...errors)
73
- : ok(...values)
48
+ const value = signal.get()
49
+ if (value === UNSET) pending = true
50
+ return value
74
51
  } catch (e) {
75
- if (isAbortError(e)) throw e
76
- const error = toError(e)
77
- cleanup = err(error)
52
+ errors.push(toError(e))
53
+ return UNSET
78
54
  }
55
+ }) as SignalValues<S>
56
+
57
+ // Effectful part
58
+ let cleanup: Cleanup | undefined
59
+ try {
60
+ cleanup = pending
61
+ ? nil()
62
+ : errors.length
63
+ ? err(...errors)
64
+ : ok(...values)
79
65
  } catch (e) {
80
- err(toError(e))
66
+ cleanup = err(toError(e))
67
+ } finally {
68
+ if (isFunction(cleanup)) run.off(cleanup)
81
69
  }
82
- if (isFunction(cleanup)) run.cleanups.add(cleanup)
70
+
83
71
  running = false
84
- }, run)) as Watcher
85
- run.cleanups = new Set<Cleanup>()
72
+ }, run),
73
+ )
86
74
  run()
87
- return () => {
88
- run.cleanups.forEach(fn => fn())
89
- run.cleanups.clear()
90
- }
75
+ return () => run.cleanup()
91
76
  }
92
77
 
93
78
  /* === Exports === */
@@ -1,9 +1,18 @@
1
1
  type Cleanup = () => void;
2
2
  type Watcher = {
3
3
  (): void;
4
- cleanups: Set<Cleanup>;
4
+ off(cleanup: Cleanup): void;
5
+ cleanup(): void;
5
6
  };
6
- type Updater = <T>() => T | boolean | void;
7
+ type Updater = <T>() => T | boolean | undefined;
8
+ /**
9
+ * Create a watcher that can be used to observe changes to a signal
10
+ *
11
+ * @since 0.14.1
12
+ * @param {() => void} notice - function to be called when the state changes
13
+ * @returns {Watcher} - watcher object with off and cleanup methods
14
+ */
15
+ declare const watch: (notice: () => void) => Watcher;
7
16
  /**
8
17
  * Add active watcher to the Set of watchers
9
18
  *
@@ -30,9 +39,9 @@ declare const batch: (fn: () => void) => void;
30
39
  * Run a function in a reactive context
31
40
  *
32
41
  * @param {() => void} run - function to run the computation or effect
33
- * @param {Watcher} mark - function to be called when the state changes or undefined for temporary unwatching while inserting auto-hydrating DOM nodes that might read signals (e.g., web components)
42
+ * @param {Watcher} watcher - function to be called when the state changes or undefined for temporary unwatching while inserting auto-hydrating DOM nodes that might read signals (e.g., web components)
34
43
  */
35
- declare const watch: (run: () => void, mark?: Watcher) => void;
44
+ declare const observe: (run: () => void, watcher?: Watcher) => void;
36
45
  /**
37
46
  * Enqueue a function to be executed on the next animation frame
38
47
  *
@@ -42,5 +51,5 @@ declare const watch: (run: () => void, mark?: Watcher) => void;
42
51
  * @param {Updater} fn - function to be executed on the next animation frame; can return updated value <T>, success <boolean> or void
43
52
  * @param {symbol} dedupe - Symbol for deduplication; if not provided, a unique Symbol is created ensuring the update is always executed
44
53
  */
45
- declare const enqueue: <T>(fn: Updater, dedupe?: symbol) => Promise<boolean | void | T>;
46
- export { type Cleanup, type Watcher, type Updater, subscribe, notify, flush, batch, watch, enqueue, };
54
+ declare const enqueue: <T>(fn: Updater, dedupe?: symbol) => Promise<boolean | T | undefined>;
55
+ export { type Cleanup, type Watcher, type Updater, subscribe, notify, flush, batch, watch, observe, enqueue, };
package/src/scheduler.ts CHANGED
@@ -4,10 +4,11 @@ type Cleanup = () => void
4
4
 
5
5
  type Watcher = {
6
6
  (): void
7
- cleanups: Set<Cleanup>
7
+ off(cleanup: Cleanup): void
8
+ cleanup(): void
8
9
  }
9
10
 
10
- type Updater = <T>() => T | boolean | void
11
+ type Updater = <T>() => T | boolean | undefined
11
12
 
12
13
  /* === Internal === */
13
14
 
@@ -26,8 +27,8 @@ const updateDOM = () => {
26
27
  requestId = undefined
27
28
  const updates = Array.from(updateMap.values())
28
29
  updateMap.clear()
29
- for (const fn of updates) {
30
- fn()
30
+ for (const update of updates) {
31
+ update()
31
32
  }
32
33
  }
33
34
 
@@ -41,17 +42,38 @@ queueMicrotask(updateDOM)
41
42
 
42
43
  /* === Functions === */
43
44
 
45
+ /**
46
+ * Create a watcher that can be used to observe changes to a signal
47
+ *
48
+ * @since 0.14.1
49
+ * @param {() => void} notice - function to be called when the state changes
50
+ * @returns {Watcher} - watcher object with off and cleanup methods
51
+ */
52
+ const watch = (notice: () => void): Watcher => {
53
+ const cleanups = new Set<Cleanup>()
54
+ const w = notice as Partial<Watcher>
55
+ w.off = (on: Cleanup) => {
56
+ cleanups.add(on)
57
+ }
58
+ w.cleanup = () => {
59
+ for (const cleanup of cleanups) {
60
+ cleanup()
61
+ }
62
+ cleanups.clear()
63
+ }
64
+ return w as Watcher
65
+ }
66
+
44
67
  /**
45
68
  * Add active watcher to the Set of watchers
46
69
  *
47
70
  * @param {Set<Watcher>} watchers - watchers of the signal
48
71
  */
49
72
  const subscribe = (watchers: Set<Watcher>) => {
50
- // if (!active) console.warn('Calling .get() outside of a reactive context')
51
73
  if (active && !watchers.has(active)) {
52
74
  const watcher = active
53
75
  watchers.add(watcher)
54
- active.cleanups.add(() => {
76
+ active.off(() => {
55
77
  watchers.delete(watcher)
56
78
  })
57
79
  }
@@ -63,9 +85,9 @@ const subscribe = (watchers: Set<Watcher>) => {
63
85
  * @param {Set<Watcher>} watchers - watchers of the signal
64
86
  */
65
87
  const notify = (watchers: Set<Watcher>) => {
66
- for (const mark of watchers) {
67
- if (batchDepth) pending.add(mark)
68
- else mark()
88
+ for (const watcher of watchers) {
89
+ if (batchDepth) pending.add(watcher)
90
+ else watcher()
69
91
  }
70
92
  }
71
93
 
@@ -76,8 +98,8 @@ const flush = () => {
76
98
  while (pending.size) {
77
99
  const watchers = Array.from(pending)
78
100
  pending.clear()
79
- for (const mark of watchers) {
80
- mark()
101
+ for (const watcher of watchers) {
102
+ watcher()
81
103
  }
82
104
  }
83
105
  }
@@ -101,11 +123,11 @@ const batch = (fn: () => void) => {
101
123
  * Run a function in a reactive context
102
124
  *
103
125
  * @param {() => void} run - function to run the computation or effect
104
- * @param {Watcher} mark - function to be called when the state changes or undefined for temporary unwatching while inserting auto-hydrating DOM nodes that might read signals (e.g., web components)
126
+ * @param {Watcher} watcher - function to be called when the state changes or undefined for temporary unwatching while inserting auto-hydrating DOM nodes that might read signals (e.g., web components)
105
127
  */
106
- const watch = (run: () => void, mark?: Watcher): void => {
128
+ const observe = (run: () => void, watcher?: Watcher): void => {
107
129
  const prev = active
108
- active = mark
130
+ active = watcher
109
131
  try {
110
132
  run()
111
133
  } finally {
@@ -123,8 +145,8 @@ const watch = (run: () => void, mark?: Watcher): void => {
123
145
  * @param {symbol} dedupe - Symbol for deduplication; if not provided, a unique Symbol is created ensuring the update is always executed
124
146
  */
125
147
  const enqueue = <T>(fn: Updater, dedupe?: symbol) =>
126
- new Promise<T | boolean | void>((resolve, reject) => {
127
- updateMap.set(dedupe || Symbol(), () => {
148
+ new Promise<T | boolean | undefined>((resolve, reject) => {
149
+ updateMap.set(dedupe || Symbol(), (): undefined => {
128
150
  try {
129
151
  resolve(fn())
130
152
  } catch (error) {
@@ -145,5 +167,6 @@ export {
145
167
  flush,
146
168
  batch,
147
169
  watch,
170
+ observe,
148
171
  enqueue,
149
172
  }
package/src/signal.d.ts CHANGED
@@ -1,8 +1,11 @@
1
- import { type ComputedCallback } from './computed';
1
+ import { type ComputedCallback, isComputedCallback } from './computed';
2
2
  type Signal<T extends {}> = {
3
3
  get(): T;
4
4
  };
5
5
  type MaybeSignal<T extends {}> = T | Signal<T> | ComputedCallback<T>;
6
+ type SignalValues<S extends Signal<unknown & {}>[]> = {
7
+ [K in keyof S]: S[K] extends Signal<infer T> ? T : never;
8
+ };
6
9
  declare const UNSET: any;
7
10
  /**
8
11
  * Check whether a value is a Signal or not
@@ -12,14 +15,6 @@ declare const UNSET: any;
12
15
  * @returns {boolean} - true if value is a Signal, false otherwise
13
16
  */
14
17
  declare const isSignal: <T extends {}>(value: unknown) => value is Signal<T>;
15
- /**
16
- * Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
17
- *
18
- * @since 0.12.0
19
- * @param {unknown} value - value to check
20
- * @returns {boolean} - true if value is a callback or callbacks object, false otherwise
21
- */
22
- declare const isComputedCallback: <T extends {}>(value: unknown) => value is ComputedCallback<T>;
23
18
  /**
24
19
  * Convert a value to a Signal if it's not already a Signal
25
20
  *
@@ -28,4 +23,4 @@ declare const isComputedCallback: <T extends {}>(value: unknown) => value is Com
28
23
  * @returns {Signal<T>} - converted Signal
29
24
  */
30
25
  declare const toSignal: <T extends {}>(value: MaybeSignal<T>) => Signal<T>;
31
- export { type Signal, type MaybeSignal, UNSET, isSignal, isComputedCallback, toSignal, };
26
+ export { type Signal, type MaybeSignal, type SignalValues, UNSET, isSignal, isComputedCallback, toSignal, };
package/src/signal.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { isState, state } from './state'
2
1
  import {
3
2
  type ComputedCallback,
4
- isComputed,
5
3
  computed,
4
+ isComputed,
5
+ isComputedCallback,
6
6
  } from './computed'
7
- import { isFunction } from './util'
7
+ import { isState, state } from './state'
8
8
 
9
9
  /* === Types === */
10
10
 
@@ -13,11 +13,16 @@ type Signal<T extends {}> = {
13
13
  }
14
14
  type MaybeSignal<T extends {}> = T | Signal<T> | ComputedCallback<T>
15
15
 
16
+ type SignalValues<S extends Signal<unknown & {}>[]> = {
17
+ [K in keyof S]: S[K] extends Signal<infer T> ? T : never
18
+ }
19
+
16
20
  /* === Constants === */
17
21
 
22
+ // biome-ignore lint/suspicious/noExplicitAny: Deliberately using any to be used as a placeholder value in any signal
18
23
  const UNSET: any = Symbol()
19
24
 
20
- /* === Exported Functions === */
25
+ /* === Functions === */
21
26
 
22
27
  /**
23
28
  * Check whether a value is a Signal or not
@@ -30,17 +35,6 @@ const isSignal = /*#__PURE__*/ <T extends {}>(
30
35
  value: unknown,
31
36
  ): value is Signal<T> => isState(value) || isComputed(value)
32
37
 
33
- /**
34
- * Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
35
- *
36
- * @since 0.12.0
37
- * @param {unknown} value - value to check
38
- * @returns {boolean} - true if value is a callback or callbacks object, false otherwise
39
- */
40
- const isComputedCallback = /*#__PURE__*/ <T extends {}>(
41
- value: unknown,
42
- ): value is ComputedCallback<T> => isFunction(value) && value.length < 2
43
-
44
38
  /**
45
39
  * Convert a value to a Signal if it's not already a Signal
46
40
  *
@@ -62,6 +56,7 @@ const toSignal = /*#__PURE__*/ <T extends {}>(
62
56
  export {
63
57
  type Signal,
64
58
  type MaybeSignal,
59
+ type SignalValues,
65
60
  UNSET,
66
61
  isSignal,
67
62
  isComputedCallback,
package/src/state.d.ts CHANGED
@@ -4,6 +4,7 @@ type State<T extends {}> = {
4
4
  set(v: T): void;
5
5
  update(fn: (v: T) => T): void;
6
6
  };
7
+ declare const TYPE_STATE = "State";
7
8
  /**
8
9
  * Create a new state signal
9
10
  *
@@ -20,4 +21,4 @@ declare const state: <T extends {}>(initialValue: T) => State<T>;
20
21
  * @returns {boolean} - true if the value is a State instance, false otherwise
21
22
  */
22
23
  declare const isState: <T extends {}>(value: unknown) => value is State<T>;
23
- export { type State, state, isState };
24
+ export { type State, TYPE_STATE, state, isState };
package/src/state.ts CHANGED
@@ -86,4 +86,4 @@ const isState = /*#__PURE__*/ <T extends {}>(
86
86
 
87
87
  /* === Exports === */
88
88
 
89
- export { type State, state, isState }
89
+ export { type State, TYPE_STATE, state, isState }