@kuindji/reactive 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +985 -0
  2. package/dist/action.d.ts +49 -0
  3. package/dist/action.js +77 -0
  4. package/dist/actionBus.d.ts +43 -0
  5. package/dist/actionBus.js +81 -0
  6. package/dist/actionMap.d.ts +23 -0
  7. package/dist/actionMap.js +26 -0
  8. package/dist/event.d.ts +143 -0
  9. package/dist/event.js +538 -0
  10. package/dist/eventBus.d.ts +115 -0
  11. package/dist/eventBus.js +508 -0
  12. package/dist/index.d.ts +5 -0
  13. package/dist/index.js +21 -0
  14. package/dist/lib/asyncCall.d.ts +1 -0
  15. package/dist/lib/asyncCall.js +17 -0
  16. package/dist/lib/listenerSorter.d.ts +5 -0
  17. package/dist/lib/listenerSorter.js +13 -0
  18. package/dist/lib/tagsIntersect.d.ts +1 -0
  19. package/dist/lib/tagsIntersect.js +11 -0
  20. package/dist/lib/types.d.ts +49 -0
  21. package/dist/lib/types.js +37 -0
  22. package/dist/react/ErrorBoundary.d.ts +10 -0
  23. package/dist/react/ErrorBoundary.js +25 -0
  24. package/dist/react/useAction.d.ts +21 -0
  25. package/dist/react/useAction.js +66 -0
  26. package/dist/react/useActionBus.d.ts +38 -0
  27. package/dist/react/useActionBus.js +44 -0
  28. package/dist/react/useActionMap.d.ts +23 -0
  29. package/dist/react/useActionMap.js +25 -0
  30. package/dist/react/useEvent.d.ts +47 -0
  31. package/dist/react/useEvent.js +66 -0
  32. package/dist/react/useEventBus.d.ts +117 -0
  33. package/dist/react/useEventBus.js +66 -0
  34. package/dist/react/useListenToAction.d.ts +3 -0
  35. package/dist/react/useListenToAction.js +38 -0
  36. package/dist/react/useListenToEvent.d.ts +4 -0
  37. package/dist/react/useListenToEvent.js +34 -0
  38. package/dist/react/useListenToEventBus.d.ts +5 -0
  39. package/dist/react/useListenToEventBus.js +37 -0
  40. package/dist/react/useStore.d.ts +51 -0
  41. package/dist/react/useStore.js +30 -0
  42. package/dist/react/useStoreState.d.ts +3 -0
  43. package/dist/react/useStoreState.js +32 -0
  44. package/dist/react.d.ts +10 -0
  45. package/dist/react.js +26 -0
  46. package/dist/store.d.ts +54 -0
  47. package/dist/store.js +193 -0
  48. package/package.json +70 -0
  49. package/src/index.ts +5 -0
package/README.md ADDED
@@ -0,0 +1,985 @@
1
+ # @kuindji/reactive
2
+
3
+ A JavaScript/TypeScript utility library for building reactive applications with events, actions, stores, and React hooks.
4
+
5
+ [![npm version](https://badge.fury.io/js/%40kuindji%2Freactive.svg)](https://badge.fury.io/js/%40kuindji%2Freactive)
6
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
8
+
9
+ ## Features
10
+
11
+ - **Event System**: Event emitter with subscriber/dispatcher and collector modes
12
+ - **Action System**: Async action handling with error management and response tracking
13
+ - **Store System**: Reactive state management with change tracking and validation
14
+ - **EventBus**: Centralized event management for complex applications
15
+ - **ActionBus & ActionMap**: Organized action management with error handling
16
+ - **React Integration**: Full React hooks support with error boundaries
17
+ - **TypeScript**: First-class TypeScript support with full type safety
18
+ - **Async Support**: Built-in async/await support for all operations
19
+ - **Error Handling**: Comprehensive error handling and recovery mechanisms
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ npm install @kuindji/reactive
25
+ # or
26
+ yarn add @kuindji/reactive
27
+ # or
28
+ bun add @kuindji/reactive
29
+ ```
30
+
31
+ ## Table of Contents
32
+
33
+ - [Event](#event)
34
+ - [EventBus](#eventbus)
35
+ - [Action](#action)
36
+ - [ActionMap](#actionmap)
37
+ - [ActionBus](#actionbus)
38
+ - [Store](#store)
39
+ - [React Hooks](#react-hooks)
40
+ - [ErrorBoundary](#errorboundary)
41
+ - [Examples](#examples)
42
+
43
+ ## Event
44
+
45
+ Event emitter with three distinct modes:
46
+
47
+ - **Subscriber/Dispatcher Mode**: Traditional event emitter pattern where listeners are notified of events
48
+ - **Collector Mode**: Trigger collects data from listeners in various ways (first, last, all, merge, etc.)
49
+ - **Pipe Mode**: Data flows through listeners in a pipeline, each transforming the data
50
+
51
+ ### Basic Usage
52
+
53
+ ```typescript
54
+ import { createEvent } from "@kuindji/reactive";
55
+
56
+ // Create a typed event
57
+ // When creating event, provide a listener signature generic to make full event api typed.
58
+ const userLoginEvent = createEvent<(userId: string, timestamp: Date) => void>();
59
+
60
+ // Add listeners
61
+ userLoginEvent.addListener((userId, timestamp) => {
62
+ console.log(`User ${userId} logged in at ${timestamp}`);
63
+ });
64
+
65
+ // Trigger the event
66
+ userLoginEvent.trigger("user123", new Date());
67
+ ```
68
+
69
+ ### Event Options
70
+
71
+ ```typescript
72
+ // all settings are optional
73
+ const event = createEvent({
74
+ async: boolean, // Call listeners asynchronously; default false
75
+ limit: number, // Event can be triggered 10 times; default 0 (unlimited)
76
+ autoTrigger: boolean, // Auto-trigger new listeners with last args; default false
77
+ maxListeners: number, // Maximum number of listeners; default: 1000
78
+ // default: undefined
79
+ filter: (args: TriggerArgs[], listener: ListenerOptions): boolean => {
80
+ // Custom filter logic
81
+ // args: arguments passed to trigger()
82
+ // listener: an object with listener options and handler itself
83
+ return true;
84
+ },
85
+ });
86
+ ```
87
+
88
+ ### Listener Options
89
+
90
+ ```typescript
91
+ // all settings are optional
92
+ event.addListener(handler, {
93
+ limit: number, // Call this listener 5 times; default 0 (unlimited)
94
+ first: boolean, // Add to beginning of listener list; default false
95
+ alwaysFirst: boolean, // Always call before other listeners; default false
96
+ alwaysLast: boolean, // Always call after other listeners; default false
97
+ start: number, // Start calling after 3rd trigger; default 0
98
+ context: object, // Listener context (this); default undefined
99
+ tags: string[], // Listener tags for filtering; default undefined
100
+ async: booleantrue, // Call this listener asynchronously; default false
101
+ extraData: object, // Custom data will be passed to filter()
102
+ });
103
+ ```
104
+
105
+ ### Collector
106
+
107
+ Collector allows you to gather data from listeners.
108
+
109
+ ```typescript
110
+ type ApplicationData = {
111
+ user: {
112
+ username?: string;
113
+ role?: string;
114
+ loggedIn: boolean;
115
+ };
116
+ notifications: {
117
+ type: string;
118
+ message: string;
119
+ }[];
120
+ }
121
+ const event = createEvent(<() => Partial<ApplicationData>>);
122
+ event.addListener(() => {
123
+ return {
124
+ user: {
125
+ username: "john",
126
+ role: "admin",
127
+ loggedIn: true
128
+ }
129
+ }
130
+ });
131
+ event.addListener(() => {
132
+ return {
133
+ notifications: [
134
+ {
135
+ type: "chat",
136
+ message: "You've got a new message!"
137
+ }
138
+ ]
139
+ }
140
+ });
141
+
142
+
143
+ const applicationData = event.merge();
144
+ ```
145
+
146
+ ### Pipe
147
+
148
+ Data flows through listeners in a pipeline, each transforming the data
149
+
150
+ ```typescript
151
+ const event = createEvent((value: number) => number);
152
+ event.addListener(value => value + value);
153
+ event.addListener(value => value * value);
154
+ const value = event.pipe(1); // value = 4
155
+ ```
156
+
157
+ ### Event API
158
+
159
+ #### Core Methods
160
+
161
+ - `addListener(listener, options?)` - Add event listener
162
+ - **Aliases**: `on()`, `listen()`, `subscribe()`
163
+ - `removeListener(listener, context?, tag?)` - Remove specific listener
164
+ - **Aliases**: `un()`, `off()`, `remove()`, `unsubscribe()`
165
+ - `hasListener(listener?, context?, tag?)` - Check if listener exists
166
+ - **Aliases**: `has()`
167
+ - `removeAllListeners(tag?)` - Remove all listeners (optionally by tag)
168
+ - `trigger(...args)` - Trigger the event
169
+ - **Aliases**: `emit()`, `dispatch()`
170
+
171
+ #### Collector Methods
172
+
173
+ - `first(...args)` - Get first listener's result
174
+ - `last(...args)` - Get last listener's result
175
+ - `all(...args)` - Get all listener results
176
+ - `merge(...args)` - Merge all results (for arrays/objects)
177
+ - `concat(...args)` - Concatenate all results
178
+ - `firstNonEmpty(...args)` - Get first non-empty result
179
+ - `untilTrue(...args)` - Stop when listener returns true
180
+ - `untilFalse(...args)` - Stop when listener returns false
181
+ - `pipe(...args)` - Pipe data through listeners
182
+
183
+ #### Async Versions
184
+
185
+ - `resolveFirst(...args)` - Async version of first()
186
+ - `resolveLast(...args)` - Async version of last()
187
+ - `resolveAll(...args)` - Async version of all()
188
+ - `resolveMerge(...args)` - Async version of merge()
189
+ - `resolveConcat(...args)` - Async version of concat()
190
+ - `resolveFirstNonEmpty(...args)` - Async version of firstNonEmpty()
191
+ - `resolvePipe(...args)` - Async version of pipe()
192
+
193
+ #### Utility Methods
194
+
195
+ - `promise(options?: ListenerOptions)` - Get a promise that resolves on next trigger
196
+ - `suspend(withQueue?: boolean)` - Suspend event triggering; When `withQueue=true`, all trigger calls will be queued and replayed after resume()
197
+ - `resume()` - Resume event triggering
198
+ - `reset()` - Reset event state
199
+ - `withTags(tags: string[], callback: () => CallbackResponse) => CallbackResponse` - Execute callback with specific tags
200
+
201
+ ## EventBus
202
+
203
+ ### Description
204
+
205
+ EventBus provides centralized event management for applications. It allows you to define multiple named events and manage them together with features like event source integration, proxying, and interception.
206
+
207
+ ### Basic Usage
208
+
209
+ ```typescript
210
+ import { createEventBus } from "@kuindji/reactive";
211
+
212
+ // Define event signatures
213
+ type AppEvents = {
214
+ userLogin: (userId: string) => void;
215
+ userLogout: (userId: string) => void;
216
+ dataUpdate: (data: any) => void;
217
+ };
218
+
219
+ // Create event bus
220
+ const eventBus = createEventBus<AppEvents>();
221
+
222
+ // Add listeners
223
+ eventBus.on("userLogin", (userId) => {
224
+ console.log(`User ${userId} logged in`);
225
+ });
226
+
227
+ // Trigger events
228
+ eventBus.trigger("userLogin", "user123");
229
+ ```
230
+
231
+ ### Relay
232
+
233
+ Relay allows you to forward events from one EventBus to another EventBus.
234
+
235
+ ```typescript
236
+ import { createEventBus, ProxyType } from "@kuindji/reactive";
237
+ // Create event buses
238
+ const mainBus = createEventBus<{
239
+ userLogin: (userId: string) => void;
240
+ userLogout: (userId: string) => void;
241
+ dataUpdate: (data: any) => void;
242
+ }>();
243
+
244
+ const externalBus = createEventBus<{
245
+ login: (userId: string) => void;
246
+ logout: (userId: string) => void;
247
+ update: (data: any) => void;
248
+ }>();
249
+
250
+ // Relay events from external bus to main bus
251
+ mainBus.relay({
252
+ eventSource: externalBus,
253
+ remoteEventName: "login",
254
+ localEventName: "userLogin",
255
+ });
256
+
257
+ mainBus.relay({
258
+ eventSource: externalBus,
259
+ remoteEventName: "logout",
260
+ localEventName: "userLogout",
261
+ });
262
+
263
+ mainBus.relay({
264
+ eventSource: externalBus,
265
+ remoteEventName: "update",
266
+ localEventName: "dataUpdate",
267
+ });
268
+
269
+ // Listen to events on main bus
270
+ mainBus.on("userLogin", (userId) => {
271
+ console.log(`User ${userId} logged in via relay`);
272
+ });
273
+
274
+ // Trigger on external bus - will be relayed to main bus
275
+ externalBus.trigger("login", "user123");
276
+ ```
277
+
278
+ #### Relay with Prefix
279
+
280
+ You can use prefixes to organize relayed events:
281
+
282
+ ```typescript
283
+ // Relay all events with a prefix
284
+ mainBus.relay({
285
+ eventSource: externalBus,
286
+ remoteEventName: "*", // Relay all events
287
+ localEventNamePrefix: "external-",
288
+ });
289
+
290
+ // Now external events will be available as:
291
+ // external-login, external-logout, external-update
292
+ mainBus.on("external-login", (userId) => {
293
+ console.log(`External login: ${userId}`);
294
+ });
295
+ ```
296
+
297
+ #### Relay with Different Proxy Types
298
+
299
+ You can control how relayed events handle return values:
300
+
301
+ ```typescript
302
+ // Relay with pipe proxy type - data flows through listeners
303
+ mainBus.relay({
304
+ eventSource: externalBus,
305
+ remoteEventName: "processData",
306
+ localEventName: "transformData",
307
+ proxyType: ProxyType.PIPE,
308
+ });
309
+ // now when you call remote "processData" event
310
+ // it will passed through mainBus's "transformData" pipeline and returned to externalBus.
311
+ const transformedData = externalBus.first("processData", { some: data });
312
+
313
+ // Relay with merge proxy type - merge results from all listeners
314
+ mainBus.relay({
315
+ eventSource: externalBus,
316
+ remoteEventName: "collectData",
317
+ localEventName: "aggregateData",
318
+ proxyType: ProxyType.MERGE,
319
+ });
320
+
321
+ // Relay with async resolve proxy type
322
+ mainBus.relay({
323
+ eventSource: externalBus,
324
+ remoteEventName: "asyncOperation",
325
+ localEventName: "handleAsync",
326
+ proxyType: ProxyType.RESOLVE_ALL,
327
+ });
328
+ ```
329
+
330
+ #### Unrelay
331
+
332
+ Stop relaying events:
333
+
334
+ ```typescript
335
+ // Stop relaying specific event
336
+ mainBus.unrelay({
337
+ eventSource: externalBus,
338
+ remoteEventName: "login",
339
+ localEventName: "userLogin",
340
+ });
341
+
342
+ // Stop relaying all events
343
+ mainBus.unrelay({
344
+ eventSource: externalBus,
345
+ remoteEventName: "*",
346
+ localEventNamePrefix: "external-",
347
+ });
348
+ ```
349
+
350
+ ### Event Source
351
+
352
+ Event sources allow you to integrate with external event systems that follow the EventSource interface. This is useful for WebSocket connections, Node.js EventEmitter, or custom event systems.
353
+
354
+ ```typescript
355
+ import { createEventBus } from "@kuindji/reactive";
356
+ import { EventEmitter } from "events";
357
+
358
+ // Create a Node.js EventEmitter as an event source
359
+ const nodeEmitter = new EventEmitter();
360
+
361
+ // Define the event source interface
362
+ const eventSource = {
363
+ name: "node-emitter",
364
+ on: (name: string, fn: (...args: any[]) => void) => {
365
+ nodeEmitter.on(name, fn);
366
+ },
367
+ un: (name: string, fn: (...args: any[]) => void) => {
368
+ nodeEmitter.off(name, fn);
369
+ },
370
+ accepts: (name: string) => true, // Accept all events
371
+ proxyType: ProxyType.TRIGGER,
372
+ };
373
+
374
+ // Create event bus
375
+ const eventBus = createEventBus<{
376
+ userAction: (action: string, userId: string) => void;
377
+ systemEvent: (event: string, data: any) => void;
378
+ }>();
379
+
380
+ // Add event source
381
+ eventBus.addEventSource(eventSource);
382
+
383
+ // Listen to events
384
+ eventBus.on("userAction", (action, userId) => {
385
+ console.log(`User ${userId} performed action: ${action}`);
386
+ });
387
+
388
+ // Emit on the external source - will be relayed to event bus
389
+ nodeEmitter.emit("userAction", "login", "user123");
390
+ ```
391
+
392
+ #### WebSocket Event Source
393
+
394
+ ```typescript
395
+ // Create WebSocket event source
396
+ const createWebSocketEventSource = (ws: WebSocket) => ({
397
+ name: "websocket",
398
+ on: (name: string, fn: (...args: any[]) => void) => {
399
+ ws.addEventListener("message", (event) => {
400
+ const data = JSON.parse(event.data);
401
+ if (data.type === name) {
402
+ fn(data.payload);
403
+ }
404
+ });
405
+ },
406
+ un: (name: string, fn: (...args: any[]) => void) => {
407
+ ws.removeEventListener("message", fn);
408
+ },
409
+ accepts: (name: string) => true,
410
+ proxyType: ProxyType.TRIGGER,
411
+ });
412
+
413
+ // Usage
414
+ const ws = new WebSocket("ws://localhost:8080");
415
+ const wsEventSource = createWebSocketEventSource(ws);
416
+
417
+ const eventBus = createEventBus<{
418
+ chatMessage: (message: string, userId: string) => void;
419
+ userJoined: (userId: string) => void;
420
+ userLeft: (userId: string) => void;
421
+ }>();
422
+
423
+ eventBus.addEventSource(wsEventSource);
424
+
425
+ // Listen to WebSocket events
426
+ eventBus.on("chatMessage", (message, userId) => {
427
+ console.log(`${userId}: ${message}`);
428
+ });
429
+ ```
430
+
431
+ #### Custom Event Source with Filtering
432
+
433
+ ```typescript
434
+ // Create custom event source with filtering
435
+ const createCustomEventSource = () => {
436
+ const listeners = new Map<string, Set<(...args: any[]) => void>>();
437
+
438
+ return {
439
+ name: "custom-source",
440
+ on: (name: string, fn: (...args: any[]) => void) => {
441
+ if (!listeners.has(name)) {
442
+ listeners.set(name, new Set());
443
+ }
444
+ listeners.get(name)!.add(fn);
445
+ },
446
+ un: (name: string, fn: (...args: any[]) => void) => {
447
+ listeners.get(name)?.delete(fn);
448
+ },
449
+ accepts: (name: string) => name.startsWith("app-"), // Only accept app-* events
450
+ proxyType: ProxyType.TRIGGER,
451
+
452
+ // Custom method to trigger events
453
+ trigger: (name: string, ...args: any[]) => {
454
+ listeners.get(name)?.forEach(fn => fn(...args));
455
+ },
456
+ };
457
+ };
458
+
459
+ const customSource = createCustomEventSource();
460
+ const eventBus = createEventBus<{
461
+ appStart: () => void;
462
+ appStop: () => void;
463
+ }>();
464
+
465
+ eventBus.addEventSource(customSource);
466
+
467
+ // Listen to custom events
468
+ eventBus.on("appStart", () => {
469
+ console.log("Application started");
470
+ });
471
+
472
+ // Trigger on custom source
473
+ customSource.trigger("appStart");
474
+ ```
475
+
476
+ ### EventBus API
477
+
478
+ #### Core Methods
479
+
480
+ - `addListener(name, handler, options?)` - Add listener to specific event
481
+ - **Aliases**: `on()`, `listen()`, `subscribe()`
482
+ - `once(name, handler, options?)` - Add one-time listener
483
+ - `removeListener(name, handler, context?, tag?)` - Remove listener
484
+ - **Aliases**: `un()`, `off()`, `remove()`, `unsubscribe()`
485
+ - `trigger(name, ...args)` - Trigger specific event
486
+ - **Aliases**: `emit()`, `dispatch()`
487
+ - `get(name)` - Get event instance by name
488
+ - `add(name, options?)` - Add new event to bus
489
+
490
+ #### Collector Methods
491
+
492
+ - `first(name, ...args)` - Get first listener result
493
+ - `last(name, ...args)` - Get last listener result
494
+ - `all(name, ...args)` - Get all listener results
495
+ - `merge(name, ...args)` - Merge all results
496
+ - `concat(name, ...args)` - Concatenate all results
497
+ - `firstNonEmpty(name, ...args)` - Get first non-empty result
498
+ - `untilTrue(name, ...args)` - Stop when listener returns true
499
+ - `untilFalse(name, ...args)` - Stop when listener returns false
500
+ - `pipe(name, ...args)` - Pipe data through listeners
501
+
502
+ #### Async Versions
503
+
504
+ - `resolveFirst(name, ...args)` - Async version of first()
505
+ - `resolveLast(name, ...args)` - Async version of last()
506
+ - `resolveAll(name, ...args)` - Async version of all()
507
+ - `resolveMerge(name, ...args)` - Async version of merge()
508
+ - `resolveConcat(name, ...args)` - Async version of concat()
509
+ - `resolveFirstNonEmpty(name, ...args)` - Async version of firstNonEmpty()
510
+ - `resolvePipe(name, ...args)` - Async version of pipe()
511
+
512
+ #### Advanced Features
513
+
514
+ - `intercept(fn)` - Intercept all event triggers
515
+ - `stopIntercepting()` - Stop interception
516
+ - `relay(options)` - Relay events from external sources
517
+ - `unrelay(options)` - Stop relaying events
518
+ - `addEventSource(source)` - Add external event source
519
+ - `removeEventSource(source)` - Remove event source
520
+ - `suspendAll(withQueue?)` - Suspend all events
521
+ - `resumeAll()` - Resume all events
522
+ - `reset()` - Reset all events
523
+ - `withTags(tags, callback)` - Execute callback with specific tags
524
+
525
+ ## Action
526
+
527
+ ### Description
528
+
529
+ Actions are async operations with built-in error handling and response tracking. They provide a structured way to handle async operations and their results.
530
+
531
+ ### Basic Usage
532
+
533
+ ```typescript
534
+ import { createAction } from "@kuindji/reactive";
535
+
536
+ // Define an async action
537
+ const fetchUserAction = createAction(async (userId: string) => {
538
+ const response = await fetch(`/api/users/${userId}`);
539
+ if (!response.ok) {
540
+ throw new Error("User not found");
541
+ }
542
+ return response.json();
543
+ });
544
+
545
+ // Add listeners for success/error
546
+ fetchUserAction.addListener(({ response, error, args }) => {
547
+ if (error) {
548
+ console.error("Action failed:", error);
549
+ }
550
+ else {
551
+ console.log("User data:", response);
552
+ }
553
+ });
554
+
555
+ // Invoke the action
556
+ const result = await fetchUserAction.invoke("user123");
557
+ ```
558
+
559
+ ### Action API
560
+
561
+ #### Core Methods
562
+
563
+ - `invoke(...args)` - Execute the action
564
+ - `addListener(handler, options?)` - Add response listener
565
+ - **Aliases**: `on()`, `listen()`, `subscribe()`
566
+ - `removeListener(handler, context?, tag?)` - Remove listener
567
+ - **Aliases**: `un()`, `off()`, `remove()`, `unsubscribe()`
568
+ - `hasListener(handler?, context?, tag?)` - Check if listener exists
569
+ - **Aliases**: `has()`
570
+ - `removeAllListeners(tag?)` - Remove all listeners
571
+
572
+ #### Error Handling
573
+
574
+ - `addErrorListener(handler, context?)` - Add error listener
575
+ - `removeErrorListener(handler, context?)` - Remove error listener
576
+ - `hasErrorListeners()` - Check if error listeners exist
577
+ - `removeAllErrorListeners(tag?)` - Remove all error listeners
578
+
579
+ #### Utility Methods
580
+
581
+ - `promise(options?)` - Get promise for next invocation
582
+ - `suspend(withQueue?)` - Suspend action execution
583
+ - `resume()` - Resume action execution
584
+ - `reset()` - Reset action state
585
+
586
+ ## ActionMap
587
+
588
+ ### Description
589
+
590
+ ActionMap provides a way to organize multiple actions with centralized error handling. It's useful for managing related actions in a structured way.
591
+
592
+ ### Basic Usage
593
+
594
+ ```typescript
595
+ import { createActionMap } from "@kuindji/reactive";
596
+
597
+ // Define actions
598
+ const actions = {
599
+ fetchUser: async (userId: string) => {
600
+ const response = await fetch(`/api/users/${userId}`);
601
+ return response.json();
602
+ },
603
+ updateUser: async (userId: string, data: any) => {
604
+ const response = await fetch(`/api/users/${userId}`, {
605
+ method: "PUT",
606
+ body: JSON.stringify(data),
607
+ });
608
+ return response.json();
609
+ },
610
+ deleteUser: async (userId: string) => {
611
+ await fetch(`/api/users/${userId}`, { method: "DELETE" });
612
+ },
613
+ };
614
+
615
+ // type ErrorResponse = {
616
+ // args: TriggerArgs[],
617
+ // error: Error,
618
+ // name?: ActionName,
619
+ // type?: "action"
620
+ // }
621
+
622
+ // Create action map with error handling
623
+ const actionMap = createActionMap(actions, (errorResponse: ErrorResponse) => {
624
+ console.error("Action failed:", errorResponse);
625
+ });
626
+
627
+ // Use actions
628
+ const user = await actionMap.fetchUser.invoke("user123");
629
+ await actionMap.updateUser.invoke("user123", { name: "John" });
630
+ ```
631
+
632
+ ### ActionMap API
633
+
634
+ The ActionMap returns an object where each key corresponds to an action with the same API as individual actions created with `createAction()`.
635
+
636
+ ## ActionBus
637
+
638
+ ### Description
639
+
640
+ ActionBus provides centralized action management similar to EventBus but for actions. It allows you to dynamically add and manage actions with built-in error handling.
641
+
642
+ ### Basic Usage
643
+
644
+ ```typescript
645
+ import { createActionBus } from "@kuindji/reactive";
646
+
647
+ type Actions = {
648
+ fetchUser: (userId: string) => Promise<UserData>;
649
+ updateUser: (userId: string, data: UserData) => Promise<UserData>;
650
+ };
651
+
652
+ // Create action bus
653
+ const actionBus = createActionBus<Actions>();
654
+
655
+ // Add actions
656
+ actionBus.add("fetchUser", async (userId) => {
657
+ const response = await fetch(`/api/users/${userId}`);
658
+ return response.json();
659
+ });
660
+
661
+ actionBus.add("updateUser", async (userId, data) => {
662
+ const response = await fetch(`/api/users/${userId}`, {
663
+ method: "PUT",
664
+ body: JSON.stringify(data),
665
+ });
666
+ return response.json();
667
+ });
668
+
669
+ // Add listeners
670
+ actionBus.on("fetchUser", ({ response, error }) => {
671
+ if (error) {
672
+ console.error("Failed to fetch user:", error);
673
+ }
674
+ else {
675
+ console.log("User fetched:", response);
676
+ }
677
+ });
678
+
679
+ // Invoke actions
680
+ const user = await actionBus.invoke("fetchUser", "user123");
681
+ ```
682
+
683
+ ### ActionBus API
684
+
685
+ #### Core Methods
686
+
687
+ - `add(name, action)` - Add action to bus
688
+ - `get(name)` - Get action by name
689
+ - `invoke(name, ...args)` - Invoke action by name
690
+ - `addListener(name, handler, options?)` - Add listener to action
691
+ - **Aliases**: `on()`, `listen()`, `subscribe()`
692
+ - `once(name, handler, options?)` - Add one-time listener
693
+ - `removeListener(name, handler, context?, tag?)` - Remove listener
694
+ - **Aliases**: `un()`, `off()`, `remove()`, `unsubscribe()`
695
+ - `has(name)` - Check if action exists
696
+ - `remove(name)` - Remove action
697
+
698
+ #### Error Handling
699
+
700
+ - `addErrorListener(handler)` - Add global error listener
701
+ - `removeErrorListener(handler)` - Remove global error listener
702
+ - `hasErrorListeners()` - Check if error listeners exist
703
+
704
+ ## Store
705
+
706
+ ### Description
707
+
708
+ Store provides reactive state management with change tracking, validation, and event-driven updates. It's designed for managing application state with full TypeScript support.
709
+
710
+ ### Basic Usage
711
+
712
+ ```typescript
713
+ import { createStore } from "@kuindji/reactive";
714
+
715
+ // Define store schema
716
+ type UserStore = {
717
+ id: string;
718
+ name: string;
719
+ email: string;
720
+ isLoggedIn: boolean;
721
+ };
722
+
723
+ // Create store with initial data
724
+ const userStore = createStore<UserStore>({
725
+ id: "",
726
+ name: "",
727
+ email: "",
728
+ isLoggedIn: false,
729
+ });
730
+
731
+ // Listen to changes
732
+ userStore.onChange("name", (newName, oldName) => {
733
+ console.log(`Name changed from ${oldName} to ${newName}`);
734
+ });
735
+
736
+ // Update store
737
+ userStore.set("name", "John Doe");
738
+ userStore.set("isLoggedIn", true);
739
+
740
+ // Get values
741
+ const name = userStore.get("name");
742
+ const userData = userStore.get([ "name", "email" ]); // { name: string, email: string }
743
+ ```
744
+
745
+ ### Store API
746
+
747
+ #### Core Methods
748
+
749
+ - `set(key, value)` - Set single property
750
+ - `set(data)` - Set multiple properties
751
+ - `asyncSet(key, value)` - Async set single property
752
+ - `asyncSet(data)` - Async set multiple properties
753
+ - `get(key)` - Get single property
754
+ - `get(keys)` - Get multiple properties
755
+ - `isEmpty()` - Check if store is empty
756
+ - `getData()` - Get all store data
757
+ - `reset()` - Reset store to initial state
758
+
759
+ #### Event Methods
760
+
761
+ - `onChange(key, handler)` - Listen to property changes
762
+ - `pipe(key, handler)` - Add data transformation pipeline
763
+ - `control(event, handler)` - Control store events
764
+
765
+ #### Control Events
766
+
767
+ - `beforeChange` - Fired before property changes (can prevent change)
768
+ - `change` - Fired after properties change
769
+ - `reset` - Fired when store is reset
770
+ - `error` - Fired when errors occur
771
+
772
+ #### Utility Methods
773
+
774
+ - `batch(fn)` - Batch multiple changes
775
+
776
+ ## React Hooks
777
+
778
+ ### Description
779
+
780
+ The library provides comprehensive React hooks for integrating reactive functionality into React components with automatic cleanup and error handling.
781
+
782
+ ### Basic Usage
783
+
784
+ ```typescript
785
+ import { useListenToEvent } from "@kuindji/reactive/react";
786
+
787
+ const event = createEvent(() => void);
788
+
789
+ function FirstComponent() {
790
+
791
+ // Use in component
792
+ const handleClick = () => {
793
+ event.trigger();
794
+ };
795
+
796
+ return (
797
+ <div>
798
+ <button onClick={handleClick}>Trigger event</button>
799
+ </div>
800
+ );
801
+ }
802
+
803
+ function AnotherComponent() {
804
+ const handler = useCallback(
805
+ () => {
806
+ console.log("something happened in first component")
807
+ },
808
+ []
809
+ );
810
+ useListenToEvent(event, handler);
811
+ }
812
+ ```
813
+
814
+ ### Available Hooks
815
+
816
+ Create and use event
817
+
818
+ ```typescript
819
+ const event = useEvent(
820
+ listenerOptions?: ListenerOptions,
821
+ listener?: Function,
822
+ errorListener?: (errorResponse) => void
823
+ );
824
+ ```
825
+
826
+ Listen to event
827
+
828
+ ```typescript
829
+ useListenToEvent(
830
+ event: Event,
831
+ listener?: Function,
832
+ errorListener?: (errorResponse) => void
833
+ )
834
+ ```
835
+
836
+ Create and use event bus
837
+
838
+ ```typescript
839
+ type EventBus = {
840
+ eventName: Function
841
+ }
842
+ type EventBusOptions = {
843
+ eventName: EventOptions
844
+ }
845
+ const eventBus = useEventBus<EventBus>(
846
+ options?: EventBusOptions,
847
+ allEventsListener?: Function,
848
+ errorListener?: (errorResponse) => void
849
+ );
850
+ ```
851
+
852
+ Listen to bus events
853
+
854
+ ```typescript
855
+ useListenToEventBus(
856
+ eventBus: EventBus,
857
+ eventName: string,
858
+ listener: Function,
859
+ listenerOptions?: ListenerOptions,
860
+ errorListener?: (errorResponse) => void
861
+ )
862
+ ```
863
+
864
+ Create and use action
865
+
866
+ ```typescript
867
+ const action = useAction(
868
+ action: Function,
869
+ listener?: Function,
870
+ errorListener?: (errorResponse) => void
871
+ );
872
+ ```
873
+
874
+ Listen to action events
875
+
876
+ ```typescript
877
+ useListenToAction(
878
+ action: Action,
879
+ listener?: Function,
880
+ errorListener?: (errorResponse) => void
881
+ )
882
+ ```
883
+
884
+ Create and use action map
885
+
886
+ ```typescript
887
+ const actionMap = useActionMap(
888
+ actions: {
889
+ actionName: Function
890
+ },
891
+ errorListener?: (errorResponse) => void
892
+ )
893
+ ```
894
+
895
+ Create and use action bus
896
+
897
+ ```typescript
898
+ type ActionsMap = {
899
+ actionName: FunctionSignature
900
+ }
901
+ const actionBus = useActionBus<ActionsMap>(
902
+ initialActions: Partial<ActionsMap>,
903
+ errorListener?: (errorResponse) => void
904
+ )
905
+ ```
906
+
907
+ Create and use data store
908
+
909
+ ```typescript
910
+ type PropTypes = {
911
+ propName: number
912
+ }
913
+ const store = useStore<PropTypes>(
914
+ initialData: Partial<PropTypes>,
915
+ config: {
916
+ onChange: {
917
+ [K in keyof PropTypes]: (
918
+ value: PropTypes[K],
919
+ prevValue: PropTypes[K] | undefined
920
+ ) => void
921
+ };
922
+ pipes: {
923
+ [K in keyof PropTypes]: (
924
+ value: PropTypes[K]
925
+ ) => PropTypes[K]
926
+ };
927
+ control: {
928
+ beforeChange: (name, value) => boolean;
929
+ change: (names) => void;
930
+ error: (errorResponse) => void;
931
+ reset: () => void;
932
+ }
933
+ }
934
+ );
935
+ ```
936
+
937
+ Use store value as state
938
+
939
+ ```typescript
940
+ const [ value, setValue ] = useStoreStore(
941
+ store: Store,
942
+ key: string
943
+ )
944
+ ```
945
+
946
+ ## ErrorBoundary
947
+
948
+ ### Description
949
+
950
+ ErrorBoundary provides catch-all error listener for actions and events. Without ErrorBoundary (or with empty "listener") and without error listener passed directly to them they will re-throw errors from listeners.
951
+
952
+ ### Basic Usage
953
+
954
+ ```typescript
955
+ import { ErrorBoundary } from "@kuindji/reactive/react";
956
+
957
+ function App() {
958
+ return (
959
+ <ErrorBoundary
960
+ listener={(errorResponse) => {
961
+ console.error("Reactive error:", errorResponse);
962
+ // Send to error reporting service
963
+ }}>
964
+ <UserComponent />
965
+ </ErrorBoundary>
966
+ );
967
+ }
968
+ ```
969
+
970
+ ### ErrorBoundary API
971
+
972
+ - `children` - React children to render
973
+ - `listener` - Error listener function (optional)
974
+
975
+ ## License
976
+
977
+ ISC License
978
+
979
+ ## Contributing
980
+
981
+ Contributions are welcome! Please feel free to submit a Pull Request.
982
+
983
+ ## Support
984
+
985
+ If you encounter any issues or have questions, please [open an issue](https://github.com/kuindji/reactive/issues) on GitHub.