@rhi-zone/rainbow 0.1.0 → 0.2.0-alpha.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.
- package/README.md +51 -0
- package/dist/async-data.d.ts +80 -2
- package/dist/computed.d.ts +5 -2
- package/dist/cond.d.ts +2 -2
- package/dist/index.d.ts +4 -3
- package/dist/lens.d.ts +51 -13
- package/dist/optic.d.ts +16 -0
- package/dist/prism.d.ts +53 -7
- package/dist/product.d.ts +14 -9
- package/dist/rainbow.js +292 -183
- package/dist/rainbow.umd.cjs +1 -1
- package/dist/signal.d.ts +28 -3
- package/dist/traversal.d.ts +23 -4
- package/package.json +10 -10
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# @rhi-zone/rainbow
|
|
2
|
+
|
|
3
|
+
Optics-based reactivity for TypeScript. Seven composable primitives, no framework coupling, no magic.
|
|
4
|
+
|
|
5
|
+
## What it is
|
|
6
|
+
|
|
7
|
+
Rainbow gives you structured state management grounded in the algebra of optics. Instead of scattered reactive variables or a centralized store with action boilerplate, you get:
|
|
8
|
+
|
|
9
|
+
- **Signals** — reactive cells with `get`, `set`, `subscribe`, and `map`.
|
|
10
|
+
- **Lenses** — focus on a field of a record, composable and law-abiding.
|
|
11
|
+
- **Prisms** — focus on a case of a sum type (discriminated unions, optionals).
|
|
12
|
+
- **Traversals** — focus on zero or more values within a collection.
|
|
13
|
+
- **Computed** — derive from multiple sources with an explicit dep list.
|
|
14
|
+
- **Cond** — conditional propagation, composable like `&&`.
|
|
15
|
+
- **Product** — pair two signals into one `Signal<[A, B]>`.
|
|
16
|
+
|
|
17
|
+
Everything is a plain TypeScript value. No decorators, no global state, no framework requirement.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install @rhi-zone/rainbow
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick example
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { signal, field, computed } from '@rhi-zone/rainbow'
|
|
29
|
+
|
|
30
|
+
type State = { user: { name: string }; count: number }
|
|
31
|
+
|
|
32
|
+
const state = signal<State>({ user: { name: 'Alice' }, count: 0 })
|
|
33
|
+
|
|
34
|
+
// Focus on a nested field — reads and writes go through the lens
|
|
35
|
+
const name = state.focus(field('user')).focus(field('name'))
|
|
36
|
+
|
|
37
|
+
name.get() // 'Alice'
|
|
38
|
+
name.set('Bob')
|
|
39
|
+
state.get() // { user: { name: 'Bob' }, count: 0 }
|
|
40
|
+
|
|
41
|
+
// Derive from multiple sources
|
|
42
|
+
const summary = computed(
|
|
43
|
+
() => `${name.get()} has visited ${state.focus(field('count')).get()} times`,
|
|
44
|
+
[name, state],
|
|
45
|
+
)
|
|
46
|
+
summary.get() // 'Bob has visited 0 times'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Docs
|
|
50
|
+
|
|
51
|
+
Full guides, API reference, and design notes are at the [Rainbow VitePress site](https://rhi.zone/rainbow/).
|
package/dist/async-data.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Signal, ReadonlySignal } from './signal.ts';
|
|
1
2
|
/**
|
|
2
3
|
* AsyncData<T, E> — the type of an asynchronous value.
|
|
3
4
|
*
|
|
@@ -21,20 +22,28 @@ export type AsyncData<T, E = unknown> = {
|
|
|
21
22
|
readonly status: 'success';
|
|
22
23
|
readonly value: T;
|
|
23
24
|
};
|
|
25
|
+
/** Singleton for the "not yet requested" state. */
|
|
24
26
|
export declare const notAsked: AsyncData<never, never>;
|
|
27
|
+
/** Singleton for the "request in flight" state. */
|
|
25
28
|
export declare const loading: AsyncData<never, never>;
|
|
29
|
+
/** Construct a failure value. */
|
|
26
30
|
export declare const failure: <E>(error: E) => AsyncData<never, E>;
|
|
31
|
+
/** Construct a success value. */
|
|
27
32
|
export declare const success: <T>(value: T) => AsyncData<T, never>;
|
|
33
|
+
/** Narrowing guard for the `notAsked` state. */
|
|
28
34
|
export declare const isNotAsked: <T, E>(ad: AsyncData<T, E>) => ad is {
|
|
29
35
|
status: 'notAsked';
|
|
30
36
|
};
|
|
37
|
+
/** Narrowing guard for the `loading` state. */
|
|
31
38
|
export declare const isLoading: <T, E>(ad: AsyncData<T, E>) => ad is {
|
|
32
39
|
status: 'loading';
|
|
33
40
|
};
|
|
41
|
+
/** Narrowing guard for the `failure` state. */
|
|
34
42
|
export declare const isFailure: <T, E>(ad: AsyncData<T, E>) => ad is {
|
|
35
43
|
status: 'failure';
|
|
36
44
|
error: E;
|
|
37
45
|
};
|
|
46
|
+
/** Narrowing guard for the `success` state. */
|
|
38
47
|
export declare const isSuccess: <T, E>(ad: AsyncData<T, E>) => ad is {
|
|
39
48
|
status: 'success';
|
|
40
49
|
value: T;
|
|
@@ -45,12 +54,81 @@ export declare const map: <T, U, E>(ad: AsyncData<T, E>, f: (value: T) => U) =>
|
|
|
45
54
|
export declare const mapError: <T, E, F>(ad: AsyncData<T, E>, f: (error: E) => F) => AsyncData<T, F>;
|
|
46
55
|
/** Chain async operations — flatMap over the success case. */
|
|
47
56
|
export declare const chain: <T, U, E>(ad: AsyncData<T, E>, f: (value: T) => AsyncData<U, E>) => AsyncData<U, E>;
|
|
48
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Unwrap the success value, or return `fallback` for all other states.
|
|
59
|
+
* @param fallback - Value to return when not in the success state.
|
|
60
|
+
*/
|
|
49
61
|
export declare const getOrElse: <T, E>(ad: AsyncData<T, E>, fallback: T) => T;
|
|
50
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* Fold over all four states.
|
|
64
|
+
* @param cases - Handlers for each state; all four must be provided.
|
|
65
|
+
*/
|
|
51
66
|
export declare const fold: <T, E, R>(ad: AsyncData<T, E>, cases: {
|
|
52
67
|
notAsked: () => R;
|
|
53
68
|
loading: () => R;
|
|
54
69
|
failure: (error: E) => R;
|
|
55
70
|
success: (value: T) => R;
|
|
56
71
|
}) => R;
|
|
72
|
+
/**
|
|
73
|
+
* Wrap an already-in-flight promise as a `ReadonlySignal<AsyncData<T, E>>`.
|
|
74
|
+
* Starts in `loading`; transitions to `success` or `failure` when the promise
|
|
75
|
+
* settles. The signal is read-only — it can only be updated by the promise.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* const user = fromPromise(fetchUser(id))
|
|
79
|
+
* // user.get() === loading initially, then success({ ... }) or failure(e)
|
|
80
|
+
*/
|
|
81
|
+
export declare function fromPromise<T, E = unknown>(promise: Promise<T>): ReadonlySignal<AsyncData<T, E>>;
|
|
82
|
+
/**
|
|
83
|
+
* Derive a `ReadonlySignal<AsyncData<T, E>>` that re-runs `fn` whenever
|
|
84
|
+
* `deps` changes. Each new run receives a fresh `AbortSignal`; the previous
|
|
85
|
+
* in-flight request is aborted before the next one starts. Starts in
|
|
86
|
+
* `loading` immediately.
|
|
87
|
+
*
|
|
88
|
+
* Returns `[signal, dispose]`. Call `dispose()` when done — it aborts any
|
|
89
|
+
* in-flight request and unsubscribes from `deps`. In a widget context, pass
|
|
90
|
+
* `dispose` to `register()` so it is called automatically on unmount.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* const [results, dispose] = fromAsync(querySignal, (q, abort) =>
|
|
94
|
+
* fetch(`/api/search?q=${q}`, { signal: abort }).then(r => r.json())
|
|
95
|
+
* )
|
|
96
|
+
* register(dispose) // clean up when widget unmounts
|
|
97
|
+
*/
|
|
98
|
+
export declare function fromAsync<D, T, E = unknown>(deps: ReadonlySignal<D>, fn: (deps: D, abort: AbortSignal) => Promise<T>): [ReadonlySignal<AsyncData<T, E>>, dispose: () => void];
|
|
99
|
+
/**
|
|
100
|
+
* Create an imperatively-triggered async operation.
|
|
101
|
+
*
|
|
102
|
+
* Unlike `fromAsync`, this is not driven by a signal — you call `trigger(input)`
|
|
103
|
+
* manually (e.g. from a button handler). Concurrent calls abort the previous
|
|
104
|
+
* in-flight request via AbortSignal.
|
|
105
|
+
*
|
|
106
|
+
* @returns
|
|
107
|
+
* `result` — ReadonlySignal<AsyncData<T, E>> starting as notAsked()
|
|
108
|
+
* `trigger` — Call with input to start the async operation
|
|
109
|
+
* `dispose` — Cancel any in-flight request and stop updates
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* const { result, trigger, dispose } = fromAsyncImperative(
|
|
113
|
+
* async (contact: Contact, signal) => await saveContact(contact, signal)
|
|
114
|
+
* )
|
|
115
|
+
* on(saveBtn, "click", () => trigger(formState.get().values))
|
|
116
|
+
* register(dispose)
|
|
117
|
+
*/
|
|
118
|
+
export declare function fromAsyncImperative<I, T, E = unknown>(fn: (input: I, signal: AbortSignal) => Promise<T>): {
|
|
119
|
+
readonly result: ReadonlySignal<AsyncData<T, E>>;
|
|
120
|
+
readonly trigger: (input: I) => void;
|
|
121
|
+
readonly dispose: () => void;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Returns a Promise that resolves with the next value from `s` that satisfies
|
|
125
|
+
* `predicate`. If the current value already satisfies it, resolves immediately.
|
|
126
|
+
*
|
|
127
|
+
* Useful for bridging signal-based async results back to imperative await:
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* trigger(contact)
|
|
131
|
+
* const result = await toNextValue(saveResult, r => !isLoading(r))
|
|
132
|
+
* if (isSuccess(result)) ...
|
|
133
|
+
*/
|
|
134
|
+
export declare function toNextValue<T>(s: Signal<T> | ReadonlySignal<T>, predicate?: (v: T) => boolean): Promise<T>;
|
package/dist/computed.d.ts
CHANGED
|
@@ -2,7 +2,10 @@ import type { ReadonlySignal } from './signal.ts';
|
|
|
2
2
|
/**
|
|
3
3
|
* Derive a read-only signal from multiple source signals.
|
|
4
4
|
*
|
|
5
|
-
* Unlike signal.map (one source), computed accepts any number of sources
|
|
6
|
-
* and recomputes when any of them change.
|
|
5
|
+
* Unlike `signal.map` (one source), `computed` accepts any number of sources
|
|
6
|
+
* and recomputes `fn` when any of them change.
|
|
7
|
+
*
|
|
8
|
+
* @param fn - Pure function that reads from one or more signals and returns the derived value.
|
|
9
|
+
* @param deps - Explicit dependency list; the signal re-evaluates when any dep changes.
|
|
7
10
|
*/
|
|
8
11
|
export declare function computed<T>(fn: () => T, deps: ReadonlySignal<unknown>[]): ReadonlySignal<T>;
|
package/dist/cond.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReadonlySignal } from './signal.ts';
|
|
2
2
|
/**
|
|
3
|
-
* Conditional signal — propagates the value when `pred` holds, undefined otherwise.
|
|
3
|
+
* Conditional signal — propagates the value when `pred` holds, `undefined` otherwise.
|
|
4
4
|
*
|
|
5
5
|
* Accepts both `ReadonlySignal<A>` and `ReadonlySignal<A | undefined>` so that
|
|
6
6
|
* cond calls can be composed directly:
|
|
@@ -10,7 +10,7 @@ import type { ReadonlySignal } from './signal.ts';
|
|
|
10
10
|
* When the source already carries `undefined` (from a prior `cond` or `narrow`),
|
|
11
11
|
* undefined passes through unchanged — equivalent to short-circuit `&&`.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
13
|
+
* @example
|
|
14
14
|
* const positiveCount = cond(n => n > 0, countSignal)
|
|
15
15
|
* const positiveEven = cond(n => n % 2 === 0, positiveCount)
|
|
16
16
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
export type { Optic } from './optic.ts';
|
|
1
2
|
export type { Lens } from './lens.ts';
|
|
2
|
-
export { lens, composeLens, field,
|
|
3
|
+
export { lens, composeLens, field, index, id, arrayOf, recordOf, mapOf } from './lens.ts';
|
|
3
4
|
export type { Prism } from './prism.ts';
|
|
4
|
-
export { prism, composePrism, some, iso } from './prism.ts';
|
|
5
|
+
export { prism, composePrism, some, iso, guard, nullable, tagged } from './prism.ts';
|
|
5
6
|
export type { Signal, ReadonlySignal } from './signal.ts';
|
|
6
7
|
export { signal, batch } from './signal.ts';
|
|
7
8
|
export { computed } from './computed.ts';
|
|
@@ -10,4 +11,4 @@ export { product, stateful } from './product.ts';
|
|
|
10
11
|
export type { Traversal } from './traversal.ts';
|
|
11
12
|
export { traversal, each, filtered, nth, composeWithLens, composeTraversal } from './traversal.ts';
|
|
12
13
|
export type { AsyncData } from './async-data.ts';
|
|
13
|
-
export { notAsked, loading, failure, success, isNotAsked, isLoading, isFailure, isSuccess, map as mapAsyncData, mapError, chain as chainAsyncData, getOrElse, fold, } from './async-data.ts';
|
|
14
|
+
export { notAsked, loading, failure, success, isNotAsked, isLoading, isFailure, isSuccess, map as mapAsyncData, mapError, chain as chainAsyncData, getOrElse, fold, fromPromise, fromAsync, fromAsyncImperative, toNextValue, } from './async-data.ts';
|
package/dist/lens.d.ts
CHANGED
|
@@ -1,22 +1,60 @@
|
|
|
1
|
+
import type { Optic } from './optic.ts';
|
|
1
2
|
/**
|
|
2
3
|
* A Lens<A, B> focuses on a field of type B within a structure of type A.
|
|
3
4
|
*
|
|
4
5
|
* Laws:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* view(review(b, a)) = b
|
|
7
|
+
* review(view(a), a) = a
|
|
8
|
+
* review(b, review(b1, a)) = review(b, a)
|
|
8
9
|
*/
|
|
9
|
-
export interface Lens<A, B> {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
export interface Lens<A, B> extends Optic<A, B> {
|
|
11
|
+
view(a: A): B;
|
|
12
|
+
review(b: B, a: A): A;
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Construct a lens from explicit view and review functions.
|
|
16
|
+
* @param view - Extract B from A.
|
|
17
|
+
* @param review - Return a new A with B replaced.
|
|
18
|
+
*/
|
|
19
|
+
export declare function lens<A, B>(view: (a: A) => B, review: (b: B, a: A) => A): Lens<A, B>;
|
|
20
|
+
/**
|
|
21
|
+
* Compose two lenses. If `ab` focuses on B within A, and `bc` focuses on C within B,
|
|
22
|
+
* the result focuses on C within A.
|
|
23
|
+
*/
|
|
14
24
|
export declare function composeLens<A, B, C>(ab: Lens<A, B>, bc: Lens<B, C>): Lens<A, C>;
|
|
15
|
-
/** Lens into
|
|
16
|
-
export declare
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
/** Lens into a tuple element by index. `index(0)` replaces `fst`, `index(1)` replaces `snd`. */
|
|
26
|
+
export declare function index<T extends readonly unknown[], N extends number & keyof T>(n: N): Lens<T, T[N]>;
|
|
27
|
+
/**
|
|
28
|
+
* Lens into a record field by key.
|
|
29
|
+
* @param key - The key of the field to focus on.
|
|
30
|
+
*/
|
|
20
31
|
export declare function field<A, K extends keyof A>(key: K): Lens<A, A[K]>;
|
|
21
|
-
/** Identity lens */
|
|
32
|
+
/** Identity lens — focuses on the whole value. */
|
|
22
33
|
export declare function id<A>(): Lens<A, A>;
|
|
34
|
+
/**
|
|
35
|
+
* Lift a `Lens<S, A>` to `Lens<S[], A[]>`, applying it pointwise over an array.
|
|
36
|
+
*
|
|
37
|
+
* Laws hold as long as the `A[]` written back has the same length as the `S[]`
|
|
38
|
+
* it came from — which is guaranteed when the only source of `A[]` values is
|
|
39
|
+
* `view` on the same array.
|
|
40
|
+
*
|
|
41
|
+
* Useful for two-way bindings over arrays of objects:
|
|
42
|
+
* signal<User[]>.focus(arrayOf(field("name"))) // Signal<string[]>
|
|
43
|
+
*/
|
|
44
|
+
export declare function arrayOf<S, A>(l: Lens<S, A>): Lens<S[], A[]>;
|
|
45
|
+
/**
|
|
46
|
+
* Lift a `Lens<S, A>` to `Lens<Record<K, S>, Record<K, A>>`, applying it to
|
|
47
|
+
* every value in the record.
|
|
48
|
+
*
|
|
49
|
+
* Useful for normalized state keyed by branded IDs:
|
|
50
|
+
* signal<Record<UserId, User>>.focus(recordOf(field("name"))) // Signal<Record<UserId, string>>
|
|
51
|
+
*/
|
|
52
|
+
export declare function recordOf<K extends string, S, A>(l: Lens<S, A>): Lens<Record<K, S>, Record<K, A>>;
|
|
53
|
+
/**
|
|
54
|
+
* Lift a `Lens<S, A>` to `Lens<Map<K, S>, Map<K, A>>`, applying it to every
|
|
55
|
+
* value in the map.
|
|
56
|
+
*
|
|
57
|
+
* Prefer this over `recordOf` when keys are arbitrary strings — `Map` avoids
|
|
58
|
+
* prototype-pollution footguns with special keys like `__proto__`.
|
|
59
|
+
*/
|
|
60
|
+
export declare function mapOf<K, S, A>(l: Lens<S, A>): Lens<Map<K, S>, Map<K, A>>;
|
package/dist/optic.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared supertype for Lens and Prism.
|
|
3
|
+
*
|
|
4
|
+
* - `view` — extract B from A; may return undefined (Prism) or always
|
|
5
|
+
* succeeds (Lens, which narrows the return to B, not B|undefined).
|
|
6
|
+
* - `review` — embed B back into A. Prism implements this as `(b) => A`,
|
|
7
|
+
* satisfying the `(b, a) => A` signature because TypeScript
|
|
8
|
+
* allows fewer parameters. Lens uses both `b` and `a` to
|
|
9
|
+
* preserve surrounding structure.
|
|
10
|
+
*
|
|
11
|
+
* Use `Optic<A, B>` at call sites that accept either optic kind.
|
|
12
|
+
*/
|
|
13
|
+
export interface Optic<A, B> {
|
|
14
|
+
view(a: A): B | undefined;
|
|
15
|
+
review(b: B, a: A): A;
|
|
16
|
+
}
|
package/dist/prism.d.ts
CHANGED
|
@@ -1,17 +1,63 @@
|
|
|
1
|
+
import type { Optic } from './optic.ts';
|
|
1
2
|
/**
|
|
2
3
|
* A Prism<A, B> focuses on a case of type B within a sum type A.
|
|
3
4
|
*
|
|
4
5
|
* Laws:
|
|
5
|
-
*
|
|
6
|
-
* if
|
|
6
|
+
* view(review(b)) = b
|
|
7
|
+
* if view(a) = b then review(b) = a
|
|
7
8
|
*/
|
|
8
|
-
export interface Prism<A, B> {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
export interface Prism<A, B> extends Optic<A, B> {
|
|
10
|
+
view(a: A): B | undefined;
|
|
11
|
+
review(b: B): A;
|
|
11
12
|
}
|
|
12
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Construct a prism from explicit view and review functions.
|
|
15
|
+
* @param view - Extract B from A, or return undefined if the case doesn't apply.
|
|
16
|
+
* @param review - Construct an A from a B.
|
|
17
|
+
*/
|
|
18
|
+
export declare function prism<A, B>(view: (a: A) => B | undefined, review: (b: B) => A): Prism<A, B>;
|
|
19
|
+
/**
|
|
20
|
+
* Compose two prisms. If `ab` focuses on B within A, and `bc` focuses on C within B,
|
|
21
|
+
* the result focuses on C within A.
|
|
22
|
+
*/
|
|
13
23
|
export declare function composePrism<A, B, C>(ab: Prism<A, B>, bc: Prism<B, C>): Prism<A, C>;
|
|
14
24
|
/** Prism for the Some case of an optional value */
|
|
15
25
|
export declare function some<A>(): Prism<A | undefined, A>;
|
|
16
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Prism that always matches — models an isomorphism between A and B.
|
|
28
|
+
* @param to - Convert A to B.
|
|
29
|
+
* @param from - Convert B back to A.
|
|
30
|
+
*/
|
|
17
31
|
export declare function iso<A, B>(to: (a: A) => B, from: (b: B) => A): Prism<A, B>;
|
|
32
|
+
/**
|
|
33
|
+
* Prism from a type predicate. The most general constructor — `some` and
|
|
34
|
+
* `nullable` are both special cases.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* const positive = guard((n: number): n is number => n > 0)
|
|
38
|
+
* signal(signal.narrow(positive)) // Signal<number | undefined>
|
|
39
|
+
*/
|
|
40
|
+
export declare function guard<A, B extends A>(predicate: (a: A) => a is B): Prism<A, B>;
|
|
41
|
+
/**
|
|
42
|
+
* Prism for the non-null case. Use `some` for `T | undefined`,
|
|
43
|
+
* `nullable` for `T | null`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function nullable<A>(): Prism<A | null, A>;
|
|
46
|
+
/**
|
|
47
|
+
* Prism for a specific variant of a discriminated union, matched by a tag field.
|
|
48
|
+
*
|
|
49
|
+
* `A` cannot be inferred from the key/value arguments alone — provide it
|
|
50
|
+
* explicitly when not in a `.narrow()` context where it can be inferred:
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* type Shape = { kind: "circle"; r: number } | { kind: "rect"; w: number; h: number }
|
|
54
|
+
*
|
|
55
|
+
* // In a narrow() call — A inferred from the signal's type:
|
|
56
|
+
* shapeSignal.narrow(tagged("kind", "circle"))
|
|
57
|
+
* // Signal<{ kind: "circle"; r: number } | undefined>
|
|
58
|
+
*
|
|
59
|
+
* // Standalone — provide A explicitly (K and V are inferred from the args):
|
|
60
|
+
* const circle = tagged<Shape>("kind", "circle")
|
|
61
|
+
* // Prism<Shape, { kind: "circle"; r: number }>
|
|
62
|
+
*/
|
|
63
|
+
export declare function tagged<A, K extends keyof A, V extends A[K]>(key: K, value: V): Prism<A, Extract<A, Record<K, V>>>;
|
package/dist/product.d.ts
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import type { Signal } from './signal.ts';
|
|
2
2
|
/**
|
|
3
|
-
* Create a signal over a pair [A, B] backed by two independent signals.
|
|
3
|
+
* Create a signal over a pair `[A, B]` backed by two independent signals.
|
|
4
|
+
* Writes to the product signal are atomically batched across both children.
|
|
5
|
+
*
|
|
6
|
+
* @param a - Signal for the first element.
|
|
7
|
+
* @param b - Signal for the second element.
|
|
4
8
|
*/
|
|
5
9
|
export declare function product<A, B>(a: Signal<A>, b: Signal<B>): Signal<[A, B]>;
|
|
6
10
|
/**
|
|
7
|
-
*
|
|
11
|
+
* Attach a local state `S` to an external signal `A`, returning `Signal<[S, A]>`.
|
|
8
12
|
*
|
|
9
|
-
* The S state starts as `init` and lives locally — not
|
|
10
|
-
* from outside except via
|
|
13
|
+
* The `S` state starts as `init` and lives locally — not observable
|
|
14
|
+
* from outside except via `.focus(index(0))` / `.focus(index(1))`.
|
|
11
15
|
*
|
|
12
|
-
*
|
|
16
|
+
* @param init - Initial value for the local state.
|
|
17
|
+
* @param outer - The external signal to pair with.
|
|
13
18
|
*
|
|
14
|
-
*
|
|
15
|
-
* const combined = stateful("", itemsSignal)
|
|
16
|
-
* const draft = combined.focus(
|
|
17
|
-
* const items = combined.focus(
|
|
19
|
+
* @example
|
|
20
|
+
* const combined = stateful("", itemsSignal) // Signal<[string, Item[]]>
|
|
21
|
+
* const draft = combined.focus(index(0)) // Signal<string> — local
|
|
22
|
+
* const items = combined.focus(index(1)) // Signal<Item[]> — external
|
|
18
23
|
*/
|
|
19
24
|
export declare function stateful<S, A>(init: S, outer: Signal<A>): Signal<[S, A]>;
|
package/dist/rainbow.js
CHANGED
|
@@ -1,62 +1,107 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var u = (
|
|
4
|
-
function
|
|
5
|
-
return {
|
|
1
|
+
var j = Object.defineProperty;
|
|
2
|
+
var S = (e, t, s) => t in e ? j(e, t, { enumerable: !0, configurable: !0, writable: !0, value: s }) : e[t] = s;
|
|
3
|
+
var u = (e, t, s) => S(e, typeof t != "symbol" ? t + "" : t, s);
|
|
4
|
+
function a(e, t) {
|
|
5
|
+
return { view: e, review: t };
|
|
6
6
|
}
|
|
7
|
-
function
|
|
7
|
+
function F(e, t) {
|
|
8
8
|
return {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
view: (s) => t.view(e.view(s)),
|
|
10
|
+
review: (s, r) => e.review(t.review(s, e.view(r)), r)
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
(t) =>
|
|
17
|
-
|
|
13
|
+
function I(e) {
|
|
14
|
+
return a(
|
|
15
|
+
(t) => t[e],
|
|
16
|
+
(t, s) => {
|
|
17
|
+
const r = [...s];
|
|
18
|
+
return r[e] = t, r;
|
|
19
|
+
}
|
|
18
20
|
);
|
|
19
21
|
}
|
|
20
|
-
function
|
|
21
|
-
return
|
|
22
|
+
function R(e) {
|
|
23
|
+
return a(
|
|
24
|
+
(t) => t[e],
|
|
25
|
+
(t, s) => ({ ...s, [e]: t })
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
function T() {
|
|
29
|
+
return a((e) => e, (e) => e);
|
|
30
|
+
}
|
|
31
|
+
function V(e) {
|
|
32
|
+
return a(
|
|
33
|
+
(t) => t.map((s) => e.view(s)),
|
|
34
|
+
(t, s) => s.map((r, n) => e.review(t[n], r))
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
function W(e) {
|
|
38
|
+
return a(
|
|
39
|
+
(t) => Object.fromEntries(Object.entries(t).map(([s, r]) => [s, e.view(r)])),
|
|
40
|
+
(t, s) => Object.fromEntries(Object.entries(s).map(([r, n]) => [r, e.review(t[r], n)]))
|
|
41
|
+
);
|
|
22
42
|
}
|
|
23
|
-
function
|
|
24
|
-
return
|
|
43
|
+
function q(e) {
|
|
44
|
+
return a(
|
|
45
|
+
(t) => new Map([...t].map(([s, r]) => [s, e.view(r)])),
|
|
46
|
+
(t, s) => new Map([...s].map(([r, n]) => [r, e.review(t.get(r), n)]))
|
|
47
|
+
);
|
|
25
48
|
}
|
|
26
|
-
function
|
|
49
|
+
function f(e, t) {
|
|
50
|
+
return { view: e, review: t };
|
|
51
|
+
}
|
|
52
|
+
function B(e, t) {
|
|
27
53
|
return {
|
|
28
|
-
|
|
29
|
-
const r =
|
|
30
|
-
return r !== void 0 ? t.
|
|
54
|
+
view: (s) => {
|
|
55
|
+
const r = e.view(s);
|
|
56
|
+
return r !== void 0 ? t.view(r) : void 0;
|
|
31
57
|
},
|
|
32
|
-
|
|
58
|
+
review: (s) => e.review(t.review(s))
|
|
33
59
|
};
|
|
34
60
|
}
|
|
35
|
-
function
|
|
36
|
-
return
|
|
37
|
-
(
|
|
38
|
-
(
|
|
61
|
+
function G() {
|
|
62
|
+
return f(
|
|
63
|
+
(e) => e,
|
|
64
|
+
(e) => e
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
function H(e, t) {
|
|
68
|
+
return f(e, t);
|
|
69
|
+
}
|
|
70
|
+
function J(e) {
|
|
71
|
+
return f(
|
|
72
|
+
(t) => e(t) ? t : void 0,
|
|
73
|
+
(t) => t
|
|
39
74
|
);
|
|
40
75
|
}
|
|
41
|
-
function
|
|
42
|
-
return
|
|
76
|
+
function K() {
|
|
77
|
+
return f(
|
|
78
|
+
(e) => e !== null ? e : void 0,
|
|
79
|
+
(e) => e
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
function Q(e, t) {
|
|
83
|
+
return f(
|
|
84
|
+
(s) => s[e] === t ? s : void 0,
|
|
85
|
+
// Extract<A, Record<K,V>> extends A, so this upcast is safe
|
|
86
|
+
(s) => s
|
|
87
|
+
);
|
|
43
88
|
}
|
|
44
|
-
let
|
|
45
|
-
const
|
|
46
|
-
function
|
|
47
|
-
|
|
89
|
+
let p = 0;
|
|
90
|
+
const _ = /* @__PURE__ */ new Map();
|
|
91
|
+
function k(e) {
|
|
92
|
+
p++;
|
|
48
93
|
try {
|
|
49
|
-
|
|
94
|
+
e();
|
|
50
95
|
} finally {
|
|
51
|
-
if (
|
|
52
|
-
for (;
|
|
53
|
-
const t = [...
|
|
54
|
-
|
|
55
|
-
for (const
|
|
96
|
+
if (p--, p === 0)
|
|
97
|
+
for (; _.size > 0; ) {
|
|
98
|
+
const t = [..._.values()];
|
|
99
|
+
_.clear();
|
|
100
|
+
for (const s of t) s();
|
|
56
101
|
}
|
|
57
102
|
}
|
|
58
103
|
}
|
|
59
|
-
class
|
|
104
|
+
class x {
|
|
60
105
|
constructor(t) {
|
|
61
106
|
u(this, "_value");
|
|
62
107
|
u(this, "_subscribers", /* @__PURE__ */ new Set());
|
|
@@ -67,292 +112,356 @@ class A {
|
|
|
67
112
|
}
|
|
68
113
|
set(t) {
|
|
69
114
|
if (!Object.is(this._value, t))
|
|
70
|
-
if (this._value = t,
|
|
71
|
-
for (const
|
|
72
|
-
|
|
115
|
+
if (this._value = t, p > 0)
|
|
116
|
+
for (const s of this._subscribers)
|
|
117
|
+
_.set(s, () => s(this._value));
|
|
73
118
|
else
|
|
74
|
-
for (const
|
|
119
|
+
for (const s of this._subscribers) s(t);
|
|
75
120
|
}
|
|
76
121
|
subscribe(t) {
|
|
77
122
|
return this._subscribers.add(t), () => this._subscribers.delete(t);
|
|
78
123
|
}
|
|
79
124
|
map(t) {
|
|
80
|
-
return new
|
|
125
|
+
return new h(this, t);
|
|
81
126
|
}
|
|
82
127
|
focus(t) {
|
|
83
|
-
return new
|
|
128
|
+
return new b(this, t);
|
|
84
129
|
}
|
|
85
130
|
narrow(t) {
|
|
86
|
-
return new
|
|
131
|
+
return new w(this, t);
|
|
87
132
|
}
|
|
88
133
|
}
|
|
89
|
-
class
|
|
90
|
-
constructor(t,
|
|
134
|
+
class h {
|
|
135
|
+
constructor(t, s) {
|
|
91
136
|
u(this, "_source");
|
|
92
137
|
u(this, "_f");
|
|
93
|
-
this._source = t, this._f =
|
|
138
|
+
this._source = t, this._f = s;
|
|
94
139
|
}
|
|
95
140
|
get() {
|
|
96
141
|
return this._f(this._source.get());
|
|
97
142
|
}
|
|
98
143
|
subscribe(t) {
|
|
99
|
-
let
|
|
144
|
+
let s = this.get();
|
|
100
145
|
return this._source.subscribe(() => {
|
|
101
146
|
const r = this.get();
|
|
102
|
-
Object.is(
|
|
147
|
+
Object.is(s, r) || (s = r, t(r));
|
|
103
148
|
});
|
|
104
149
|
}
|
|
105
150
|
map(t) {
|
|
106
|
-
return new
|
|
151
|
+
return new h(this, t);
|
|
107
152
|
}
|
|
108
153
|
}
|
|
109
|
-
class
|
|
110
|
-
constructor(t,
|
|
154
|
+
class b {
|
|
155
|
+
constructor(t, s) {
|
|
111
156
|
u(this, "_source");
|
|
112
157
|
u(this, "_lens");
|
|
113
|
-
this._source = t, this._lens =
|
|
158
|
+
this._source = t, this._lens = s;
|
|
114
159
|
}
|
|
115
160
|
get() {
|
|
116
|
-
return this._lens.
|
|
161
|
+
return this._lens.view(this._source.get());
|
|
117
162
|
}
|
|
118
163
|
set(t) {
|
|
119
|
-
this._source.set(this._lens.
|
|
164
|
+
this._source.set(this._lens.review(t, this._source.get()));
|
|
120
165
|
}
|
|
121
166
|
subscribe(t) {
|
|
122
|
-
let
|
|
167
|
+
let s = this.get();
|
|
123
168
|
return this._source.subscribe(() => {
|
|
124
169
|
const r = this.get();
|
|
125
|
-
Object.is(
|
|
170
|
+
Object.is(s, r) || (s = r, t(r));
|
|
126
171
|
});
|
|
127
172
|
}
|
|
128
173
|
map(t) {
|
|
129
|
-
return new
|
|
174
|
+
return new h(this, t);
|
|
130
175
|
}
|
|
131
176
|
focus(t) {
|
|
132
|
-
return new
|
|
177
|
+
return new b(this, t);
|
|
133
178
|
}
|
|
134
179
|
narrow(t) {
|
|
135
|
-
return new
|
|
180
|
+
return new w(this, t);
|
|
136
181
|
}
|
|
137
182
|
}
|
|
138
|
-
class
|
|
139
|
-
constructor(t,
|
|
183
|
+
class w {
|
|
184
|
+
constructor(t, s) {
|
|
140
185
|
u(this, "_source");
|
|
141
186
|
u(this, "_prism");
|
|
142
|
-
this._source = t, this._prism =
|
|
187
|
+
this._source = t, this._prism = s;
|
|
143
188
|
}
|
|
144
189
|
get() {
|
|
145
|
-
return this._prism.
|
|
190
|
+
return this._prism.view(this._source.get());
|
|
146
191
|
}
|
|
147
192
|
set(t) {
|
|
148
|
-
t !== void 0 && this._source.set(this._prism.
|
|
193
|
+
t !== void 0 && this._source.set(this._prism.review(t));
|
|
149
194
|
}
|
|
150
195
|
subscribe(t) {
|
|
151
|
-
let
|
|
196
|
+
let s = this.get();
|
|
152
197
|
return this._source.subscribe(() => {
|
|
153
198
|
const r = this.get();
|
|
154
|
-
Object.is(
|
|
199
|
+
Object.is(s, r) || (s = r, t(r));
|
|
155
200
|
});
|
|
156
201
|
}
|
|
157
202
|
map(t) {
|
|
158
|
-
return new
|
|
203
|
+
return new h(this, t);
|
|
159
204
|
}
|
|
160
205
|
focus(t) {
|
|
161
|
-
return new
|
|
206
|
+
return new b(this, t);
|
|
162
207
|
}
|
|
163
208
|
narrow(t) {
|
|
164
|
-
return new
|
|
209
|
+
return new w(this, t);
|
|
165
210
|
}
|
|
166
211
|
}
|
|
167
|
-
function
|
|
168
|
-
return new
|
|
212
|
+
function g(e) {
|
|
213
|
+
return new x(e);
|
|
169
214
|
}
|
|
170
|
-
function
|
|
171
|
-
return new
|
|
215
|
+
function E(e, t) {
|
|
216
|
+
return new b(e, t);
|
|
172
217
|
}
|
|
173
|
-
function
|
|
174
|
-
return new
|
|
218
|
+
function P(e, t) {
|
|
219
|
+
return new w(e, t);
|
|
175
220
|
}
|
|
176
|
-
function
|
|
177
|
-
return new
|
|
221
|
+
function C(e, t) {
|
|
222
|
+
return new M(e, t);
|
|
178
223
|
}
|
|
179
|
-
class
|
|
180
|
-
constructor(t,
|
|
224
|
+
class M {
|
|
225
|
+
constructor(t, s) {
|
|
181
226
|
u(this, "_fn");
|
|
182
227
|
u(this, "_deps");
|
|
183
|
-
this._fn = t, this._deps =
|
|
228
|
+
this._fn = t, this._deps = s;
|
|
184
229
|
}
|
|
185
230
|
get() {
|
|
186
231
|
return this._fn();
|
|
187
232
|
}
|
|
188
233
|
subscribe(t) {
|
|
189
|
-
let
|
|
234
|
+
let s = this.get();
|
|
190
235
|
const r = this._deps.map(
|
|
191
236
|
(n) => n.subscribe(() => {
|
|
192
237
|
const i = this.get();
|
|
193
|
-
Object.is(
|
|
238
|
+
Object.is(s, i) || (s = i, t(i));
|
|
194
239
|
})
|
|
195
240
|
);
|
|
196
241
|
return () => r.forEach((n) => n());
|
|
197
242
|
}
|
|
198
243
|
map(t) {
|
|
199
|
-
return
|
|
244
|
+
return C(() => t(this.get()), [this]);
|
|
200
245
|
}
|
|
201
246
|
}
|
|
202
|
-
function
|
|
203
|
-
return t.map((
|
|
247
|
+
function U(e, t) {
|
|
248
|
+
return t.map((s) => s !== void 0 && e(s) ? s : void 0);
|
|
204
249
|
}
|
|
205
|
-
class
|
|
206
|
-
constructor(t,
|
|
207
|
-
this._a = t, this._b =
|
|
250
|
+
class D {
|
|
251
|
+
constructor(t, s) {
|
|
252
|
+
this._a = t, this._b = s;
|
|
208
253
|
}
|
|
209
254
|
get() {
|
|
210
255
|
return [this._a.get(), this._b.get()];
|
|
211
256
|
}
|
|
212
|
-
set([t,
|
|
213
|
-
|
|
214
|
-
this._a.set(t), this._b.set(
|
|
257
|
+
set([t, s]) {
|
|
258
|
+
k(() => {
|
|
259
|
+
this._a.set(t), this._b.set(s);
|
|
215
260
|
});
|
|
216
261
|
}
|
|
217
262
|
subscribe(t) {
|
|
218
|
-
const
|
|
263
|
+
const s = () => t(this.get()), r = this._a.subscribe(s), n = this._b.subscribe(s);
|
|
219
264
|
return () => {
|
|
220
265
|
r(), n();
|
|
221
266
|
};
|
|
222
267
|
}
|
|
223
268
|
map(t) {
|
|
224
|
-
let
|
|
225
|
-
const r =
|
|
269
|
+
let s = t(this.get());
|
|
270
|
+
const r = g(s);
|
|
226
271
|
return this.subscribe((n) => {
|
|
227
272
|
const i = t(n);
|
|
228
|
-
Object.is(
|
|
273
|
+
Object.is(s, i) || (s = i, r.set(i));
|
|
229
274
|
}), r;
|
|
230
275
|
}
|
|
231
276
|
focus(t) {
|
|
232
|
-
return
|
|
277
|
+
return E(this, t);
|
|
233
278
|
}
|
|
234
279
|
narrow(t) {
|
|
235
|
-
return
|
|
280
|
+
return P(this, t);
|
|
236
281
|
}
|
|
237
282
|
}
|
|
238
|
-
function
|
|
239
|
-
return new
|
|
283
|
+
function L(e, t) {
|
|
284
|
+
return new D(e, t);
|
|
240
285
|
}
|
|
241
|
-
function
|
|
242
|
-
return
|
|
286
|
+
function X(e, t) {
|
|
287
|
+
return L(g(e), t);
|
|
243
288
|
}
|
|
244
|
-
function
|
|
245
|
-
return { getAll:
|
|
289
|
+
function v(e, t) {
|
|
290
|
+
return { getAll: e, modify: t };
|
|
246
291
|
}
|
|
247
|
-
function
|
|
248
|
-
return
|
|
249
|
-
(
|
|
250
|
-
(
|
|
292
|
+
function Y() {
|
|
293
|
+
return v(
|
|
294
|
+
(e) => [...e],
|
|
295
|
+
(e, t) => e.map(t)
|
|
251
296
|
);
|
|
252
297
|
}
|
|
253
|
-
function
|
|
254
|
-
return
|
|
255
|
-
(t) => t.filter(
|
|
256
|
-
(t,
|
|
298
|
+
function Z(e) {
|
|
299
|
+
return v(
|
|
300
|
+
(t) => t.filter(e),
|
|
301
|
+
(t, s) => t.map((r) => e(r) ? s(r) : r)
|
|
257
302
|
);
|
|
258
303
|
}
|
|
259
|
-
function
|
|
260
|
-
return
|
|
261
|
-
(t) => t.filter((
|
|
262
|
-
(t,
|
|
304
|
+
function $(e) {
|
|
305
|
+
return v(
|
|
306
|
+
(t) => t.filter((s, r) => r === e),
|
|
307
|
+
(t, s) => t.map((r, n) => n === e ? s(r) : r)
|
|
263
308
|
);
|
|
264
309
|
}
|
|
265
|
-
function
|
|
266
|
-
return
|
|
267
|
-
(
|
|
268
|
-
(
|
|
310
|
+
function tt(e, t) {
|
|
311
|
+
return v(
|
|
312
|
+
(s) => t.getAll(e.view(s)),
|
|
313
|
+
(s, r) => e.review(t.modify(e.view(s), r), s)
|
|
269
314
|
);
|
|
270
315
|
}
|
|
271
|
-
function
|
|
272
|
-
return
|
|
273
|
-
(
|
|
274
|
-
(
|
|
316
|
+
function et(e, t) {
|
|
317
|
+
return v(
|
|
318
|
+
(s) => e.getAll(s).flatMap((r) => t.getAll(r)),
|
|
319
|
+
(s, r) => e.modify(s, (n) => t.modify(n, r))
|
|
275
320
|
);
|
|
276
321
|
}
|
|
277
|
-
const
|
|
278
|
-
switch (
|
|
322
|
+
const d = { status: "notAsked" }, c = { status: "loading" }, m = (e) => ({ status: "failure", error: e }), A = (e) => ({ status: "success", value: e }), st = (e) => e.status === "notAsked", rt = (e) => e.status === "loading", nt = (e) => e.status === "failure", N = (e) => e.status === "success", it = (e, t) => {
|
|
323
|
+
switch (e.status) {
|
|
279
324
|
case "success":
|
|
280
|
-
return
|
|
325
|
+
return A(t(e.value));
|
|
281
326
|
case "failure":
|
|
282
|
-
return
|
|
327
|
+
return e;
|
|
283
328
|
case "loading":
|
|
284
|
-
return
|
|
329
|
+
return c;
|
|
285
330
|
case "notAsked":
|
|
286
|
-
return
|
|
331
|
+
return d;
|
|
287
332
|
}
|
|
288
|
-
},
|
|
289
|
-
switch (
|
|
333
|
+
}, ut = (e, t) => {
|
|
334
|
+
switch (e.status) {
|
|
290
335
|
case "failure":
|
|
291
|
-
return
|
|
336
|
+
return m(t(e.error));
|
|
292
337
|
case "success":
|
|
293
|
-
return
|
|
338
|
+
return e;
|
|
294
339
|
case "loading":
|
|
295
|
-
return
|
|
340
|
+
return c;
|
|
296
341
|
case "notAsked":
|
|
297
|
-
return
|
|
342
|
+
return d;
|
|
298
343
|
}
|
|
299
|
-
},
|
|
300
|
-
switch (
|
|
344
|
+
}, ot = (e, t) => {
|
|
345
|
+
switch (e.status) {
|
|
301
346
|
case "success":
|
|
302
|
-
return t(
|
|
347
|
+
return t(e.value);
|
|
303
348
|
case "failure":
|
|
304
|
-
return
|
|
349
|
+
return e;
|
|
305
350
|
case "loading":
|
|
306
|
-
return
|
|
351
|
+
return c;
|
|
307
352
|
case "notAsked":
|
|
308
|
-
return
|
|
353
|
+
return d;
|
|
309
354
|
}
|
|
310
|
-
},
|
|
311
|
-
switch (
|
|
355
|
+
}, ct = (e, t) => N(e) ? e.value : t, at = (e, t) => {
|
|
356
|
+
switch (e.status) {
|
|
312
357
|
case "notAsked":
|
|
313
358
|
return t.notAsked();
|
|
314
359
|
case "loading":
|
|
315
360
|
return t.loading();
|
|
316
361
|
case "failure":
|
|
317
|
-
return t.failure(
|
|
362
|
+
return t.failure(e.error);
|
|
318
363
|
case "success":
|
|
319
|
-
return t.success(
|
|
364
|
+
return t.success(e.value);
|
|
320
365
|
}
|
|
321
366
|
};
|
|
367
|
+
function lt(e) {
|
|
368
|
+
const t = g(c);
|
|
369
|
+
return e.then(
|
|
370
|
+
(s) => t.set(A(s)),
|
|
371
|
+
(s) => t.set(m(s))
|
|
372
|
+
), t;
|
|
373
|
+
}
|
|
374
|
+
function ft(e, t) {
|
|
375
|
+
const s = g(c);
|
|
376
|
+
let r = new AbortController();
|
|
377
|
+
const n = (l) => {
|
|
378
|
+
r.abort(), r = new AbortController(), s.set(c);
|
|
379
|
+
const { signal: o } = r;
|
|
380
|
+
t(l, o).then(
|
|
381
|
+
(O) => {
|
|
382
|
+
o.aborted || s.set(A(O));
|
|
383
|
+
},
|
|
384
|
+
(O) => {
|
|
385
|
+
o.aborted || s.set(m(O));
|
|
386
|
+
}
|
|
387
|
+
);
|
|
388
|
+
};
|
|
389
|
+
n(e.get());
|
|
390
|
+
const i = e.subscribe(n);
|
|
391
|
+
return [s, () => {
|
|
392
|
+
r.abort(), i();
|
|
393
|
+
}];
|
|
394
|
+
}
|
|
395
|
+
function ht(e) {
|
|
396
|
+
const t = g(d);
|
|
397
|
+
let s = null, r = !1;
|
|
398
|
+
return { result: t, trigger: (y) => {
|
|
399
|
+
if (r) return;
|
|
400
|
+
s !== null && s.abort(), s = new AbortController(), t.set(c);
|
|
401
|
+
const { signal: l } = s;
|
|
402
|
+
e(y, l).then(
|
|
403
|
+
(o) => {
|
|
404
|
+
!l.aborted && !r && t.set(A(o));
|
|
405
|
+
},
|
|
406
|
+
(o) => {
|
|
407
|
+
!l.aborted && !r && t.set(m(o));
|
|
408
|
+
}
|
|
409
|
+
);
|
|
410
|
+
}, dispose: () => {
|
|
411
|
+
r = !0, s !== null && (s.abort(), s = null);
|
|
412
|
+
} };
|
|
413
|
+
}
|
|
414
|
+
function bt(e, t) {
|
|
415
|
+
const s = e.get();
|
|
416
|
+
return t === void 0 || t(s) ? Promise.resolve(s) : new Promise((r) => {
|
|
417
|
+
const n = e.subscribe((i) => {
|
|
418
|
+
t(i) && (n(), r(i));
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
}
|
|
322
422
|
export {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
423
|
+
V as arrayOf,
|
|
424
|
+
k as batch,
|
|
425
|
+
ot as chainAsyncData,
|
|
426
|
+
F as composeLens,
|
|
427
|
+
B as composePrism,
|
|
428
|
+
et as composeTraversal,
|
|
429
|
+
tt as composeWithLens,
|
|
430
|
+
C as computed,
|
|
431
|
+
U as cond,
|
|
432
|
+
Y as each,
|
|
433
|
+
m as failure,
|
|
434
|
+
R as field,
|
|
435
|
+
Z as filtered,
|
|
436
|
+
at as fold,
|
|
437
|
+
ft as fromAsync,
|
|
438
|
+
ht as fromAsyncImperative,
|
|
439
|
+
lt as fromPromise,
|
|
440
|
+
ct as getOrElse,
|
|
441
|
+
J as guard,
|
|
442
|
+
T as id,
|
|
443
|
+
I as index,
|
|
444
|
+
nt as isFailure,
|
|
445
|
+
rt as isLoading,
|
|
446
|
+
st as isNotAsked,
|
|
447
|
+
N as isSuccess,
|
|
448
|
+
H as iso,
|
|
449
|
+
a as lens,
|
|
450
|
+
c as loading,
|
|
451
|
+
it as mapAsyncData,
|
|
452
|
+
ut as mapError,
|
|
453
|
+
q as mapOf,
|
|
454
|
+
d as notAsked,
|
|
455
|
+
$ as nth,
|
|
456
|
+
K as nullable,
|
|
457
|
+
f as prism,
|
|
458
|
+
L as product,
|
|
459
|
+
W as recordOf,
|
|
460
|
+
g as signal,
|
|
461
|
+
G as some,
|
|
462
|
+
X as stateful,
|
|
463
|
+
A as success,
|
|
464
|
+
Q as tagged,
|
|
465
|
+
bt as toNextValue,
|
|
466
|
+
v as traversal
|
|
358
467
|
};
|
package/dist/rainbow.umd.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(r,u){typeof exports=="object"&&typeof module<"u"?u(exports):typeof define=="function"&&define.amd?define(["exports"],u):(r=typeof globalThis<"u"?globalThis:r||self,u(r.Rainbow={}))})(this,(function(r){"use strict";var he=Object.defineProperty;var de=(r,u,f)=>u in r?he(r,u,{enumerable:!0,configurable:!0,writable:!0,value:f}):r[u]=f;var o=(r,u,f)=>de(r,typeof u!="symbol"?u+"":u,f);function u(t,e){return{view:t,review:e}}function f(t,e){return{view:s=>e.view(t.view(s)),review:(s,n)=>t.review(e.review(s,t.view(n)),n)}}function M(t){return u(e=>e[t],(e,s)=>{const n=[...s];return n[t]=e,n})}function T(t){return u(e=>e[t],(e,s)=>({...s,[t]:e}))}function C(){return u(t=>t,t=>t)}function N(t){return u(e=>e.map(s=>t.view(s)),(e,s)=>s.map((n,i)=>t.review(e[i],n)))}function D(t){return u(e=>Object.fromEntries(Object.entries(e).map(([s,n])=>[s,t.view(n)])),(e,s)=>Object.fromEntries(Object.entries(s).map(([n,i])=>[n,t.review(e[n],i)])))}function F(t){return u(e=>new Map([...e].map(([s,n])=>[s,t.view(n)])),(e,s)=>new Map([...s].map(([n,i])=>[n,t.review(e.get(n),i)])))}function h(t,e){return{view:t,review:e}}function I(t,e){return{view:s=>{const n=t.view(s);return n!==void 0?e.view(n):void 0},review:s=>t.review(e.review(s))}}function R(){return h(t=>t,t=>t)}function V(t,e){return h(t,e)}function W(t){return h(e=>t(e)?e:void 0,e=>e)}function z(){return h(t=>t!==null?t:void 0,t=>t)}function q(t,e){return h(s=>s[t]===e?s:void 0,s=>s)}let O=0;const y=new Map;function S(t){O++;try{t()}finally{if(O--,O===0)for(;y.size>0;){const e=[...y.values()];y.clear();for(const s of e)s()}}}class B{constructor(e){o(this,"_value");o(this,"_subscribers",new Set);this._value=e}get(){return this._value}set(e){if(!Object.is(this._value,e))if(this._value=e,O>0)for(const s of this._subscribers)y.set(s,()=>s(this._value));else for(const s of this._subscribers)s(e)}subscribe(e){return this._subscribers.add(e),()=>this._subscribers.delete(e)}map(e){return new g(this,e)}focus(e){return new m(this,e)}narrow(e){return new w(this,e)}}class g{constructor(e,s){o(this,"_source");o(this,"_f");this._source=e,this._f=s}get(){return this._f(this._source.get())}subscribe(e){let s=this.get();return this._source.subscribe(()=>{const n=this.get();Object.is(s,n)||(s=n,e(n))})}map(e){return new g(this,e)}}class m{constructor(e,s){o(this,"_source");o(this,"_lens");this._source=e,this._lens=s}get(){return this._lens.view(this._source.get())}set(e){this._source.set(this._lens.review(e,this._source.get()))}subscribe(e){let s=this.get();return this._source.subscribe(()=>{const n=this.get();Object.is(s,n)||(s=n,e(n))})}map(e){return new g(this,e)}focus(e){return new m(this,e)}narrow(e){return new w(this,e)}}class w{constructor(e,s){o(this,"_source");o(this,"_prism");this._source=e,this._prism=s}get(){return this._prism.view(this._source.get())}set(e){e!==void 0&&this._source.set(this._prism.review(e))}subscribe(e){let s=this.get();return this._source.subscribe(()=>{const n=this.get();Object.is(s,n)||(s=n,e(n))})}map(e){return new g(this,e)}focus(e){return new m(this,e)}narrow(e){return new w(this,e)}}function d(t){return new B(t)}function G(t,e){return new m(t,e)}function H(t,e){return new w(t,e)}function k(t,e){return new J(t,e)}class J{constructor(e,s){o(this,"_fn");o(this,"_deps");this._fn=e,this._deps=s}get(){return this._fn()}subscribe(e){let s=this.get();const n=this._deps.map(i=>i.subscribe(()=>{const c=this.get();Object.is(s,c)||(s=c,e(c))}));return()=>n.forEach(i=>i())}map(e){return k(()=>e(this.get()),[this])}}function K(t,e){return e.map(s=>s!==void 0&&t(s)?s:void 0)}class Q{constructor(e,s){this._a=e,this._b=s}get(){return[this._a.get(),this._b.get()]}set([e,s]){S(()=>{this._a.set(e),this._b.set(s)})}subscribe(e){const s=()=>e(this.get()),n=this._a.subscribe(s),i=this._b.subscribe(s);return()=>{n(),i()}}map(e){let s=e(this.get());const n=d(s);return this.subscribe(i=>{const c=e(i);Object.is(s,c)||(s=c,n.set(c))}),n}focus(e){return G(this,e)}narrow(e){return H(this,e)}}function P(t,e){return new Q(t,e)}function U(t,e){return P(d(t),e)}function b(t,e){return{getAll:t,modify:e}}function X(){return b(t=>[...t],(t,e)=>t.map(e))}function Y(t){return b(e=>e.filter(t),(e,s)=>e.map(n=>t(n)?s(n):n))}function Z(t){return b(e=>e.filter((s,n)=>n===t),(e,s)=>e.map((n,i)=>i===t?s(n):n))}function $(t,e){return b(s=>e.getAll(t.view(s)),(s,n)=>t.review(e.modify(t.view(s),n),s))}function x(t,e){return b(s=>t.getAll(s).flatMap(n=>e.getAll(n)),(s,n)=>t.modify(s,i=>e.modify(i,n)))}const v={status:"notAsked"},a={status:"loading"},_=t=>({status:"failure",error:t}),p=t=>({status:"success",value:t}),ee=t=>t.status==="notAsked",te=t=>t.status==="loading",se=t=>t.status==="failure",E=t=>t.status==="success",ne=(t,e)=>{switch(t.status){case"success":return p(e(t.value));case"failure":return t;case"loading":return a;case"notAsked":return v}},re=(t,e)=>{switch(t.status){case"failure":return _(e(t.error));case"success":return t;case"loading":return a;case"notAsked":return v}},ie=(t,e)=>{switch(t.status){case"success":return e(t.value);case"failure":return t;case"loading":return a;case"notAsked":return v}},ue=(t,e)=>E(t)?t.value:e,ce=(t,e)=>{switch(t.status){case"notAsked":return e.notAsked();case"loading":return e.loading();case"failure":return e.failure(t.error);case"success":return e.success(t.value)}};function oe(t){const e=d(a);return t.then(s=>e.set(p(s)),s=>e.set(_(s))),e}function ae(t,e){const s=d(a);let n=new AbortController;const i=A=>{n.abort(),n=new AbortController,s.set(a);const{signal:l}=n;e(A,l).then(j=>{l.aborted||s.set(p(j))},j=>{l.aborted||s.set(_(j))})};i(t.get());const c=t.subscribe(i);return[s,()=>{n.abort(),c()}]}function le(t){const e=d(v);let s=null,n=!1;return{result:e,trigger:L=>{if(n)return;s!==null&&s.abort(),s=new AbortController,e.set(a);const{signal:A}=s;t(L,A).then(l=>{!A.aborted&&!n&&e.set(p(l))},l=>{!A.aborted&&!n&&e.set(_(l))})},dispose:()=>{n=!0,s!==null&&(s.abort(),s=null)}}}function fe(t,e){const s=t.get();return e===void 0||e(s)?Promise.resolve(s):new Promise(n=>{const i=t.subscribe(c=>{e(c)&&(i(),n(c))})})}r.arrayOf=N,r.batch=S,r.chainAsyncData=ie,r.composeLens=f,r.composePrism=I,r.composeTraversal=x,r.composeWithLens=$,r.computed=k,r.cond=K,r.each=X,r.failure=_,r.field=T,r.filtered=Y,r.fold=ce,r.fromAsync=ae,r.fromAsyncImperative=le,r.fromPromise=oe,r.getOrElse=ue,r.guard=W,r.id=C,r.index=M,r.isFailure=se,r.isLoading=te,r.isNotAsked=ee,r.isSuccess=E,r.iso=V,r.lens=u,r.loading=a,r.mapAsyncData=ne,r.mapError=re,r.mapOf=F,r.notAsked=v,r.nth=Z,r.nullable=z,r.prism=h,r.product=P,r.recordOf=D,r.signal=d,r.some=R,r.stateful=U,r.success=p,r.tagged=q,r.toNextValue=fe,r.traversal=b,Object.defineProperty(r,Symbol.toStringTag,{value:"Module"})}));
|
package/dist/signal.d.ts
CHANGED
|
@@ -13,22 +13,47 @@ export declare function batch(fn: () => void): void;
|
|
|
13
13
|
* Reading tracks the dependency; writing propagates to subscribers.
|
|
14
14
|
*/
|
|
15
15
|
export interface Signal<A> {
|
|
16
|
+
/** Return the current value. */
|
|
16
17
|
get(): A;
|
|
18
|
+
/** Update the value and notify subscribers. No-op if the value is unchanged (`Object.is`). */
|
|
17
19
|
set(a: A): void;
|
|
20
|
+
/**
|
|
21
|
+
* Subscribe to value changes.
|
|
22
|
+
* @returns An unsubscribe function.
|
|
23
|
+
*/
|
|
18
24
|
subscribe(fn: Subscriber<A>): () => void;
|
|
19
|
-
/**
|
|
25
|
+
/** Return a read-only derived signal by applying `f` to each value. */
|
|
20
26
|
map<B>(f: (a: A) => B): ReadonlySignal<B>;
|
|
21
|
-
/**
|
|
27
|
+
/** Return a read-write signal focused on B via a lens. */
|
|
22
28
|
focus<B>(lens: Lens<A, B>): Signal<B>;
|
|
23
|
-
/**
|
|
29
|
+
/** Return a read-write signal focused via a prism; yields `undefined` when the case doesn't match. */
|
|
24
30
|
narrow<B>(prism: Prism<A, B>): Signal<B | undefined>;
|
|
25
31
|
}
|
|
32
|
+
/** A read-only view of a reactive value. */
|
|
26
33
|
export interface ReadonlySignal<A> {
|
|
34
|
+
/** Return the current value. */
|
|
27
35
|
get(): A;
|
|
36
|
+
/**
|
|
37
|
+
* Subscribe to value changes.
|
|
38
|
+
* @returns An unsubscribe function.
|
|
39
|
+
*/
|
|
28
40
|
subscribe(fn: Subscriber<A>): () => void;
|
|
41
|
+
/** Return a read-only derived signal by applying `f` to each value. */
|
|
29
42
|
map<B>(f: (a: A) => B): ReadonlySignal<B>;
|
|
30
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Create a root signal with the given initial value.
|
|
46
|
+
* @param initial - The starting value.
|
|
47
|
+
*/
|
|
31
48
|
export declare function signal<A>(initial: A): Signal<A>;
|
|
49
|
+
/**
|
|
50
|
+
* Create a signal focused on part of another signal via a lens.
|
|
51
|
+
* Reads and writes pass through the lens; the source signal is the source of truth.
|
|
52
|
+
*/
|
|
32
53
|
export declare function focusSignal<A, B>(source: Signal<A>, lens: Lens<A, B>): Signal<B>;
|
|
54
|
+
/**
|
|
55
|
+
* Create a signal focused on a prism case of another signal.
|
|
56
|
+
* Yields `undefined` when the prism doesn't match; writes are no-ops when `b` is `undefined`.
|
|
57
|
+
*/
|
|
33
58
|
export declare function narrowSignal<A, B>(source: Signal<A>, prism: Prism<A, B>): Signal<B | undefined>;
|
|
34
59
|
export {};
|
package/dist/traversal.d.ts
CHANGED
|
@@ -7,17 +7,36 @@ import type { Lens } from './lens.ts';
|
|
|
7
7
|
* modify(modify(a, f), g) = modify(a, g ∘ f) [when f, g commute on elements]
|
|
8
8
|
*/
|
|
9
9
|
export interface Traversal<A, B> {
|
|
10
|
+
/** Extract all focused values from `a`. */
|
|
10
11
|
getAll(a: A): B[];
|
|
12
|
+
/** Apply `f` to every focused value and return the updated `a`. */
|
|
11
13
|
modify(a: A, f: (b: B) => B): A;
|
|
12
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Construct a traversal from explicit getAll and modify functions.
|
|
17
|
+
* @param getAll - Extract all focused values.
|
|
18
|
+
* @param modify - Apply a function to every focused value.
|
|
19
|
+
*/
|
|
13
20
|
export declare function traversal<A, B>(getAll: (a: A) => B[], modify: (a: A, f: (b: B) => B) => A): Traversal<A, B>;
|
|
14
21
|
/** Focus on every element of an array */
|
|
15
22
|
export declare function each<B>(): Traversal<B[], B>;
|
|
16
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Focus on elements matching a predicate.
|
|
25
|
+
* Non-matching elements pass through unmodified.
|
|
26
|
+
*/
|
|
17
27
|
export declare function filtered<B>(pred: (b: B) => boolean): Traversal<B[], B>;
|
|
18
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Focus on a single element by index.
|
|
30
|
+
* If the index is out of bounds, `getAll` returns `[]` and `modify` is a no-op.
|
|
31
|
+
*/
|
|
19
32
|
export declare function nth<B>(index: number): Traversal<B[], B>;
|
|
20
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Compose a lens with a traversal.
|
|
35
|
+
* Focuses the lens on B within A, then the traversal on C within B.
|
|
36
|
+
*/
|
|
21
37
|
export declare function composeWithLens<A, B extends object, C>(lens: Lens<A, B>, t: Traversal<B, C>): Traversal<A, C>;
|
|
22
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Compose two traversals.
|
|
40
|
+
* `getAll` flatMaps; `modify` nests.
|
|
41
|
+
*/
|
|
23
42
|
export declare function composeTraversal<A, B, C>(ab: Traversal<A, B>, bc: Traversal<B, C>): Traversal<A, C>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rhi-zone/rainbow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-alpha.0",
|
|
4
4
|
"description": "Optics-based reactivity — structured state for the web",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/rainbow.umd.cjs",
|
|
@@ -15,18 +15,18 @@
|
|
|
15
15
|
},
|
|
16
16
|
"files": ["dist"],
|
|
17
17
|
"scripts": {
|
|
18
|
-
"dev":
|
|
19
|
-
"build":
|
|
20
|
-
"typecheck":
|
|
21
|
-
"test":
|
|
18
|
+
"dev": "vite build --watch",
|
|
19
|
+
"build": "vite build && tsgo --emitDeclarationOnly",
|
|
20
|
+
"typecheck": "tsgo --noEmit",
|
|
21
|
+
"test": "vitest run",
|
|
22
22
|
"test:watch": "vitest"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@types/react":
|
|
25
|
+
"@types/react": "^19.2.14",
|
|
26
26
|
"@vitejs/plugin-vue": "^6.0.4",
|
|
27
|
-
"fast-check":
|
|
28
|
-
"vite":
|
|
29
|
-
"vitest":
|
|
30
|
-
"vue":
|
|
27
|
+
"fast-check": "^4.5.3",
|
|
28
|
+
"vite": "^6.0.0",
|
|
29
|
+
"vitest": "^2.0.0",
|
|
30
|
+
"vue": "^3.5.29"
|
|
31
31
|
}
|
|
32
32
|
}
|