@context-action/core 0.8.1 โ 0.8.6
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 +104 -3
- package/dist/index.cjs +361 -19
- package/dist/index.d.cts +127 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +127 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +355 -19
- package/dist/index.js.map +1 -1
- package/package.json +37 -23
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @context-action/core
|
|
2
2
|
|
|
3
|
-
Type-safe action pipeline management library for JavaScript/TypeScript applications with advanced filtering, performance optimizations, and React integration support.
|
|
3
|
+
Type-safe action pipeline management library for **vanilla JavaScript/TypeScript** applications with advanced filtering, performance optimizations, and optional React integration support.
|
|
4
|
+
|
|
5
|
+
> **โจ Framework-Agnostic**: Works with vanilla JavaScript, React, Vue, Svelte, or any JavaScript environment. No framework dependencies required!
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
@@ -10,6 +12,15 @@ npm install @context-action/core
|
|
|
10
12
|
pnpm install @context-action/core
|
|
11
13
|
```
|
|
12
14
|
|
|
15
|
+
### CDN (for quick prototyping)
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<script type="module">
|
|
19
|
+
import { ActionRegister } from 'https://esm.sh/@context-action/core@latest';
|
|
20
|
+
// Your code here
|
|
21
|
+
</script>
|
|
22
|
+
```
|
|
23
|
+
|
|
13
24
|
## Quick Start
|
|
14
25
|
|
|
15
26
|
```typescript
|
|
@@ -42,6 +53,94 @@ await actions.dispatch('increment');
|
|
|
42
53
|
await actions.dispatch('setCount', 42);
|
|
43
54
|
```
|
|
44
55
|
|
|
56
|
+
## ๐ Vanilla JavaScript Support
|
|
57
|
+
|
|
58
|
+
**@context-action/core works perfectly with vanilla JavaScript!** No React, Vue, or any framework required.
|
|
59
|
+
|
|
60
|
+
### Browser Example (HTML + JavaScript)
|
|
61
|
+
|
|
62
|
+
```html
|
|
63
|
+
<!DOCTYPE html>
|
|
64
|
+
<html>
|
|
65
|
+
<head>
|
|
66
|
+
<title>Context-Action Example</title>
|
|
67
|
+
</head>
|
|
68
|
+
<body>
|
|
69
|
+
<div id="counter">0</div>
|
|
70
|
+
<button id="increment">Increment</button>
|
|
71
|
+
|
|
72
|
+
<script type="module">
|
|
73
|
+
import { ActionRegister } from 'https://esm.sh/@context-action/core@latest';
|
|
74
|
+
|
|
75
|
+
// Simple store
|
|
76
|
+
class Store {
|
|
77
|
+
constructor(initialState) {
|
|
78
|
+
this.state = initialState;
|
|
79
|
+
this.listeners = new Set();
|
|
80
|
+
}
|
|
81
|
+
getValue() { return this.state; }
|
|
82
|
+
setValue(newState) {
|
|
83
|
+
this.state = newState;
|
|
84
|
+
this.listeners.forEach(fn => fn(this.state));
|
|
85
|
+
}
|
|
86
|
+
subscribe(listener) {
|
|
87
|
+
this.listeners.add(listener);
|
|
88
|
+
return () => this.listeners.delete(listener);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Create store and actions
|
|
93
|
+
const counterStore = new Store({ count: 0 });
|
|
94
|
+
const actions = new ActionRegister({ name: 'Counter' });
|
|
95
|
+
|
|
96
|
+
// Register handler
|
|
97
|
+
actions.register('increment', () => {
|
|
98
|
+
const current = counterStore.getValue();
|
|
99
|
+
counterStore.setValue({ count: current.count + 1 });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Subscribe to updates
|
|
103
|
+
counterStore.subscribe(state => {
|
|
104
|
+
document.getElementById('counter').textContent = state.count;
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Wire up button
|
|
108
|
+
document.getElementById('increment').onclick = () => {
|
|
109
|
+
actions.dispatch('increment');
|
|
110
|
+
};
|
|
111
|
+
</script>
|
|
112
|
+
</body>
|
|
113
|
+
</html>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Node.js Example
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
import { ActionRegister } from '@context-action/core';
|
|
120
|
+
|
|
121
|
+
const actions = new ActionRegister({ name: 'MyApp' });
|
|
122
|
+
|
|
123
|
+
actions.register('processData', async (data, controller) => {
|
|
124
|
+
console.log('Processing:', data);
|
|
125
|
+
|
|
126
|
+
// Business logic here
|
|
127
|
+
const result = await someAsyncOperation(data);
|
|
128
|
+
|
|
129
|
+
controller.setResult(result);
|
|
130
|
+
}, { priority: 100 });
|
|
131
|
+
|
|
132
|
+
// Dispatch action
|
|
133
|
+
const result = await actions.dispatchWithResult('processData', {
|
|
134
|
+
input: 'example'
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
console.log('Result:', result.successResults);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**๐ Learn More:**
|
|
141
|
+
- [Vanilla JavaScript Guide](../../docs/en/guide/vanilla-js-guide.md) - Complete guide with examples
|
|
142
|
+
- [Live Examples](../../examples/vanilla-js/) - Interactive HTML examples (counter, todo app)
|
|
143
|
+
|
|
45
144
|
### Memory Management
|
|
46
145
|
|
|
47
146
|
```typescript
|
|
@@ -489,5 +588,7 @@ Apache-2.0
|
|
|
489
588
|
|
|
490
589
|
- [Main Repository](https://github.com/mineclover/context-action)
|
|
491
590
|
- [Documentation](https://mineclover.github.io/context-action/)
|
|
492
|
-
- [
|
|
493
|
-
- [Examples](../../
|
|
591
|
+
- [Vanilla JS Guide](../../docs/en/guide/vanilla-js-guide.md) - Complete vanilla JavaScript guide
|
|
592
|
+
- [Vanilla JS Examples](../../examples/vanilla-js/) - Interactive examples (counter, todo app)
|
|
593
|
+
- [React Package](../react/README.md) - React integration
|
|
594
|
+
- [Examples](../../example/README.md) - React example application
|
package/dist/index.cjs
CHANGED
|
@@ -315,27 +315,91 @@ var ActionGuard = class {
|
|
|
315
315
|
this.guards = /* @__PURE__ */ new Map();
|
|
316
316
|
this.maxIdleTime = 6e4;
|
|
317
317
|
this.cleanupIntervalMs = 3e4;
|
|
318
|
+
this.maxGuards = 1e3;
|
|
319
|
+
this.accessOrder = [];
|
|
318
320
|
if (autoCleanup) this.startAutoCleanup();
|
|
319
321
|
}
|
|
320
322
|
/**
|
|
321
323
|
* Start automatic cleanup of idle guard states
|
|
322
|
-
*
|
|
324
|
+
*
|
|
323
325
|
* @internal
|
|
324
326
|
*/
|
|
325
327
|
startAutoCleanup() {
|
|
326
328
|
this.cleanupInterval = setInterval(() => {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
329
|
+
this.performCleanup();
|
|
330
|
+
}, this.cleanupIntervalMs);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* ๐ง Optimized cleanup with early exit and batched operations
|
|
334
|
+
*
|
|
335
|
+
* @internal
|
|
336
|
+
*/
|
|
337
|
+
performCleanup() {
|
|
338
|
+
const guardCount = this.guards.size;
|
|
339
|
+
if (guardCount === 0) return;
|
|
340
|
+
const now = Date.now();
|
|
341
|
+
const keysToDelete = [];
|
|
342
|
+
if (guardCount <= 10) this.guards.forEach((state, key) => {
|
|
343
|
+
const isIdle = now - state.lastExecuted > this.maxIdleTime;
|
|
344
|
+
const hasActiveTimers = state.debounceTimer || state.throttleTimer;
|
|
345
|
+
if (isIdle && !hasActiveTimers) keysToDelete.push(key);
|
|
346
|
+
});
|
|
347
|
+
else {
|
|
348
|
+
const entriesToCheck = Math.min(this.accessOrder.length, Math.ceil(guardCount / 4));
|
|
349
|
+
for (let i = 0; i < entriesToCheck; i++) {
|
|
350
|
+
const key = this.accessOrder[i];
|
|
351
|
+
if (!key) continue;
|
|
352
|
+
const state = this.guards.get(key);
|
|
353
|
+
if (!state) {
|
|
354
|
+
keysToDelete.push(key);
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
330
357
|
const isIdle = now - state.lastExecuted > this.maxIdleTime;
|
|
331
358
|
const hasActiveTimers = state.debounceTimer || state.throttleTimer;
|
|
332
359
|
if (isIdle && !hasActiveTimers) keysToDelete.push(key);
|
|
333
|
-
});
|
|
334
|
-
if (keysToDelete.length > 0) {
|
|
335
|
-
keysToDelete.forEach((key) => this.guards.delete(key));
|
|
336
|
-
if (typeof process !== "undefined" && process.env?.DEBUG_CONTEXT_ACTION) console.debug(`[ActionGuard] Cleaned up ${keysToDelete.length} idle guards`);
|
|
337
360
|
}
|
|
338
|
-
}
|
|
361
|
+
}
|
|
362
|
+
if (keysToDelete.length > 0) {
|
|
363
|
+
keysToDelete.forEach((key) => {
|
|
364
|
+
this.guards.delete(key);
|
|
365
|
+
const accessIndex = this.accessOrder.indexOf(key);
|
|
366
|
+
if (accessIndex !== -1) this.accessOrder.splice(accessIndex, 1);
|
|
367
|
+
});
|
|
368
|
+
if (typeof process !== "undefined" && process.env?.DEBUG_CONTEXT_ACTION) console.debug(`[ActionGuard] Cleaned up ${keysToDelete.length} idle guards`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* ๐ง Update access order for LRU tracking
|
|
373
|
+
*
|
|
374
|
+
* @internal
|
|
375
|
+
*/
|
|
376
|
+
updateAccessOrder(key) {
|
|
377
|
+
const existingIndex = this.accessOrder.indexOf(key);
|
|
378
|
+
if (existingIndex !== -1) this.accessOrder.splice(existingIndex, 1);
|
|
379
|
+
this.accessOrder.push(key);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* ๐ง Evict oldest guards if max limit exceeded
|
|
383
|
+
*
|
|
384
|
+
* @internal
|
|
385
|
+
*/
|
|
386
|
+
evictIfNeeded() {
|
|
387
|
+
if (this.guards.size >= this.maxGuards) {
|
|
388
|
+
const evictCount = Math.ceil(this.maxGuards * .1);
|
|
389
|
+
this.accessOrder.slice(0, evictCount).forEach((key) => {
|
|
390
|
+
const state = this.guards.get(key);
|
|
391
|
+
if (state) {
|
|
392
|
+
if (state.debounceTimer) {
|
|
393
|
+
clearTimeout(state.debounceTimer);
|
|
394
|
+
if (state.debounceResolve) state.debounceResolve(false);
|
|
395
|
+
}
|
|
396
|
+
if (state.throttleTimer) clearTimeout(state.throttleTimer);
|
|
397
|
+
}
|
|
398
|
+
this.guards.delete(key);
|
|
399
|
+
});
|
|
400
|
+
this.accessOrder = this.accessOrder.slice(evictCount);
|
|
401
|
+
if (typeof process !== "undefined" && process.env?.DEBUG_CONTEXT_ACTION) console.debug(`[ActionGuard] Evicted ${evictCount} oldest guards due to limit`);
|
|
402
|
+
}
|
|
339
403
|
}
|
|
340
404
|
/**
|
|
341
405
|
* Apply debouncing to an action
|
|
@@ -360,6 +424,7 @@ var ActionGuard = class {
|
|
|
360
424
|
* @internal
|
|
361
425
|
*/
|
|
362
426
|
async debounce(actionKey, debounceMs) {
|
|
427
|
+
this.evictIfNeeded();
|
|
363
428
|
/** Get or create guard state for this action */
|
|
364
429
|
let state = this.guards.get(actionKey);
|
|
365
430
|
if (!state) {
|
|
@@ -374,6 +439,7 @@ var ActionGuard = class {
|
|
|
374
439
|
};
|
|
375
440
|
this.guards.set(actionKey, state);
|
|
376
441
|
}
|
|
442
|
+
this.updateAccessOrder(actionKey);
|
|
377
443
|
/** Clear any existing debounce timer to restart the delay period */
|
|
378
444
|
if (state.debounceTimer) {
|
|
379
445
|
clearTimeout(state.debounceTimer);
|
|
@@ -418,6 +484,7 @@ var ActionGuard = class {
|
|
|
418
484
|
* @internal
|
|
419
485
|
*/
|
|
420
486
|
throttle(actionKey, throttleMs) {
|
|
487
|
+
this.evictIfNeeded();
|
|
421
488
|
/** Get or create guard state for this action */
|
|
422
489
|
let state = this.guards.get(actionKey);
|
|
423
490
|
if (!state) {
|
|
@@ -432,6 +499,7 @@ var ActionGuard = class {
|
|
|
432
499
|
};
|
|
433
500
|
this.guards.set(actionKey, state);
|
|
434
501
|
}
|
|
502
|
+
this.updateAccessOrder(actionKey);
|
|
435
503
|
const now = Date.now();
|
|
436
504
|
const timeSinceLastExecution = now - state.lastExecuted;
|
|
437
505
|
/** Check if enough time has passed since last execution */
|
|
@@ -549,6 +617,7 @@ var ActionGuard = class {
|
|
|
549
617
|
this.cleanupInterval = void 0;
|
|
550
618
|
}
|
|
551
619
|
this.clearAll();
|
|
620
|
+
this.accessOrder = [];
|
|
552
621
|
}
|
|
553
622
|
/**
|
|
554
623
|
* ๐ Get statistics about active guards
|
|
@@ -633,7 +702,11 @@ var OperationQueue = class {
|
|
|
633
702
|
* - ์์
์๋ฃ ์ ๋๊ธฐ ์ค์ธ ํ๋ก์ธ์ค์๊ฒ ์๋ ์๋ฆผ
|
|
634
703
|
*/
|
|
635
704
|
async processQueue() {
|
|
636
|
-
if (this.processingPromise)
|
|
705
|
+
if (this.processingPromise) {
|
|
706
|
+
await this.processingPromise;
|
|
707
|
+
if (this.queue.length > 0 && !this.processingPromise) return this.processQueue();
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
637
710
|
this.processingPromise = this._doProcess();
|
|
638
711
|
try {
|
|
639
712
|
await this.processingPromise;
|
|
@@ -745,6 +818,95 @@ var OperationQueue = class {
|
|
|
745
818
|
}
|
|
746
819
|
};
|
|
747
820
|
|
|
821
|
+
//#endregion
|
|
822
|
+
//#region src/errors.ts
|
|
823
|
+
/**
|
|
824
|
+
* Action payload ๊ฒ์ฆ ์คํจ ์๋ฌ
|
|
825
|
+
*
|
|
826
|
+
* dispatch ์ Zod ์คํค๋ง ๊ฒ์ฆ์ด ์คํจํ๋ฉด ๋ฐ์ํฉ๋๋ค.
|
|
827
|
+
* (validationMode๊ฐ 'strict'์ผ ๋๋ง throw)
|
|
828
|
+
*
|
|
829
|
+
* @example
|
|
830
|
+
* ```typescript
|
|
831
|
+
* try {
|
|
832
|
+
* dispatch('updateUser', { id: '', name: 'John' });
|
|
833
|
+
* } catch (error) {
|
|
834
|
+
* if (error instanceof ActionValidationError) {
|
|
835
|
+
* console.log('Action:', error.action);
|
|
836
|
+
* console.log('Issues:', error.issues);
|
|
837
|
+
* console.log('Formatted:', error.formattedErrors);
|
|
838
|
+
* }
|
|
839
|
+
* }
|
|
840
|
+
* ```
|
|
841
|
+
*/
|
|
842
|
+
var ActionValidationError = class ActionValidationError extends Error {
|
|
843
|
+
/**
|
|
844
|
+
* @param action - ๊ฒ์ฆ ์คํจํ action ์ด๋ฆ
|
|
845
|
+
* @param zodError - Zod ๊ฒ์ฆ ์๋ฌ ๊ฐ์ฒด (ZodError compatible)
|
|
846
|
+
*/
|
|
847
|
+
constructor(action, zodError) {
|
|
848
|
+
const message = `Action "${action}" payload validation failed: ${zodError && typeof zodError === "object" && "message" in zodError ? String(zodError.message) : "Validation failed"}`;
|
|
849
|
+
super(message);
|
|
850
|
+
this.name = "ActionValidationError";
|
|
851
|
+
this.action = action;
|
|
852
|
+
this.zodError = zodError;
|
|
853
|
+
Object.setPrototypeOf(this, ActionValidationError.prototype);
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Zod ๊ฒ์ฆ ์ด์ ๋ชฉ๋ก
|
|
857
|
+
*/
|
|
858
|
+
get issues() {
|
|
859
|
+
if (this.zodError && typeof this.zodError === "object" && "issues" in this.zodError && Array.isArray(this.zodError.issues)) return this.zodError.issues;
|
|
860
|
+
return [];
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* ํฌ๋งท๋ ์๋ฌ ๊ฐ์ฒด (ํ๋๋ณ ์๋ฌ ๋ฉ์์ง)
|
|
864
|
+
*/
|
|
865
|
+
get formattedErrors() {
|
|
866
|
+
if (this.zodError && typeof this.zodError === "object" && "format" in this.zodError && typeof this.zodError.format === "function") return this.zodError.format();
|
|
867
|
+
return {};
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* ํ๋ซ ์๋ฌ ๋งต (ํ๋๋ช
โ ์๋ฌ ๋ฉ์์ง ๋ฐฐ์ด)
|
|
871
|
+
*/
|
|
872
|
+
get flattenedErrors() {
|
|
873
|
+
if (this.zodError && typeof this.zodError === "object" && "flatten" in this.zodError && typeof this.zodError.flatten === "function") return this.zodError.flatten();
|
|
874
|
+
return {
|
|
875
|
+
fieldErrors: {},
|
|
876
|
+
formErrors: []
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* ์ฒซ ๋ฒ์งธ ์๋ฌ ๋ฉ์์ง
|
|
881
|
+
*/
|
|
882
|
+
get firstError() {
|
|
883
|
+
return this.issues[0]?.message;
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* ์๋ฌ ๋ฐ์ ํ๋ ๊ฒฝ๋ก ๋ชฉ๋ก
|
|
887
|
+
*/
|
|
888
|
+
get errorPaths() {
|
|
889
|
+
return this.issues.map((issue) => issue.path.map((p) => String(p)).join("."));
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* JSON ์ง๋ ฌํ
|
|
893
|
+
*/
|
|
894
|
+
toJSON() {
|
|
895
|
+
return {
|
|
896
|
+
name: this.name,
|
|
897
|
+
action: this.action,
|
|
898
|
+
message: this.message,
|
|
899
|
+
issues: this.issues
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
/**
|
|
904
|
+
* ActionValidationError ํ์
๊ฐ๋
|
|
905
|
+
*/
|
|
906
|
+
function isActionValidationError(error) {
|
|
907
|
+
return error instanceof ActionValidationError;
|
|
908
|
+
}
|
|
909
|
+
|
|
748
910
|
//#endregion
|
|
749
911
|
//#region src/ActionRegister.ts
|
|
750
912
|
/**
|
|
@@ -762,6 +924,35 @@ var OperationQueue = class {
|
|
|
762
924
|
*
|
|
763
925
|
* @public
|
|
764
926
|
*/
|
|
927
|
+
/**
|
|
928
|
+
* Type guard to determine if an object is DispatchOptions
|
|
929
|
+
* Extracted as utility function for reuse and performance
|
|
930
|
+
*
|
|
931
|
+
* @param obj - Object to check
|
|
932
|
+
* @returns True if object is DispatchOptions
|
|
933
|
+
* @internal
|
|
934
|
+
*/
|
|
935
|
+
function isDispatchOptions(obj) {
|
|
936
|
+
if (!obj || typeof obj !== "object") return false;
|
|
937
|
+
if ("debounce" in obj && typeof obj.debounce === "number") return true;
|
|
938
|
+
if ("throttle" in obj && typeof obj.throttle === "number") return true;
|
|
939
|
+
if ("executionMode" in obj) return true;
|
|
940
|
+
if ("signal" in obj && obj.signal instanceof AbortSignal) return true;
|
|
941
|
+
if ("immediate" in obj && typeof obj.immediate === "boolean") return true;
|
|
942
|
+
if ("queuePriority" in obj && typeof obj.queuePriority === "number") return true;
|
|
943
|
+
if ("timeout" in obj && typeof obj.timeout === "number") return true;
|
|
944
|
+
if ("retryOnError" in obj && typeof obj.retryOnError === "object") return true;
|
|
945
|
+
if ("autoAbort" in obj && typeof obj.autoAbort === "object") return true;
|
|
946
|
+
if ("filter" in obj && typeof obj.filter === "object" && obj.filter !== null) {
|
|
947
|
+
const filter = obj.filter;
|
|
948
|
+
if ("handlerIds" in filter || "excludeHandlerIds" in filter || "priority" in filter || "custom" in filter) return true;
|
|
949
|
+
}
|
|
950
|
+
if ("result" in obj && typeof obj.result === "object" && obj.result !== null) {
|
|
951
|
+
const result = obj.result;
|
|
952
|
+
if ("strategy" in result || "merger" in result || "collect" in result || "maxResults" in result || "includeErrors" in result) return true;
|
|
953
|
+
}
|
|
954
|
+
return false;
|
|
955
|
+
}
|
|
765
956
|
var ActionRegister = class {
|
|
766
957
|
constructor(config = {}) {
|
|
767
958
|
this.pipelines = /* @__PURE__ */ new Map();
|
|
@@ -810,18 +1001,16 @@ var ActionRegister = class {
|
|
|
810
1001
|
* @public
|
|
811
1002
|
*/
|
|
812
1003
|
get actions() {
|
|
813
|
-
|
|
1004
|
+
if (!this._actionsProxy) this._actionsProxy = new Proxy({}, { get: (_target, prop) => {
|
|
814
1005
|
if (typeof prop === "string" && this.pipelines.has(prop)) {
|
|
815
1006
|
const actionKey = prop;
|
|
816
1007
|
return (payloadOrOptions, options) => {
|
|
817
|
-
const isDispatchOptions = (obj) => {
|
|
818
|
-
return obj && typeof obj === "object" && ("debounce" in obj || "throttle" in obj || "executionMode" in obj || "signal" in obj || "immediate" in obj || "filter" in obj || "result" in obj);
|
|
819
|
-
};
|
|
820
1008
|
if (payloadOrOptions && isDispatchOptions(payloadOrOptions)) return this.dispatch(actionKey, void 0, payloadOrOptions);
|
|
821
1009
|
else return this.dispatch(actionKey, payloadOrOptions, options);
|
|
822
1010
|
};
|
|
823
1011
|
}
|
|
824
1012
|
} });
|
|
1013
|
+
return this._actionsProxy;
|
|
825
1014
|
}
|
|
826
1015
|
/**
|
|
827
1016
|
* Actions-based dispatching with result collection
|
|
@@ -847,18 +1036,16 @@ var ActionRegister = class {
|
|
|
847
1036
|
* @returns Proxy object with action functions that return ExecutionResult
|
|
848
1037
|
*/
|
|
849
1038
|
get actionsWithResult() {
|
|
850
|
-
|
|
1039
|
+
if (!this._actionsWithResultProxy) this._actionsWithResultProxy = new Proxy({}, { get: (_target, prop) => {
|
|
851
1040
|
if (typeof prop === "string" && this.pipelines.has(prop)) {
|
|
852
1041
|
const actionKey = prop;
|
|
853
1042
|
return (payloadOrOptions, options) => {
|
|
854
|
-
const isDispatchOptions = (obj) => {
|
|
855
|
-
return obj && typeof obj === "object" && ("debounce" in obj || "throttle" in obj || "executionMode" in obj || "signal" in obj || "immediate" in obj || "filter" in obj || "result" in obj);
|
|
856
|
-
};
|
|
857
1043
|
if (payloadOrOptions && isDispatchOptions(payloadOrOptions)) return this.dispatchWithResult(actionKey, void 0, payloadOrOptions);
|
|
858
1044
|
else return this.dispatchWithResult(actionKey, payloadOrOptions, options);
|
|
859
1045
|
};
|
|
860
1046
|
}
|
|
861
1047
|
} });
|
|
1048
|
+
return this._actionsWithResultProxy;
|
|
862
1049
|
}
|
|
863
1050
|
/**
|
|
864
1051
|
* Register an action handler with optional configuration
|
|
@@ -1043,6 +1230,20 @@ var ActionRegister = class {
|
|
|
1043
1230
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1044
1231
|
});
|
|
1045
1232
|
if (payload instanceof Event && process.env.NODE_ENV === "development") console.warn(`Event object passed to action "${String(action)}"`, payload.type);
|
|
1233
|
+
if (this.registryConfig?.schema && this.registryConfig?.validateOnDispatch !== false) {
|
|
1234
|
+
const actionSchema = this.registryConfig.schema[action];
|
|
1235
|
+
if (actionSchema) {
|
|
1236
|
+
const result = actionSchema.safeParse(payload);
|
|
1237
|
+
if (!result.success) {
|
|
1238
|
+
const mode = this.registryConfig.validationMode ?? "strict";
|
|
1239
|
+
if (mode === "strict") throw new ActionValidationError(action, result.error);
|
|
1240
|
+
else if (mode === "warn") {
|
|
1241
|
+
console.warn(`Action "${String(action)}" payload validation failed:`, result.error.message);
|
|
1242
|
+
this.log(`Validation warning for action '${String(action)}'`, { issues: result.error.issues }, "warn");
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1046
1247
|
const [effectiveSignal, autoAbortController, cleanup] = this.createAbortSignal(options);
|
|
1047
1248
|
if (options?.autoAbort?.onControllerCreated && autoAbortController) options.autoAbort.onControllerCreated(autoAbortController);
|
|
1048
1249
|
if (effectiveSignal?.aborted) {
|
|
@@ -1931,13 +2132,154 @@ function isReactActionError(error) {
|
|
|
1931
2132
|
return error instanceof ReactActionError;
|
|
1932
2133
|
}
|
|
1933
2134
|
|
|
2135
|
+
//#endregion
|
|
2136
|
+
//#region src/action-schema.ts
|
|
2137
|
+
/**
|
|
2138
|
+
* Zod ์คํค๋ง๋ฅผ JSON Schema๋ก ๋ณํ (Zod 4 ๋ค์ดํฐ๋ธ API)
|
|
2139
|
+
*
|
|
2140
|
+
* @param schema - Zod ์คํค๋ง
|
|
2141
|
+
* @returns JSON Schema (draft-7)
|
|
2142
|
+
*/
|
|
2143
|
+
function zodToJsonSchema(schema, zodModule) {
|
|
2144
|
+
return zodModule.toJSONSchema(schema, {
|
|
2145
|
+
target: "draft-7",
|
|
2146
|
+
metadata: zodModule.globalRegistry
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
/**
|
|
2150
|
+
* Zod ์คํค๋ง ๊ธฐ๋ฐ Action ์ ์
|
|
2151
|
+
*
|
|
2152
|
+
* defineTool ํจํด์ ๊ธฐ๋ฐ์ผ๋ก context-action์ ๋ง๊ฒ ๊ตฌํ:
|
|
2153
|
+
* - Single Source of Truth: Zod ์คํค๋ง๋ก ํ์
+ ๊ฒ์ฆ + ๋ฉํ๋ฐ์ดํฐ ํตํฉ
|
|
2154
|
+
* - ๋ฐํ์ ๊ฒ์ฆ: validate(), safeParse()
|
|
2155
|
+
* - Tool Chain ํธํ: toMCP(), toOpenAI(), toAnthropic()
|
|
2156
|
+
*
|
|
2157
|
+
* @param options - Action ์ ์ ์ต์
|
|
2158
|
+
* @param zodModule - Zod ๋ชจ๋ (peerDependency๋ก ์ฃผ์
)
|
|
2159
|
+
* @returns UnifiedAction ์ธ์คํด์ค
|
|
2160
|
+
*
|
|
2161
|
+
* @example
|
|
2162
|
+
* ```typescript
|
|
2163
|
+
* import { z } from 'zod';
|
|
2164
|
+
* import { defineAction } from '@context-action/core';
|
|
2165
|
+
*
|
|
2166
|
+
* const updateUserAction = defineAction({
|
|
2167
|
+
* name: 'updateUser',
|
|
2168
|
+
* description: 'Update user profile',
|
|
2169
|
+
* parameters: z.object({
|
|
2170
|
+
* id: z.string().min(1).meta({ description: 'User ID' }),
|
|
2171
|
+
* name: z.string().min(2).max(50).meta({ description: 'User name' }),
|
|
2172
|
+
* email: z.string().email().optional(),
|
|
2173
|
+
* }),
|
|
2174
|
+
* }, z);
|
|
2175
|
+
*
|
|
2176
|
+
* // ๊ฒ์ฆ
|
|
2177
|
+
* const validated = updateUserAction.validate({ id: '123', name: 'John' });
|
|
2178
|
+
*
|
|
2179
|
+
* // Tool chain ๋ณํ
|
|
2180
|
+
* const mcpTool = updateUserAction.toMCP();
|
|
2181
|
+
* ```
|
|
2182
|
+
*/
|
|
2183
|
+
function defineAction(options, zodModule) {
|
|
2184
|
+
const { name, description, parameters } = options;
|
|
2185
|
+
const jsonSchema = zodToJsonSchema(parameters, zodModule);
|
|
2186
|
+
return {
|
|
2187
|
+
name,
|
|
2188
|
+
description,
|
|
2189
|
+
zodSchema: parameters,
|
|
2190
|
+
jsonSchema,
|
|
2191
|
+
validate: (payload) => {
|
|
2192
|
+
return parameters.parse(payload);
|
|
2193
|
+
},
|
|
2194
|
+
safeParse: (payload) => {
|
|
2195
|
+
return parameters.safeParse(payload);
|
|
2196
|
+
},
|
|
2197
|
+
toJSONSchema: () => jsonSchema,
|
|
2198
|
+
toMCP: () => ({
|
|
2199
|
+
name,
|
|
2200
|
+
description,
|
|
2201
|
+
inputSchema: jsonSchema
|
|
2202
|
+
}),
|
|
2203
|
+
toOpenAI: () => ({
|
|
2204
|
+
type: "function",
|
|
2205
|
+
function: {
|
|
2206
|
+
name,
|
|
2207
|
+
description,
|
|
2208
|
+
parameters: {
|
|
2209
|
+
type: "object",
|
|
2210
|
+
properties: jsonSchema.properties ?? {},
|
|
2211
|
+
required: jsonSchema.required
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}),
|
|
2215
|
+
toAnthropic: () => ({
|
|
2216
|
+
name,
|
|
2217
|
+
description,
|
|
2218
|
+
input_schema: jsonSchema
|
|
2219
|
+
})
|
|
2220
|
+
};
|
|
2221
|
+
}
|
|
2222
|
+
/**
|
|
2223
|
+
* ๋ค์ค Action ์คํค๋ง ์์ฑ
|
|
2224
|
+
*
|
|
2225
|
+
* ์ฌ๋ฌ defineAction์ ๋ฌถ์ด์ ActionSchemaMap ์์ฑ
|
|
2226
|
+
*
|
|
2227
|
+
* @param actions - UnifiedAction ๋งต
|
|
2228
|
+
* @returns ActionSchemaMap
|
|
2229
|
+
*
|
|
2230
|
+
* @example
|
|
2231
|
+
* ```typescript
|
|
2232
|
+
* const userActionSchema = createActionSchema({
|
|
2233
|
+
* updateUser: defineAction({ ... }, z),
|
|
2234
|
+
* deleteUser: defineAction({ ... }, z),
|
|
2235
|
+
* });
|
|
2236
|
+
*
|
|
2237
|
+
* type UserActions = InferActionPayloadMap<typeof userActionSchema>;
|
|
2238
|
+
* ```
|
|
2239
|
+
*/
|
|
2240
|
+
function createActionSchema(actions) {
|
|
2241
|
+
return actions;
|
|
2242
|
+
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Zod ๋ชจ๋์ ๋ฐ์ธ๋ฉํ defineAction ํฉํ ๋ฆฌ ์์ฑ
|
|
2245
|
+
*
|
|
2246
|
+
* ๋งค๋ฒ z ๋ชจ๋์ ์ ๋ฌํ์ง ์์๋ ๋๋๋ก ํฉํ ๋ฆฌ ํจํด ์ ๊ณต
|
|
2247
|
+
*
|
|
2248
|
+
* @param zodModule - Zod ๋ชจ๋
|
|
2249
|
+
* @returns defineAction ํจ์ (z ๋ฐ์ธ๋ฉ๋จ)
|
|
2250
|
+
*
|
|
2251
|
+
* @example
|
|
2252
|
+
* ```typescript
|
|
2253
|
+
* import { z } from 'zod';
|
|
2254
|
+
* import { createActionFactory } from '@context-action/core';
|
|
2255
|
+
*
|
|
2256
|
+
* const defineAction = createActionFactory(z);
|
|
2257
|
+
*
|
|
2258
|
+
* const updateUser = defineAction({
|
|
2259
|
+
* name: 'updateUser',
|
|
2260
|
+
* parameters: z.object({ id: z.string() }),
|
|
2261
|
+
* });
|
|
2262
|
+
* ```
|
|
2263
|
+
*/
|
|
2264
|
+
function createActionFactory(zodModule) {
|
|
2265
|
+
return (options) => {
|
|
2266
|
+
return defineAction(options, zodModule);
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
|
|
1934
2270
|
//#endregion
|
|
1935
2271
|
exports.ActionGuard = ActionGuard;
|
|
1936
2272
|
exports.ActionRegister = ActionRegister;
|
|
2273
|
+
exports.ActionValidationError = ActionValidationError;
|
|
1937
2274
|
exports.ReactActionError = ReactActionError;
|
|
1938
2275
|
exports.ReactDevUtils = ReactDevUtils;
|
|
2276
|
+
exports.createActionFactory = createActionFactory;
|
|
1939
2277
|
exports.createActionHandler = createActionHandler;
|
|
2278
|
+
exports.createActionSchema = createActionSchema;
|
|
2279
|
+
exports.defineAction = defineAction;
|
|
1940
2280
|
exports.executeParallel = executeParallel;
|
|
1941
2281
|
exports.executeRace = executeRace;
|
|
1942
2282
|
exports.executeSequential = executeSequential;
|
|
1943
|
-
exports.
|
|
2283
|
+
exports.isActionValidationError = isActionValidationError;
|
|
2284
|
+
exports.isReactActionError = isReactActionError;
|
|
2285
|
+
exports.zodToJsonSchema = zodToJsonSchema;
|