@yoltra/core 0.1.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/LICENSE +21 -0
- package/README.es.md +473 -0
- package/README.md +468 -0
- package/dist/types/eventBus/EventBus.d.ts +127 -0
- package/dist/types/eventBus/LooseEventBus.d.ts +219 -0
- package/dist/types/eventBus/index.d.ts +2 -0
- package/dist/types/index.d.ts +15 -0
- package/dist/types/reducer/Reducer.d.ts +81 -0
- package/dist/types/store/Store.d.ts +834 -0
- package/dist/types/types.d.ts +939 -0
- package/dist/types/utils/detectChangedProps.d.ts +67 -0
- package/dist/types/utils/immutability.d.ts +47 -0
- package/dist/types/utils/index.d.ts +2 -0
- package/dist/yoltra.cjs.js +8 -0
- package/dist/yoltra.esm.js +1557 -0
- package/dist/yoltra.umd.js +8 -0
- package/package.json +83 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @yoltra/core
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Flexible, synchronous pub/sub bus that supports **exact** and **pattern** event subscriptions.
|
|
6
|
+
*
|
|
7
|
+
* @typeParam C - Channel name type (defaults to `string`).
|
|
8
|
+
* @typeParam T - Event type name type (defaults to `string`). Types are treated as **dot-separated paths** (e.g. `"a.b.c"`).
|
|
9
|
+
* @typeParam P - Payload type for all events (defaults to `any`).
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* - **Exact handlers** subscribe to a specific `(channel, type)` pair. Type keys are **normalized** by stripping a single leading dot (`".foo"` → `"foo"`).
|
|
13
|
+
* - **Pattern handlers** subscribe using wildcards over dot-separated segments:
|
|
14
|
+
* - `*` matches **one** segment.
|
|
15
|
+
* - `**` matches **zero or more** segments (greedy).
|
|
16
|
+
* - On {@link LooseEventBus.emit | `emit`}, exact handlers fire first, then any matching pattern handlers.
|
|
17
|
+
* - Handlers are **de-duplicated**: if the same function is both exact and pattern-registered, it is called **once**.
|
|
18
|
+
* - Handler invocation is **synchronous**. Exceptions are caught and logged; remaining handlers still run.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* type C = 'ui' | 'data';
|
|
23
|
+
* type T = string;
|
|
24
|
+
* type P = unknown;
|
|
25
|
+
*
|
|
26
|
+
* const bus = new LooseEventBus<C, T, P>();
|
|
27
|
+
*
|
|
28
|
+
* // Exact
|
|
29
|
+
* const offA = bus.on('ui', 'panel.open', () => console.log('panel opened'));
|
|
30
|
+
*
|
|
31
|
+
* // Patterns
|
|
32
|
+
* const offB = bus.on('ui', 'panel.*', () => console.log('any single sub-event under panel'));
|
|
33
|
+
* const offC = bus.on('ui', 'panel.**', () => console.log('any depth under panel'));
|
|
34
|
+
*
|
|
35
|
+
* bus.emit('ui', 'panel.open', null);
|
|
36
|
+
* // => exact fires, then 'panel.*', then 'panel.**'
|
|
37
|
+
*
|
|
38
|
+
* offA(); offB(); offC(); // unsubscribe
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
43
|
+
export declare class LooseEventBus<C extends string = string, T extends string = string, P = any> {
|
|
44
|
+
/**
|
|
45
|
+
* Exact handlers: `channel → type → [handlers]`.
|
|
46
|
+
* @internal
|
|
47
|
+
*/
|
|
48
|
+
private handlers;
|
|
49
|
+
/**
|
|
50
|
+
* Pattern handlers with `*` and `**`: `channel → pattern(string) → [handlers]`.
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
private patternHandlers;
|
|
54
|
+
/**
|
|
55
|
+
* Subscribes a handler to either an **exact** type or a **pattern**.
|
|
56
|
+
*
|
|
57
|
+
* @param channel - Channel to subscribe on.
|
|
58
|
+
* @param type - Exact event type (e.g. `"a.b"`) or pattern (contains `*`/`**`).
|
|
59
|
+
* @param handler - Function invoked with the emitted payload.
|
|
60
|
+
* @returns An **unsubscribe** function that removes this handler.
|
|
61
|
+
*
|
|
62
|
+
* @remarks
|
|
63
|
+
* - Exact subscriptions are stored under a **normalized** key (leading `.` removed).
|
|
64
|
+
* - Pattern subscriptions are stored **as provided**; matching normalizes the subject.
|
|
65
|
+
*
|
|
66
|
+
* @example Exact subscription
|
|
67
|
+
* ```ts
|
|
68
|
+
* const off = bus.on('data', 'items.loaded', ({ count }) => {
|
|
69
|
+
* console.log('Loaded', count);
|
|
70
|
+
* });
|
|
71
|
+
* // Later
|
|
72
|
+
* off();
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* @example Pattern subscription
|
|
76
|
+
* ```ts
|
|
77
|
+
* // Match any single sub-event: 'panel.open', 'panel.close', etc.
|
|
78
|
+
* const offStar = bus.on('ui', 'panel.*', () => {});
|
|
79
|
+
*
|
|
80
|
+
* // Match any depth: 'panel.open', 'panel.items.add', 'panel', etc.
|
|
81
|
+
* const offGlob = bus.on('ui', 'panel.**', () => {});
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @public
|
|
85
|
+
*/
|
|
86
|
+
on(channel: C, type: T, handler: (payload: P) => void): () => void;
|
|
87
|
+
/**
|
|
88
|
+
* Unsubscribes an **exact** handler. The `type` key is normalized internally,
|
|
89
|
+
* so callers can pass `"foo"` or `".foo"` interchangeably.
|
|
90
|
+
*
|
|
91
|
+
* @param channel - Channel name.
|
|
92
|
+
* @param type - Exact event type key to remove (normalization applied).
|
|
93
|
+
* @param handler - The same handler reference previously passed to {@link LooseEventBus.on | `on`}.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* const h = () => {};
|
|
98
|
+
* bus.on('ui', 'panel.open', h);
|
|
99
|
+
* // Remove it (with or without leading dot)
|
|
100
|
+
* bus.off('ui', '.panel.open', h);
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @public
|
|
104
|
+
*/
|
|
105
|
+
off(channel: C, type: T, handler: (payload: P) => void): void;
|
|
106
|
+
/**
|
|
107
|
+
* Internal exact unsubscription using an already **normalized** type key.
|
|
108
|
+
*
|
|
109
|
+
* @param channel - Channel name.
|
|
110
|
+
* @param normalizedType - Event type key with leading dot removed.
|
|
111
|
+
* @param handler - Handler to remove.
|
|
112
|
+
* @internal
|
|
113
|
+
*/
|
|
114
|
+
private offExactNormalized;
|
|
115
|
+
/**
|
|
116
|
+
* Internal removal for a **pattern** subscription. No-ops if missing.
|
|
117
|
+
*
|
|
118
|
+
* @param channel - Channel name.
|
|
119
|
+
* @param pattern - Pattern string as originally subscribed.
|
|
120
|
+
* @param handler - Handler to remove.
|
|
121
|
+
* @internal
|
|
122
|
+
*/
|
|
123
|
+
private offPattern;
|
|
124
|
+
/**
|
|
125
|
+
* Emits an event to all exact subscribers first, then to **matching pattern** subscribers.
|
|
126
|
+
* Duplicate handler references are called **once** (de-duped).
|
|
127
|
+
*
|
|
128
|
+
* @param channel - Channel to emit on.
|
|
129
|
+
* @param type - Event type (subject). A leading dot is ignored for matching.
|
|
130
|
+
* @param payload - Payload delivered to handlers.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```ts
|
|
134
|
+
* // Suppose:
|
|
135
|
+
* // - on('ui', 'panel.open', h)
|
|
136
|
+
* // - on('ui', 'panel.*', h) // same handler ref!
|
|
137
|
+
* // - on('ui', 'panel.**', other)
|
|
138
|
+
* bus.emit('ui', 'panel.open', { id: 1 });
|
|
139
|
+
* // => 'h' runs once (de-duped), then 'other'
|
|
140
|
+
* ```
|
|
141
|
+
*
|
|
142
|
+
* @public
|
|
143
|
+
*/
|
|
144
|
+
emit(channel: C, type: T, payload: P): void;
|
|
145
|
+
/**
|
|
146
|
+
* Determines if a string is a **pattern** (contains `*`).
|
|
147
|
+
* @param s - Event type or pattern string.
|
|
148
|
+
* @returns `true` if it contains at least one `*`, else `false`.
|
|
149
|
+
* @internal
|
|
150
|
+
*/
|
|
151
|
+
private isPattern;
|
|
152
|
+
/**
|
|
153
|
+
* Normalizes event type keys for exact matching by stripping a **single** leading dot.
|
|
154
|
+
*
|
|
155
|
+
* @param s - Event type key.
|
|
156
|
+
* @returns Normalized key without a leading dot.
|
|
157
|
+
* @example
|
|
158
|
+
* ```ts
|
|
159
|
+
* normalizeTypeKey('.a.b') // 'a.b'
|
|
160
|
+
* normalizeTypeKey('a.b') // 'a.b'
|
|
161
|
+
* ```
|
|
162
|
+
* @internal
|
|
163
|
+
*/
|
|
164
|
+
private normalizeTypeKey;
|
|
165
|
+
/**
|
|
166
|
+
* Splits a path into dot-separated segments after normalization and removes empties.
|
|
167
|
+
* @param p - Event type or pattern string.
|
|
168
|
+
* @internal
|
|
169
|
+
*/
|
|
170
|
+
private splitPath;
|
|
171
|
+
/**
|
|
172
|
+
* Pattern matcher over dot-separated segments.
|
|
173
|
+
*
|
|
174
|
+
* Rules:
|
|
175
|
+
* - **literal**: exact match.
|
|
176
|
+
* - `*` : matches exactly **one** segment.
|
|
177
|
+
* - `**` : matches **zero or more** remaining segments (including empty).
|
|
178
|
+
*
|
|
179
|
+
* @param pattern - Pattern (may include `*`/`**`).
|
|
180
|
+
* @param path - Subject event type key to test.
|
|
181
|
+
* @returns `true` if the pattern matches the path; otherwise `false`.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* matchPattern('a.*', 'a.b') // true
|
|
186
|
+
* matchPattern('a.*', 'a.b.c') // false
|
|
187
|
+
* matchPattern('a.**', 'a') // true
|
|
188
|
+
* matchPattern('a.**', 'a.b.c') // true
|
|
189
|
+
* matchPattern('**.end', 'x.y.end') // true
|
|
190
|
+
* ```
|
|
191
|
+
*
|
|
192
|
+
* @internal
|
|
193
|
+
*/
|
|
194
|
+
private matchPattern;
|
|
195
|
+
/**
|
|
196
|
+
* Removes **all** listeners (exact and pattern). Useful for tests/HMR teardown.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```ts
|
|
200
|
+
* afterEach(() => bus.clear());
|
|
201
|
+
* ```
|
|
202
|
+
*
|
|
203
|
+
* @public
|
|
204
|
+
*/
|
|
205
|
+
clear(): void;
|
|
206
|
+
/**
|
|
207
|
+
* Returns a snapshot of all registered subscriptions for DevTools introspection.
|
|
208
|
+
*
|
|
209
|
+
* @returns An array of `{ channel, type, count }` entries for each distinct
|
|
210
|
+
* (channel, type/pattern) pair with at least one handler.
|
|
211
|
+
*
|
|
212
|
+
* @internal
|
|
213
|
+
*/
|
|
214
|
+
__introspect(): Array<{
|
|
215
|
+
channel: string;
|
|
216
|
+
type: string;
|
|
217
|
+
count: number;
|
|
218
|
+
}>;
|
|
219
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @yoltra/core
|
|
3
|
+
*
|
|
4
|
+
* yoltra Core - Channel/Event-driven state management
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
export { EventBus } from './eventBus/EventBus';
|
|
9
|
+
export { LooseEventBus } from './eventBus/LooseEventBus';
|
|
10
|
+
export { Reducer } from './reducer/Reducer';
|
|
11
|
+
export { Store, createStore, typedEvents } from './store/Store';
|
|
12
|
+
export { detectChangedProps } from './utils/detectChangedProps';
|
|
13
|
+
export { freezeState } from './utils/immutability';
|
|
14
|
+
export { eventKeys } from './types';
|
|
15
|
+
export type { EventMapBase, EventKey, Event, EventUnion, Change, Emit, Unsubscribe, StoreSpec, StoreInstance, ReducerSpec, ReducerFunction, EffectSpec, EffectFunction, MiddlewareFunction, MiddlewareSpec, MiddlewareInput, DeepReadonly, DeepRO, Primitive, Path, PathValue, WithGlob, Dotted, EventPhase, EventSubscriptionHandler, NarrowedEventHandler, When, EventFromWhen, EventConsumerType, EventConsumerMeta, } from './types';
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { EventMapBase, EventUnion, ReducerFunction } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Thin wrapper around a pure reducer function (stateful event consumer):
|
|
4
|
+
* given a state `S` and an event (from {@link EventUnion | `EventUnion<EM>`}),
|
|
5
|
+
* returns the next state `S`.
|
|
6
|
+
*
|
|
7
|
+
* @typeParam S - State shape handled by this reducer.
|
|
8
|
+
* @typeParam EM - Event map describing the valid event keys and payload types.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* - The reducer function is expected to be **pure** and **side-effect free**.
|
|
12
|
+
* - Use this class when you want to pass a reducer around as a value, or to
|
|
13
|
+
* unify the reducer interface across the core API.
|
|
14
|
+
*
|
|
15
|
+
* @example Basic counter
|
|
16
|
+
* ```ts
|
|
17
|
+
* type State = { count: number };
|
|
18
|
+
* type EM = { math: { add: number; set: number } };
|
|
19
|
+
*
|
|
20
|
+
* const rf: ReducerFunction<State, EM> = (s, evt) => {
|
|
21
|
+
* if (evt.channel === 'math' && evt.type === 'add') {
|
|
22
|
+
* return { count: s.count + evt.payload };
|
|
23
|
+
* }
|
|
24
|
+
* if (evt.channel === 'math' && evt.type === 'set') {
|
|
25
|
+
* return { count: evt.payload };
|
|
26
|
+
* }
|
|
27
|
+
* return s;
|
|
28
|
+
* };
|
|
29
|
+
*
|
|
30
|
+
* const r = new Reducer<State, EM>(rf);
|
|
31
|
+
*
|
|
32
|
+
* const s0 = { count: 0 };
|
|
33
|
+
* const s1 = r.reduce(s0, {
|
|
34
|
+
* channel: 'math',
|
|
35
|
+
* type: 'add',
|
|
36
|
+
* payload: 2,
|
|
37
|
+
* id: crypto.randomUUID()
|
|
38
|
+
* } as EventUnion<EM>);
|
|
39
|
+
* // s1.count === 2
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
export declare class Reducer<S, EM extends EventMapBase = EventMapBase> {
|
|
45
|
+
/**
|
|
46
|
+
* The underlying pure reducer function.
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
private readonly _reduce;
|
|
50
|
+
/**
|
|
51
|
+
* Creates a new {@link Reducer} from a pure reducer function.
|
|
52
|
+
*
|
|
53
|
+
* @param reduce - A function `(state, event) => nextState` that implements your update logic.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* const reducer = new Reducer<MyState, MyEM>((state, event) => {
|
|
58
|
+
* // implement your transitions here
|
|
59
|
+
* return state;
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* @public
|
|
64
|
+
*/
|
|
65
|
+
constructor(reduce: ReducerFunction<S, EM>);
|
|
66
|
+
/**
|
|
67
|
+
* Applies the reducer to produce the next state.
|
|
68
|
+
*
|
|
69
|
+
* @param state - Current state.
|
|
70
|
+
* @param event - An event drawn from {@link EventUnion | `EventUnion<EM>`}.
|
|
71
|
+
* @returns The next state produced by the underlying reducer function.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* const next = reducer.reduce(curr, someEvent as EventUnion<MyEM>);
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* @public
|
|
79
|
+
*/
|
|
80
|
+
reduce(state: S, event: EventUnion<EM>): S;
|
|
81
|
+
}
|