@context-action/core 0.1.0 → 0.2.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/dist/index.js CHANGED
@@ -235,7 +235,7 @@ var require_toPropertyKey = /* @__PURE__ */ __commonJS({ "../../node_modules/.pn
235
235
  //#region ../../node_modules/.pnpm/@oxc-project+runtime@0.81.0/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js
236
236
  var require_defineProperty = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.81.0/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js": ((exports, module) => {
237
237
  var toPropertyKey = require_toPropertyKey();
238
- function _defineProperty$2(e, r, t) {
238
+ function _defineProperty$3(e, r, t) {
239
239
  return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
240
240
  value: t,
241
241
  enumerable: !0,
@@ -243,12 +243,12 @@ var require_defineProperty = /* @__PURE__ */ __commonJS({ "../../node_modules/.p
243
243
  writable: !0
244
244
  }) : e[r] = t, e;
245
245
  }
246
- module.exports = _defineProperty$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
246
+ module.exports = _defineProperty$3, module.exports.__esModule = true, module.exports["default"] = module.exports;
247
247
  }) });
248
248
 
249
249
  //#endregion
250
250
  //#region src/action-guard.ts
251
- var import_defineProperty$1 = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
251
+ var import_defineProperty$2 = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
252
252
  /**
253
253
  * Action Guard system for managing action execution timing
254
254
  * @implements action-guard
@@ -279,7 +279,7 @@ var import_defineProperty$1 = /* @__PURE__ */ __toESM(require_defineProperty(),
279
279
  */
280
280
  var ActionGuard = class {
281
281
  constructor() {
282
- (0, import_defineProperty$1.default)(this, "guards", /* @__PURE__ */ new Map());
282
+ (0, import_defineProperty$2.default)(this, "guards", /* @__PURE__ */ new Map());
283
283
  }
284
284
  /**
285
285
  * Check if action should be debounced
@@ -299,15 +299,21 @@ var ActionGuard = class {
299
299
  this.guards.set(actionKey, state);
300
300
  }
301
301
  /** Clear any existing debounce timer to restart the delay period */
302
- /** This implements the "debounce" behavior where rapid calls reset the timer */
303
- if (state.debounceTimer) clearTimeout(state.debounceTimer);
304
- /** Create new debounce promise that resolves after the delay period */
305
- /** The promise will only resolve if no new debounce requests arrive */
302
+ if (state.debounceTimer) {
303
+ clearTimeout(state.debounceTimer);
304
+ if (state.debounceResolve) {
305
+ state.debounceResolve(false);
306
+ state.debounceResolve = void 0;
307
+ }
308
+ }
309
+ /** Create new debounce promise */
306
310
  return new Promise((resolve) => {
311
+ state.debounceResolve = resolve;
307
312
  state.debounceTimer = setTimeout(() => {
308
- /** Clean up timer reference to prevent memory leaks */
313
+ /** Clean up timer and resolver references */
309
314
  state.debounceTimer = void 0;
310
- /** Update last execution timestamp for throttling calculations */
315
+ state.debounceResolve = void 0;
316
+ /** Update last execution timestamp */
311
317
  state.lastExecuted = Date.now();
312
318
  resolve(true);
313
319
  }, debounceMs);
@@ -363,7 +369,10 @@ var ActionGuard = class {
363
369
  const state = this.guards.get(actionKey);
364
370
  if (state) {
365
371
  /** Clear debounce timer if active to prevent memory leaks */
366
- if (state.debounceTimer) clearTimeout(state.debounceTimer);
372
+ if (state.debounceTimer) {
373
+ clearTimeout(state.debounceTimer);
374
+ if (state.debounceResolve) state.debounceResolve(false);
375
+ }
367
376
  /** Clear throttle timer if active to prevent memory leaks */
368
377
  if (state.throttleTimer) clearTimeout(state.throttleTimer);
369
378
  /** Remove guard state from memory */
@@ -378,7 +387,10 @@ var ActionGuard = class {
378
387
  /** This prevents memory leaks when clearing the entire guard system */
379
388
  for (const [, state] of this.guards) {
380
389
  /** Clear any active debounce timers */
381
- if (state.debounceTimer) clearTimeout(state.debounceTimer);
390
+ if (state.debounceTimer) {
391
+ clearTimeout(state.debounceTimer);
392
+ if (state.debounceResolve) state.debounceResolve(false);
393
+ }
382
394
  /** Clear any active throttle timers */
383
395
  if (state.throttleTimer) clearTimeout(state.throttleTimer);
384
396
  }
@@ -400,6 +412,112 @@ var ActionGuard = class {
400
412
  }
401
413
  };
402
414
 
415
+ //#endregion
416
+ //#region src/concurrency/OperationQueue.ts
417
+ var import_defineProperty$1 = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
418
+ /**
419
+ * 작업 큐 관리자
420
+ *
421
+ * 핵심 기능:
422
+ * 1. 작업 직렬화 - 모든 작업을 순서대로 실행
423
+ * 2. 우선순위 지원 - 중요한 작업 우선 처리
424
+ * 3. 에러 처리 - 개별 작업 실패가 전체에 영향 주지 않음
425
+ * 4. 메모리 관리 - 완료된 작업 자동 정리
426
+ */
427
+ var OperationQueue = class {
428
+ constructor(name = "OperationQueue") {
429
+ this.name = name;
430
+ (0, import_defineProperty$1.default)(this, "queue", []);
431
+ (0, import_defineProperty$1.default)(this, "isProcessing", false);
432
+ (0, import_defineProperty$1.default)(this, "operationCounter", 0);
433
+ }
434
+ /**
435
+ * 작업을 큐에 추가하고 실행 결과를 반환
436
+ *
437
+ * @param operation 실행할 작업
438
+ * @param priority 우선순위 (높을수록 먼저 실행)
439
+ * @returns Promise로 래핑된 작업 결과
440
+ */
441
+ enqueue(operation, priority = 0) {
442
+ return new Promise((resolve, reject) => {
443
+ const queuedOperation = {
444
+ id: `${this.name}-${++this.operationCounter}`,
445
+ operation,
446
+ resolve,
447
+ reject,
448
+ priority,
449
+ timestamp: Date.now()
450
+ };
451
+ let insertIndex = this.queue.length;
452
+ for (let i = 0; i < this.queue.length; i++) if ((this.queue[i].priority || 0) < priority) {
453
+ insertIndex = i;
454
+ break;
455
+ }
456
+ this.queue.splice(insertIndex, 0, queuedOperation);
457
+ this.processQueue();
458
+ });
459
+ }
460
+ /**
461
+ * 큐 처리 메인 로직
462
+ *
463
+ * 한 번에 하나씩 순서대로 작업을 실행하여 동시성 문제 방지
464
+ */
465
+ async processQueue() {
466
+ if (this.isProcessing || this.queue.length === 0) return;
467
+ this.isProcessing = true;
468
+ try {
469
+ while (this.queue.length > 0) {
470
+ const operation = this.queue.shift();
471
+ try {
472
+ const result = await Promise.resolve(operation.operation());
473
+ operation.resolve(result);
474
+ } catch (error) {
475
+ operation.reject(error);
476
+ }
477
+ }
478
+ } finally {
479
+ this.isProcessing = false;
480
+ }
481
+ }
482
+ /**
483
+ * 현재 큐 상태 조회 (디버깅용)
484
+ */
485
+ getQueueInfo() {
486
+ return {
487
+ name: this.name,
488
+ queueLength: this.queue.length,
489
+ isProcessing: this.isProcessing,
490
+ operations: this.queue.map((op) => ({
491
+ id: op.id,
492
+ priority: op.priority,
493
+ timestamp: op.timestamp
494
+ }))
495
+ };
496
+ }
497
+ /**
498
+ * 큐 비우기 (테스트용)
499
+ */
500
+ clear() {
501
+ this.queue.forEach((operation) => {
502
+ operation.reject(/* @__PURE__ */ new Error("Queue cleared"));
503
+ });
504
+ this.queue = [];
505
+ this.isProcessing = false;
506
+ }
507
+ /**
508
+ * 큐 크기 조회
509
+ */
510
+ get size() {
511
+ return this.queue.length;
512
+ }
513
+ /**
514
+ * 처리 중 여부 조회
515
+ */
516
+ get processing() {
517
+ return this.isProcessing;
518
+ }
519
+ };
520
+
403
521
  //#endregion
404
522
  //#region src/ActionRegister.ts
405
523
  var import_defineProperty = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
@@ -425,7 +543,7 @@ var import_defineProperty = /* @__PURE__ */ __toESM(require_defineProperty(), 1)
425
543
  * // 핸들러 등록
426
544
  * register.register('updateUser', ({ id, name }, controller) => {
427
545
  * userStore.setValue({ id, name });
428
- * controller.next();
546
+ * // 핸들러가 자동으로 다음 핸들러로 진행
429
547
  * }, { priority: 10 });
430
548
  *
431
549
  * // 액션 디스패치
@@ -442,18 +560,93 @@ var ActionRegister = class {
442
560
  (0, import_defineProperty.default)(this, "name", void 0);
443
561
  (0, import_defineProperty.default)(this, "registryConfig", void 0);
444
562
  (0, import_defineProperty.default)(this, "executionStats", /* @__PURE__ */ new Map());
563
+ (0, import_defineProperty.default)(this, "registrationQueue", void 0);
564
+ (0, import_defineProperty.default)(this, "dispatchQueue", void 0);
445
565
  this.name = config.name || "ActionRegister";
446
566
  this.registryConfig = config.registry;
447
567
  this.actionGuard = new ActionGuard();
568
+ this.registrationQueue = new OperationQueue(`${this.name}-Registration`);
569
+ this.dispatchQueue = new OperationQueue(`${this.name}-Dispatch`);
448
570
  if (this.registryConfig?.defaultExecutionMode) this.executionMode = this.registryConfig.defaultExecutionMode;
449
571
  if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 ActionRegister created: ${this.name}`, {
450
572
  defaultExecutionMode: this.executionMode,
451
573
  maxHandlers: this.registryConfig.maxHandlers,
452
- autoCleanup: this.registryConfig.autoCleanup ?? true
574
+ autoCleanup: this.registryConfig.autoCleanup ?? true,
575
+ concurrencyProtection: true
453
576
  });
454
577
  }
455
578
  register(action, handler, config = {}) {
456
579
  const handlerId = config.id || `handler_${++this.handlerCounter}_${Math.random().toString(36).substr(2, 5)}`;
580
+ const unregisterFn = this._performRegistrationSync(action, handler, config, handlerId);
581
+ return unregisterFn;
582
+ }
583
+ /**
584
+ * 🆕 동기적 등록 수행 (개선된 버전)
585
+ */
586
+ _performRegistrationSync(action, handler, config, handlerId) {
587
+ const registration = {
588
+ handler,
589
+ config: {
590
+ priority: config.priority ?? 0,
591
+ id: handlerId,
592
+ blocking: config.blocking ?? false,
593
+ once: config.once ?? false,
594
+ condition: config.condition || (() => true),
595
+ debounce: config.debounce ?? void 0,
596
+ throttle: config.throttle ?? void 0,
597
+ validation: config.validation ?? void 0,
598
+ middleware: config.middleware ?? false,
599
+ tags: config.tags ?? [],
600
+ category: config.category ?? void 0,
601
+ description: config.description ?? void 0,
602
+ version: config.version ?? void 0,
603
+ returnType: config.returnType ?? "value",
604
+ timeout: config.timeout ?? void 0,
605
+ retries: config.retries ?? 0,
606
+ dependencies: config.dependencies ?? [],
607
+ conflicts: config.conflicts ?? [],
608
+ environment: config.environment ?? void 0,
609
+ feature: config.feature ?? void 0,
610
+ metrics: config.metrics ?? {
611
+ collectTiming: false,
612
+ collectErrors: false,
613
+ customMetrics: {}
614
+ },
615
+ metadata: config.metadata ?? {}
616
+ },
617
+ id: handlerId
618
+ };
619
+ if (!this.pipelines.has(action)) this.pipelines.set(action, []);
620
+ const pipeline = this.pipelines.get(action);
621
+ const existingIndex = pipeline.findIndex((reg) => reg.id === handlerId);
622
+ if (existingIndex !== -1) return () => {};
623
+ if (this.registryConfig?.maxHandlers && pipeline.length >= this.registryConfig.maxHandlers) throw new Error(`Maximum number of handlers (${this.registryConfig.maxHandlers}) reached for action '${String(action)}' in registry '${this.name}'`);
624
+ pipeline.push(registration);
625
+ pipeline.sort((a, b) => b.config.priority - a.config.priority);
626
+ if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Handler registered: ${String(action)}`, {
627
+ handlerId,
628
+ priority: config.priority,
629
+ tags: config.tags,
630
+ category: config.category,
631
+ totalHandlers: pipeline.length,
632
+ registry: this.name
633
+ });
634
+ return () => {
635
+ const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
636
+ if (index !== -1) {
637
+ pipeline.splice(index, 1);
638
+ if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Handler unregistered: ${String(action)}`, {
639
+ handlerId,
640
+ remainingHandlers: pipeline.length,
641
+ registry: this.name
642
+ });
643
+ }
644
+ };
645
+ }
646
+ /**
647
+ * 🆕 실제 등록 작업 수행 (큐에서 호출됨)
648
+ */
649
+ _performRegistration(action, handler, config, handlerId) {
457
650
  const registration = {
458
651
  handler,
459
652
  config: {
@@ -514,6 +707,14 @@ var ActionRegister = class {
514
707
  };
515
708
  }
516
709
  async dispatch(action, payload, options) {
710
+ return this.dispatchQueue.enqueue(async () => {
711
+ return this._performDispatch(action, payload, options);
712
+ });
713
+ }
714
+ /**
715
+ * 🆕 실제 디스패치 작업 수행 (큐에서 호출됨)
716
+ */
717
+ async _performDispatch(action, payload, options) {
517
718
  let autoAbortController;
518
719
  let effectiveSignal = options?.signal;
519
720
  if (options?.autoAbort?.enabled) {
@@ -811,7 +1012,6 @@ var ActionRegister = class {
811
1012
  async executePipeline(context, autoAbortController, autoAbortOptions) {
812
1013
  const createController = (_registration, _index) => {
813
1014
  return {
814
- next: () => {},
815
1015
  abort: (reason) => {
816
1016
  context.aborted = true;
817
1017
  context.abortReason = reason;