mega-framework 0.1.10 → 0.1.13

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 (91) hide show
  1. package/README.md +14 -4
  2. package/package.json +23 -21
  3. package/sample/crud/.env +10 -2
  4. package/sample/crud/.env.example +8 -0
  5. package/sample/crud/apps/main/controllers/bus-controller.js +122 -0
  6. package/sample/crud/apps/main/controllers/lock-controller.js +117 -0
  7. package/sample/crud/apps/main/locales/server/en.json +31 -1
  8. package/sample/crud/apps/main/locales/server/ko.json +31 -1
  9. package/sample/crud/apps/main/public/js/bus-demo.js +131 -0
  10. package/sample/crud/apps/main/public/js/lock-demo.js +122 -0
  11. package/sample/crud/apps/main/routes/bus.js +43 -0
  12. package/sample/crud/apps/main/routes/lock.js +35 -0
  13. package/sample/crud/apps/main/services/jobs-demo-service.js +21 -14
  14. package/sample/crud/apps/main/views/bus/index.ejs +80 -0
  15. package/sample/crud/apps/main/views/layouts/main.ejs +2 -0
  16. package/sample/crud/apps/main/views/lock/index.ejs +99 -0
  17. package/sample/crud/docs/guide/03-service-model-db.md +110 -6
  18. package/sample/crud/docs/guide/05-scheduler-job-worker.md +3 -1
  19. package/sample/crud/docs/guide/09-distributed-lock-and-bus.md +224 -0
  20. package/sample/crud/docs/guide/10-multi-app.md +74 -0
  21. package/sample/crud/mega.config.js +32 -0
  22. package/sample/crud/package.json +3 -2
  23. package/sample/multi/.env +16 -0
  24. package/sample/multi/.env.example +17 -0
  25. package/sample/multi/README.md +54 -0
  26. package/sample/multi/apps/admin/app.config.js +24 -0
  27. package/sample/multi/apps/admin/controllers/admin-controller.js +42 -0
  28. package/sample/multi/apps/admin/public/js/admin.js +31 -0
  29. package/sample/multi/apps/admin/routes/pages.js +11 -0
  30. package/sample/multi/apps/admin/views/index.ejs +33 -0
  31. package/sample/multi/apps/web/app.config.js +30 -0
  32. package/sample/multi/apps/web/controllers/web-controller.js +45 -0
  33. package/sample/multi/apps/web/public/js/web.js +24 -0
  34. package/sample/multi/apps/web/routes/pages.js +13 -0
  35. package/sample/multi/apps/web/views/index.ejs +51 -0
  36. package/sample/multi/mega.config.js +42 -0
  37. package/sample/multi/package.json +20 -0
  38. package/sample/simple/package.json +2 -2
  39. package/src/adapters/nats-adapter.js +39 -44
  40. package/src/adapters/nats-codec.js +38 -0
  41. package/src/cli/commands/scaffold.js +1 -0
  42. package/src/cli/index.js +9 -1
  43. package/src/core/app-registry.js +69 -0
  44. package/src/core/boot.js +99 -0
  45. package/src/core/bus/cluster-bus.js +190 -0
  46. package/src/core/bus/contract.js +123 -0
  47. package/src/core/bus/index.js +285 -0
  48. package/src/core/bus/memory-bus.js +103 -0
  49. package/src/core/bus/nats-bus.js +203 -0
  50. package/src/core/config-validator.js +118 -1
  51. package/src/core/ctx-builder.js +14 -1
  52. package/src/core/index.js +2 -0
  53. package/src/core/lock/cluster-lock.js +174 -0
  54. package/src/core/lock/contract.js +123 -0
  55. package/src/core/lock/fifo-waitlist.js +93 -0
  56. package/src/core/lock/index.js +292 -0
  57. package/src/core/lock/memory-lock.js +162 -0
  58. package/src/core/lock/redis-lock.js +276 -0
  59. package/src/core/mega-app.js +29 -0
  60. package/src/core/migration/generate.js +1 -1
  61. package/src/core/migration/journal.js +1 -1
  62. package/src/core/scope-registry.js +9 -0
  63. package/src/eslint-plugin/no-direct-model-import.js +2 -2
  64. package/src/index.js +2 -0
  65. package/src/lib/mega-job-queue.js +71 -47
  66. package/templates/model/code-mongo.tpl +1 -9
  67. package/templates/model/code.tpl +1 -10
  68. package/templates/model/test-mongo.tpl +1 -23
  69. package/templates/model/test.tpl +0 -17
  70. package/types/adapters/mega-adapter.d.ts +1 -1
  71. package/types/adapters/nats-adapter.d.ts +4 -4
  72. package/types/adapters/nats-codec.d.ts +13 -0
  73. package/types/adapters/redlock-adapter.d.ts +1 -1
  74. package/types/core/app-registry.d.ts +22 -0
  75. package/types/core/bus/cluster-bus.d.ts +45 -0
  76. package/types/core/bus/contract.d.ts +164 -0
  77. package/types/core/bus/index.d.ts +100 -0
  78. package/types/core/bus/memory-bus.d.ts +45 -0
  79. package/types/core/bus/nats-bus.d.ts +41 -0
  80. package/types/core/index.d.ts +1 -0
  81. package/types/core/lock/cluster-lock.d.ts +44 -0
  82. package/types/core/lock/contract.d.ts +181 -0
  83. package/types/core/lock/fifo-waitlist.d.ts +38 -0
  84. package/types/core/lock/index.d.ts +96 -0
  85. package/types/core/lock/memory-lock.d.ts +58 -0
  86. package/types/core/lock/redis-lock.d.ts +43 -0
  87. package/types/core/mega-app.d.ts +10 -0
  88. package/types/core/scope-registry.d.ts +6 -0
  89. package/types/index.d.ts +1 -1
  90. package/types/lib/mega-job-queue.d.ts +27 -4
  91. package/sample/simple/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
@@ -0,0 +1,164 @@
1
+ /**
2
+ * @typedef {Object} BusEnvelope - wire/IPC 로 오가는 메시지 봉투(payload 와 meta 분리 — meta 는 traceId 등).
3
+ * @property {any} payload - 사용자 데이터.
4
+ * @property {Record<string, any>} [meta] - 부가 메타(전파용). 없으면 빈 객체로 취급.
5
+ */
6
+ /**
7
+ * @typedef {Object} EmitOpts - emit 옵션.
8
+ * @property {Record<string, any>} [meta] - 이 메시지의 meta.
9
+ * @property {boolean} [persist] - true 면 영속 전달(JetStream — nats-bus 만; 그 외 driver 는 경고 후 비영속).
10
+ */
11
+ /**
12
+ * @typedef {Object} OnOpts - on(구독) 옵션.
13
+ * @property {boolean} [persist] - true 면 영속 구독(JetStream consumer — nats-bus 만).
14
+ * @property {boolean} [ordered] - true 면 같은 subject 메시지를 순서대로 1건씩 직렬 처리(핸들러 await 직렬화).
15
+ */
16
+ /**
17
+ * @typedef {(payload: any, meta: Record<string, any>, subject: string) => any} BusHandler - 사용자 핸들러.
18
+ * 3번째 인자 `subject` 는 메시지가 도착한 **구체** subject(prefix 제거됨) — wildcard 구독에서 어느 subject 가
19
+ * 매칭됐는지 안다. `meta.subject` 에도 같은 값이 담긴다(정본 위치). request 대상이면 **반환값이 reply**.
20
+ */
21
+ /**
22
+ * @typedef {Object} Subscription - on/subscribe 가 돌려주는 구독 핸들.
23
+ * @property {() => Promise<void>} unsubscribe - 구독 해제.
24
+ */
25
+ /**
26
+ * @typedef {(envelope: BusEnvelope) => void} ReplyFn - driver 가 핸들러에 주는 응답 함수(request 일 때만).
27
+ *
28
+ * @typedef {Object} BusDriver - driver 가 구현하는 저수준 인터페이스(manager 가 호출).
29
+ * @property {string} name - 'nats' | 'cluster' | 'memory'.
30
+ * @property {(subject: string, envelope: BusEnvelope, opts: { persist?: boolean }) => Promise<void>} publish -
31
+ * fire-and-forget 발행(구독자 전원 fan-out).
32
+ * @property {(subject: string, handler: (envelope: BusEnvelope, reply?: ReplyFn, subject?: string) => any, opts: { persist?: boolean, ordered?: boolean }) => Promise<Subscription>} subscribe -
33
+ * 구독(subject 는 wildcard 가능). `reply` 는 request 로 들어온 메시지일 때만 제공. 핸들러의 3번째 인자는
34
+ * 매칭된 **구체** subject(driver 가 채워 manager 로 넘김 — wildcard 어디로 왔는지 안다).
35
+ * @property {(subject: string, envelope: BusEnvelope, opts: { timeout: number }) => Promise<BusEnvelope>} request -
36
+ * req/reply — 첫 응답 envelope 반환. 응답자 없으면 `bus.no_responders`, 시한 초과면 `bus.request_timeout` throw.
37
+ * @property {() => Promise<{ driver: string, subscriptions: number }>} stats - 현재 구독 수.
38
+ * @property {() => Promise<void>} close - 구독·타이머·IPC·연결참조 정리.
39
+ */
40
+ /**
41
+ * subject 정규화 — prefix 를 앞에 붙인다(네임스페이스 격리). prefix 가 빈 값이면 그대로.
42
+ * @param {string} subject @param {string} [prefix] - 예: 'app.'.
43
+ * @returns {string}
44
+ */
45
+ export function normalizeSubject(subject: string, prefix?: string): string;
46
+ /**
47
+ * subject 검증 — 비어있지 않고 공백/제어문자 없는 문자열. publish subject 는 wildcard 금지(구체적이어야 함).
48
+ * @param {string} subject @param {{ allowWildcard?: boolean }} [opts] - true 면 `*`/`>` 허용(구독용).
49
+ * @returns {void}
50
+ * @throws {MegaValidationError} `bus.invalid_subject`
51
+ */
52
+ export function assertSubject(subject: string, { allowWildcard }?: {
53
+ allowWildcard?: boolean;
54
+ }): void;
55
+ /**
56
+ * NATS 식 wildcard 매칭 — cluster/memory-bus 의 자체 패턴 매칭(nats-bus 는 NATS native 사용).
57
+ * `*` = 한 토큰, `>` = 한 토큰 이상(꼬리). 토큰 수가 정확히 맞아야 매칭(`>` 제외).
58
+ * @param {string} pattern - 구독 패턴(wildcard 가능). @param {string} subject - 구체 subject.
59
+ * @returns {boolean}
60
+ */
61
+ export function matchSubject(pattern: string, subject: string): boolean;
62
+ /** request/reply 응답 대기 디폴트(ms). config `bus.requestTimeoutMs` 로 조정. */
63
+ export const DEFAULT_REQUEST_TIMEOUT_MS: 5000;
64
+ /**
65
+ * - wire/IPC 로 오가는 메시지 봉투(payload 와 meta 분리 — meta 는 traceId 등).
66
+ */
67
+ export type BusEnvelope = {
68
+ /**
69
+ * - 사용자 데이터.
70
+ */
71
+ payload: any;
72
+ /**
73
+ * - 부가 메타(전파용). 없으면 빈 객체로 취급.
74
+ */
75
+ meta?: Record<string, any>;
76
+ };
77
+ /**
78
+ * - emit 옵션.
79
+ */
80
+ export type EmitOpts = {
81
+ /**
82
+ * - 이 메시지의 meta.
83
+ */
84
+ meta?: Record<string, any>;
85
+ /**
86
+ * - true 면 영속 전달(JetStream — nats-bus 만; 그 외 driver 는 경고 후 비영속).
87
+ */
88
+ persist?: boolean;
89
+ };
90
+ /**
91
+ * - on(구독) 옵션.
92
+ */
93
+ export type OnOpts = {
94
+ /**
95
+ * - true 면 영속 구독(JetStream consumer — nats-bus 만).
96
+ */
97
+ persist?: boolean;
98
+ /**
99
+ * - true 면 같은 subject 메시지를 순서대로 1건씩 직렬 처리(핸들러 await 직렬화).
100
+ */
101
+ ordered?: boolean;
102
+ };
103
+ /**
104
+ * - 사용자 핸들러.
105
+ * 3번째 인자 `subject` 는 메시지가 도착한 **구체** subject(prefix 제거됨) — wildcard 구독에서 어느 subject 가
106
+ * 매칭됐는지 안다. `meta.subject` 에도 같은 값이 담긴다(정본 위치). request 대상이면 **반환값이 reply**.
107
+ */
108
+ export type BusHandler = (payload: any, meta: Record<string, any>, subject: string) => any;
109
+ /**
110
+ * - on/subscribe 가 돌려주는 구독 핸들.
111
+ */
112
+ export type Subscription = {
113
+ /**
114
+ * - 구독 해제.
115
+ */
116
+ unsubscribe: () => Promise<void>;
117
+ };
118
+ /**
119
+ * - driver 가 핸들러에 주는 응답 함수(request 일 때만).
120
+ */
121
+ export type ReplyFn = (envelope: BusEnvelope) => void;
122
+ /**
123
+ * - driver 가 구현하는 저수준 인터페이스(manager 가 호출).
124
+ */
125
+ export type BusDriver = {
126
+ /**
127
+ * - 'nats' | 'cluster' | 'memory'.
128
+ */
129
+ name: string;
130
+ /**
131
+ * -
132
+ * fire-and-forget 발행(구독자 전원 fan-out).
133
+ */
134
+ publish: (subject: string, envelope: BusEnvelope, opts: {
135
+ persist?: boolean;
136
+ }) => Promise<void>;
137
+ /**
138
+ * -
139
+ * 구독(subject 는 wildcard 가능). `reply` 는 request 로 들어온 메시지일 때만 제공. 핸들러의 3번째 인자는
140
+ * 매칭된 **구체** subject(driver 가 채워 manager 로 넘김 — wildcard 어디로 왔는지 안다).
141
+ */
142
+ subscribe: (subject: string, handler: (envelope: BusEnvelope, reply?: ReplyFn, subject?: string) => any, opts: {
143
+ persist?: boolean;
144
+ ordered?: boolean;
145
+ }) => Promise<Subscription>;
146
+ /**
147
+ * -
148
+ * req/reply — 첫 응답 envelope 반환. 응답자 없으면 `bus.no_responders`, 시한 초과면 `bus.request_timeout` throw.
149
+ */
150
+ request: (subject: string, envelope: BusEnvelope, opts: {
151
+ timeout: number;
152
+ }) => Promise<BusEnvelope>;
153
+ /**
154
+ * - 현재 구독 수.
155
+ */
156
+ stats: () => Promise<{
157
+ driver: string;
158
+ subscriptions: number;
159
+ }>;
160
+ /**
161
+ * - 구독·타이머·IPC·연결참조 정리.
162
+ */
163
+ close: () => Promise<void>;
164
+ };
@@ -0,0 +1,100 @@
1
+ /** 활성 버스 manager 설정(boot bus 스테이지). null 로 해제(셧다운/테스트). @param {BusManager | null} m @returns {void} */
2
+ export function setBusManager(m: BusManager | null): void;
3
+ /** 현재 활성 버스 manager(없으면 null). @returns {BusManager | null} */
4
+ export function getBusManager(): BusManager | null;
5
+ /**
6
+ * 메시지 버스 manager 를 만든다 — driver 자동 폴백 포함. boot 가 1회 호출해 `ctx.bus` 에 부착한다.
7
+ * @param {{ driver?: string, nats?: string, prefix?: string, defaultPersist?: boolean, requestTimeoutMs?: number }} [busConfig]
8
+ * @param {{ logger?: any, nc?: any, isClusterWorker?: boolean }} [deps] - `nc` boot 가 bus 어댑터에서 빌린 NatsConnection(없으면 null).
9
+ * @returns {BusManager}
10
+ */
11
+ export function createBusManager(busConfig?: {
12
+ driver?: string;
13
+ nats?: string;
14
+ prefix?: string;
15
+ defaultPersist?: boolean;
16
+ requestTimeoutMs?: number;
17
+ }, deps?: {
18
+ logger?: any;
19
+ nc?: any;
20
+ isClusterWorker?: boolean;
21
+ }): BusManager;
22
+ /**
23
+ * `ctx.bus` 콜러블에 사용자 API 메서드를 얹는다 — `ctx.bus(alias)`(기존)와 `ctx.bus.emit(...)`(신규) 공존.
24
+ * @param {Function & Record<string, any>} busAccessor - `(alias) => MegaBusAdapter` 콜러블(ctx-builder 산출).
25
+ * @param {BusManager} manager @returns {Function & Record<string, any>} 같은 accessor(체이닝).
26
+ */
27
+ export function attachBusApi(busAccessor: Function & Record<string, any>, manager: BusManager): Function & Record<string, any>;
28
+ /**
29
+ * 사용자 메시지 버스 API. driver 한 개를 감싸 emit/on/off/request/with 를 제공한다.
30
+ */
31
+ export class BusManager {
32
+ /**
33
+ * @param {import('./contract.js').BusDriver} driver
34
+ * @param {{ defaults: { prefix: string, defaultPersist: boolean, requestTimeoutMs: number }, logger?: any }} opts
35
+ */
36
+ constructor(driver: import("./contract.js").BusDriver, { defaults, logger }: {
37
+ defaults: {
38
+ prefix: string;
39
+ defaultPersist: boolean;
40
+ requestTimeoutMs: number;
41
+ };
42
+ logger?: any;
43
+ });
44
+ /** @returns {string} 활성 driver 이름('nats'|'cluster'|'memory'). */
45
+ get driverName(): string;
46
+ /**
47
+ * 메시지 발행(fire-and-forget fan-out).
48
+ * @param {string} subject - 구체 subject(wildcard 불가).
49
+ * @param {any} payload
50
+ * @param {import('./contract.js').EmitOpts} [opts] - meta / persist.
51
+ * @returns {Promise<void>}
52
+ */
53
+ emit(subject: string, payload: any, opts?: import("./contract.js").EmitOpts): Promise<void>;
54
+ /**
55
+ * 구독. 핸들러는 `(payload, meta)`. request 대상이면 핸들러 **반환값이 reply**(undefined 면 응답 안 함).
56
+ * @param {string} subject - wildcard(`*`/`>`) 가능.
57
+ * @param {import('./contract.js').BusHandler} handler
58
+ * @param {import('./contract.js').OnOpts} [opts] - persist / ordered.
59
+ * @returns {Promise<import('./contract.js').Subscription>}
60
+ */
61
+ on(subject: string, handler: import("./contract.js").BusHandler, opts?: import("./contract.js").OnOpts): Promise<import("./contract.js").Subscription>;
62
+ /**
63
+ * 구독 해제 — 같은 (subject, handler) 로 등록된 구독을 푼다.
64
+ * @param {string} subject @param {import('./contract.js').BusHandler} handler @returns {Promise<void>}
65
+ */
66
+ off(subject: string, handler: import("./contract.js").BusHandler): Promise<void>;
67
+ /**
68
+ * req/reply — 첫 응답 payload 반환. 응답자 없으면 `bus.no_responders`, 시한 초과면 `bus.request_timeout`.
69
+ * @param {string} subject @param {any} payload
70
+ * @param {{ timeout?: number, meta?: Record<string, any> }} [opts]
71
+ * @returns {Promise<any>}
72
+ */
73
+ request(subject: string, payload: any, opts?: {
74
+ timeout?: number;
75
+ meta?: Record<string, any>;
76
+ }): Promise<any>;
77
+ /**
78
+ * meta 를 바인딩한 스코프 버스 — 이후 emit/request 가 이 meta 를 자동 병합한다(요청 단위 traceId 등).
79
+ * @param {Record<string, any>} meta @returns {{ emit: Function, on: Function, off: Function, request: Function, with: Function }}
80
+ */
81
+ with(meta: Record<string, any>): {
82
+ emit: Function;
83
+ on: Function;
84
+ off: Function;
85
+ request: Function;
86
+ with: Function;
87
+ };
88
+ /** @returns {Promise<{ driver: string, subscriptions: number }>} */
89
+ stats(): Promise<{
90
+ driver: string;
91
+ subscriptions: number;
92
+ }>;
93
+ /** 정리 — 등록 구독 해제 + driver 정리. @returns {Promise<void>} */
94
+ close(): Promise<void>;
95
+ #private;
96
+ }
97
+ import { MemoryBusDriver } from './memory-bus.js';
98
+ import { ClusterBusDriver } from './cluster-bus.js';
99
+ import { NatsBusDriver } from './nats-bus.js';
100
+ export { MemoryBusDriver, ClusterBusDriver, NatsBusDriver };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @typedef {Object} MemorySub - 구독 레코드.
3
+ * @property {string} pattern @property {(envelope: import('./contract.js').BusEnvelope, reply?: import('./contract.js').ReplyFn, subject?: string) => any} handler
4
+ * @property {boolean} ordered @property {Promise<void>} tail - ordered 직렬화용 꼬리 프라미스.
5
+ */
6
+ export class MemoryBusDriver {
7
+ /** @type {'memory'} */
8
+ get name(): "memory";
9
+ /** 매칭 구독에 fan-out. memory 는 persist 무시(opts 는 계약 정합용). @param {string} subject @param {import('./contract.js').BusEnvelope} envelope @param {{ persist?: boolean }} [_opts] @returns {Promise<void>} */
10
+ publish(subject: string, envelope: import("./contract.js").BusEnvelope, _opts?: {
11
+ persist?: boolean;
12
+ }): Promise<void>;
13
+ /** 구독 등록. @param {string} pattern @param {MemorySub['handler']} handler @param {{ ordered?: boolean }} opts @returns {Promise<import('./contract.js').Subscription>} */
14
+ subscribe(pattern: string, handler: MemorySub["handler"], { ordered }?: {
15
+ ordered?: boolean;
16
+ }): Promise<import("./contract.js").Subscription>;
17
+ /**
18
+ * req/reply — 매칭 핸들러에 replyFn 을 주고 첫 응답을 반환. 응답자 없으면 no_responders, 시한 초과면 timeout.
19
+ * @param {string} subject @param {import('./contract.js').BusEnvelope} envelope @param {{ timeout: number }} opts
20
+ * @returns {Promise<import('./contract.js').BusEnvelope>}
21
+ */
22
+ request(subject: string, envelope: import("./contract.js").BusEnvelope, { timeout }: {
23
+ timeout: number;
24
+ }): Promise<import("./contract.js").BusEnvelope>;
25
+ /** @returns {Promise<{ driver: string, subscriptions: number }>} */
26
+ stats(): Promise<{
27
+ driver: string;
28
+ subscriptions: number;
29
+ }>;
30
+ /** 정리 — 구독 비움. @returns {Promise<void>} */
31
+ close(): Promise<void>;
32
+ #private;
33
+ }
34
+ /**
35
+ * - 구독 레코드.
36
+ */
37
+ export type MemorySub = {
38
+ pattern: string;
39
+ handler: (envelope: import("./contract.js").BusEnvelope, reply?: import("./contract.js").ReplyFn, subject?: string) => any;
40
+ ordered: boolean;
41
+ /**
42
+ * - ordered 직렬화용 꼬리 프라미스.
43
+ */
44
+ tail: Promise<void>;
45
+ };
@@ -0,0 +1,41 @@
1
+ export class NatsBusDriver {
2
+ /** @param {{ nc: any }} opts - `nc` 빌린 NatsConnection(`busAdapter.native`). */
3
+ constructor({ nc }: {
4
+ nc: any;
5
+ });
6
+ /** @type {'nats'} */
7
+ get name(): "nats";
8
+ /**
9
+ * 발행 — persist 면 JetStream(영속), 아니면 core pub/sub(비영속).
10
+ * @param {string} subject @param {import('./contract.js').BusEnvelope} envelope @param {{ persist?: boolean }} opts
11
+ * @returns {Promise<void>}
12
+ */
13
+ publish(subject: string, envelope: import("./contract.js").BusEnvelope, { persist }?: {
14
+ persist?: boolean;
15
+ }): Promise<void>;
16
+ /**
17
+ * 구독 — persist 면 JetStream ephemeral consumer, 아니면 core 구독. core 는 wildcard·reply 를 native 지원.
18
+ * @param {string} pattern @param {(envelope: import('./contract.js').BusEnvelope, reply?: import('./contract.js').ReplyFn, subject?: string) => any} handler
19
+ * @param {{ persist?: boolean, ordered?: boolean }} opts @returns {Promise<import('./contract.js').Subscription>}
20
+ */
21
+ subscribe(pattern: string, handler: (envelope: import("./contract.js").BusEnvelope, reply?: import("./contract.js").ReplyFn, subject?: string) => any, { persist, ordered }?: {
22
+ persist?: boolean;
23
+ ordered?: boolean;
24
+ }): Promise<import("./contract.js").Subscription>;
25
+ /**
26
+ * req/reply — NATS native request. 첫 응답 envelope 반환. v3 에러 클래스로 timeout/no-responders 판별.
27
+ * @param {string} subject @param {import('./contract.js').BusEnvelope} envelope @param {{ timeout: number }} opts
28
+ * @returns {Promise<import('./contract.js').BusEnvelope>}
29
+ */
30
+ request(subject: string, envelope: import("./contract.js").BusEnvelope, { timeout }: {
31
+ timeout: number;
32
+ }): Promise<import("./contract.js").BusEnvelope>;
33
+ /** @returns {Promise<{ driver: string, subscriptions: number }>} 영속 구독 수(core 구독은 NATS 측 관리라 미집계). */
34
+ stats(): Promise<{
35
+ driver: string;
36
+ subscriptions: number;
37
+ }>;
38
+ /** 정리 — 영속 consumer 정지. nc 는 어댑터 소유라 닫지 않는다. @returns {Promise<void>} */
39
+ close(): Promise<void>;
40
+ #private;
41
+ }
@@ -11,6 +11,7 @@ export { MegaHubLink } from "./hub-link.js";
11
11
  export { createSessionCleanupSchedule } from "./session-cleanup-schedule.js";
12
12
  export { Router, MegaRouteError } from "./router.js";
13
13
  export { bootApp, buildBootContext } from "./boot.js";
14
+ export { getApp, hasApp } from "./app-registry.js";
14
15
  export { wrapEnvelope, errorEnvelope, synthesizeEnvelopeResponseSchema, ENVELOPE_MARK } from "./envelope.js";
15
16
  export { buildHttpPipeline, wrapPreHandler, composeTransform, composeAfter } from "./pipeline.js";
16
17
  export { ajvErrorToValidationError, MAX_VALIDATION_DETAILS } from "./ajv-mapper.js";
@@ -0,0 +1,44 @@
1
+ /**
2
+ * master 프로세스에 락 responder 를 설치한다(멱등). 워커들의 IPC 요청을 한 MemoryLockDriver 로 직렬화한다.
3
+ * `mega start` 클러스터 분기에서 master 가 1회 호출한다.
4
+ *
5
+ * @param {import('node:cluster').Cluster} [cluster] - 테스트 주입용(기본 node:cluster).
6
+ * @returns {MemoryLockDriver | null} 설치된 master driver(테스트/관측용). 이미 설치됐으면 null.
7
+ */
8
+ export function installClusterLockMaster(cluster?: import("node:cluster").Cluster): MemoryLockDriver | null;
9
+ /** 테스트 격리용 — master responder 설치 플래그 초기화. @returns {void} */
10
+ export function _resetClusterLockMaster(): void;
11
+ /**
12
+ * 워커 측 cluster 락 driver — master 에 IPC 위임하는 얇은 RPC 클라이언트.
13
+ * driver 계약은 {@link import('./contract.js').LockDriver} 를 따른다(typedef — 런타임 implements 아님).
14
+ */
15
+ export class ClusterLockDriver {
16
+ /**
17
+ * @param {{ proc?: NodeJS.Process }} [opts] - `proc` 테스트 주입용(기본 전역 process). cluster 워커면
18
+ * `process.send` 로 master 에 닿는다.
19
+ * @throws {Error} proc.send 가 없으면(=cluster 워커가 아님) — factory 가 cluster 워커일 때만 생성한다.
20
+ */
21
+ constructor({ proc }?: {
22
+ proc?: NodeJS.Process;
23
+ });
24
+ /** @type {'cluster'} */
25
+ get name(): "cluster";
26
+ /** @param {string} key @param {import('./contract.js').NormalizedLockOpts} opts @returns {Promise<import('./contract.js').DriverLock | null>} */
27
+ acquire(key: string, opts: import("./contract.js").NormalizedLockOpts): Promise<import("./contract.js").DriverLock | null>;
28
+ /** @param {string} key @param {string} token @returns {Promise<boolean>} */
29
+ release(key: string, token: string): Promise<boolean>;
30
+ /** @param {string} key @param {string} token @param {number} ttl @returns {Promise<boolean>} */
31
+ extend(key: string, token: string, ttl: number): Promise<boolean>;
32
+ /** @param {string} key @returns {Promise<void>} */
33
+ forceRelease(key: string): Promise<void>;
34
+ /** @returns {Promise<{ driver: string, active: number, waiting: number }>} */
35
+ stats(): Promise<{
36
+ driver: string;
37
+ active: number;
38
+ waiting: number;
39
+ }>;
40
+ /** 정리 — IPC 리스너 제거 + 대기 요청에 null 응답(누수 방지). @returns {Promise<void>} */
41
+ close(): Promise<void>;
42
+ #private;
43
+ }
44
+ import { MemoryLockDriver } from './memory-lock.js';
@@ -0,0 +1,181 @@
1
+ /**
2
+ * @typedef {Object} LockOpts - acquire/with/tryAcquire 옵션(정규화 전).
3
+ * @property {number} [ttl] - 락 보유 시간(ms, 양의 정수). 미지정 시 manager 디폴트.
4
+ * @property {number} [waitMs] - 획득 대기 상한(ms, 0 이상). 0 = 즉시(tryAcquire 와 동일). 미지정 시 디폴트.
5
+ * @property {boolean} [fifo] - true 면 대기자를 도착 순서대로 깨운다(기아 방지). driver 가 지원해야 함.
6
+ * @property {boolean} [fence] - true 면 단조 증가 fence 토큰을 함께 반환(외부 시스템의 stale-writer 차단용).
7
+ * @property {boolean} [extendable] - true 면 manager 가 watchdog 으로 ttl 절반마다 자동 연장(긴 임계구역).
8
+ */
9
+ /**
10
+ * @typedef {Object} NormalizedLockOpts - 검증·기본값 적용된 옵션.
11
+ * @property {number} ttl @property {number} waitMs @property {boolean} fifo @property {boolean} fence @property {boolean} extendable
12
+ */
13
+ /**
14
+ * @typedef {Object} DriverLock - driver.acquire 성공 결과(저수준 — manager 가 LockHandle 로 감싼다).
15
+ * @property {string} token - 이 보유를 식별하는 랜덤 토큰(release/extend 가 소유 검증에 쓴다).
16
+ * @property {number} [fence] - fence 토큰(단조 증가). `fence:true` 일 때만.
17
+ */
18
+ /**
19
+ * @typedef {Object} LockHandle - 사용자에게 반환되는 락 핸들(manager 구성).
20
+ * @property {string} key - 잠근 자원 키.
21
+ * @property {string} token - 보유 토큰.
22
+ * @property {number} [fence] - fence 토큰(`fence:true` 일 때만).
23
+ * @property {() => Promise<void>} release - 락 해제(idempotent — 이미 풀렸어도 안전). watchdog 도 멈춘다.
24
+ * @property {(ttl?: number) => Promise<boolean>} extend - TTL 연장. 아직 보유 중이면 true, 잃었으면 false.
25
+ */
26
+ /**
27
+ * @typedef {Object} LockDriver - driver 가 구현하는 저수준 인터페이스(manager 가 호출).
28
+ * @property {string} name - 'redis' | 'cluster' | 'memory'.
29
+ * @property {(key: string, opts: NormalizedLockOpts) => Promise<DriverLock | null>} acquire -
30
+ * waitMs 안에 획득하면 `{token, fence?}`, 못 잡으면 `null`(throw 아님 — 경합은 정상 결과).
31
+ * @property {(key: string, token: string) => Promise<boolean>} release - 토큰이 현재 보유자면 풀고 true,
32
+ * 아니면(이미 만료/타인 보유) false. 절대 throw 안 함.
33
+ * @property {(key: string, token: string, ttl: number) => Promise<boolean>} extend - 토큰이 보유자면 ttl
34
+ * 갱신 후 true, 아니면 false.
35
+ * @property {(key: string) => Promise<void>} forceRelease - 관리용 강제 해제(토큰 무관). 대기자 깨움 포함.
36
+ * @property {() => Promise<{ driver: string, active: number, waiting: number }>} stats - 현재 보유/대기 수.
37
+ * @property {() => Promise<void>} close - 타이머·구독·연결참조 정리(graceful shutdown).
38
+ */
39
+ /**
40
+ * 락 보유 토큰 생성 — 충돌 사실상 불가한 랜덤 24-hex. release/extend 가 소유권 검증에 쓴다.
41
+ * @returns {string}
42
+ */
43
+ export function generateToken(): string;
44
+ /**
45
+ * acquire/with/tryAcquire 옵션을 검증·정규화한다(fail-fast — 잘못된 입력은 락 시도 전에 throw).
46
+ * @param {{ ttl: number, waitMs: number, fifo: boolean }} defaults - manager 디폴트(config 유래).
47
+ * @param {LockOpts} [opts] - 사용자 옵션.
48
+ * @returns {NormalizedLockOpts}
49
+ * @throws {MegaValidationError} `lock.invalid_opts` - ttl/waitMs 가 정수 범위를 벗어나거나 boolean 아닌 플래그.
50
+ */
51
+ export function normalizeLockOpts(defaults: {
52
+ ttl: number;
53
+ waitMs: number;
54
+ fifo: boolean;
55
+ }, opts?: LockOpts): NormalizedLockOpts;
56
+ /**
57
+ * 락 key 검증 — 비어있지 않은 문자열. 저장소 키/redis subject 로 쓰이므로 제어문자·공백을 막는다.
58
+ * @param {string} key
59
+ * @returns {void}
60
+ * @throws {MegaValidationError} `lock.invalid_key`
61
+ */
62
+ export function assertLockKey(key: string): void;
63
+ /** 락 TTL 디폴트(ms) — 임계구역 보호 시간. config `lock.ttl` 로 조정. */
64
+ export const DEFAULT_TTL_MS: 5000;
65
+ /** 획득 대기 디폴트(ms) — 경합 시 이만큼 기다린다(0=즉시 실패). config `lock.waitMs` 로 조정. */
66
+ export const DEFAULT_WAIT_MS: 1000;
67
+ /** watchdog 자동 연장 주기 = ttl × 이 비율. 만료 전에 넉넉히 갱신(절반). */
68
+ export const WATCHDOG_RATIO: 0.5;
69
+ /**
70
+ * - acquire/with/tryAcquire 옵션(정규화 전).
71
+ */
72
+ export type LockOpts = {
73
+ /**
74
+ * - 락 보유 시간(ms, 양의 정수). 미지정 시 manager 디폴트.
75
+ */
76
+ ttl?: number;
77
+ /**
78
+ * - 획득 대기 상한(ms, 0 이상). 0 = 즉시(tryAcquire 와 동일). 미지정 시 디폴트.
79
+ */
80
+ waitMs?: number;
81
+ /**
82
+ * - true 면 대기자를 도착 순서대로 깨운다(기아 방지). driver 가 지원해야 함.
83
+ */
84
+ fifo?: boolean;
85
+ /**
86
+ * - true 면 단조 증가 fence 토큰을 함께 반환(외부 시스템의 stale-writer 차단용).
87
+ */
88
+ fence?: boolean;
89
+ /**
90
+ * - true 면 manager 가 watchdog 으로 ttl 절반마다 자동 연장(긴 임계구역).
91
+ */
92
+ extendable?: boolean;
93
+ };
94
+ /**
95
+ * - 검증·기본값 적용된 옵션.
96
+ */
97
+ export type NormalizedLockOpts = {
98
+ ttl: number;
99
+ waitMs: number;
100
+ fifo: boolean;
101
+ fence: boolean;
102
+ extendable: boolean;
103
+ };
104
+ /**
105
+ * - driver.acquire 성공 결과(저수준 — manager 가 LockHandle 로 감싼다).
106
+ */
107
+ export type DriverLock = {
108
+ /**
109
+ * - 이 보유를 식별하는 랜덤 토큰(release/extend 가 소유 검증에 쓴다).
110
+ */
111
+ token: string;
112
+ /**
113
+ * - fence 토큰(단조 증가). `fence:true` 일 때만.
114
+ */
115
+ fence?: number;
116
+ };
117
+ /**
118
+ * - 사용자에게 반환되는 락 핸들(manager 구성).
119
+ */
120
+ export type LockHandle = {
121
+ /**
122
+ * - 잠근 자원 키.
123
+ */
124
+ key: string;
125
+ /**
126
+ * - 보유 토큰.
127
+ */
128
+ token: string;
129
+ /**
130
+ * - fence 토큰(`fence:true` 일 때만).
131
+ */
132
+ fence?: number;
133
+ /**
134
+ * - 락 해제(idempotent — 이미 풀렸어도 안전). watchdog 도 멈춘다.
135
+ */
136
+ release: () => Promise<void>;
137
+ /**
138
+ * - TTL 연장. 아직 보유 중이면 true, 잃었으면 false.
139
+ */
140
+ extend: (ttl?: number) => Promise<boolean>;
141
+ };
142
+ /**
143
+ * - driver 가 구현하는 저수준 인터페이스(manager 가 호출).
144
+ */
145
+ export type LockDriver = {
146
+ /**
147
+ * - 'redis' | 'cluster' | 'memory'.
148
+ */
149
+ name: string;
150
+ /**
151
+ * -
152
+ * waitMs 안에 획득하면 `{token, fence?}`, 못 잡으면 `null`(throw 아님 — 경합은 정상 결과).
153
+ */
154
+ acquire: (key: string, opts: NormalizedLockOpts) => Promise<DriverLock | null>;
155
+ /**
156
+ * - 토큰이 현재 보유자면 풀고 true,
157
+ * 아니면(이미 만료/타인 보유) false. 절대 throw 안 함.
158
+ */
159
+ release: (key: string, token: string) => Promise<boolean>;
160
+ /**
161
+ * - 토큰이 보유자면 ttl
162
+ * 갱신 후 true, 아니면 false.
163
+ */
164
+ extend: (key: string, token: string, ttl: number) => Promise<boolean>;
165
+ /**
166
+ * - 관리용 강제 해제(토큰 무관). 대기자 깨움 포함.
167
+ */
168
+ forceRelease: (key: string) => Promise<void>;
169
+ /**
170
+ * - 현재 보유/대기 수.
171
+ */
172
+ stats: () => Promise<{
173
+ driver: string;
174
+ active: number;
175
+ waiting: number;
176
+ }>;
177
+ /**
178
+ * - 타이머·구독·연결참조 정리(graceful shutdown).
179
+ */
180
+ close: () => Promise<void>;
181
+ };
@@ -0,0 +1,38 @@
1
+ export class FifoWaitlist {
2
+ /**
3
+ * @param {RedisLike} client - ioredis 호환 클라이언트(명령용, redis-lock 과 공유).
4
+ * @param {{ zkey: (key: string) => string, seqKey: string }} opts -
5
+ * `zkey` 락 key → zset 키 매퍼, `seqKey` 전역 시퀀스 카운터 키(네임스페이스 일원화).
6
+ */
7
+ constructor(client: RedisLike, { zkey, seqKey }: {
8
+ zkey: (key: string) => string;
9
+ seqKey: string;
10
+ });
11
+ /**
12
+ * 대기열에 합류 — 전역 INCR 시퀀스를 점수로 부여해 도착 순서를 확정한다.
13
+ * @param {string} key @param {string} token @param {number} deadlineMs - 이 대기자의 시한(now+waitMs); reaping 판정용.
14
+ * @returns {Promise<string>} 등록된 member 문자열(leave 에 그대로 넘겨 정확히 제거).
15
+ */
16
+ join(key: string, token: string, deadlineMs: number): Promise<string>;
17
+ /**
18
+ * 내가 큐의 head 인지 — 시한 지난(버려진) head 를 청소한 뒤 맨 앞 token 과 비교.
19
+ * @param {string} key @param {string} token @param {number} nowMs - 현재 시각(Date.now()).
20
+ * @returns {Promise<boolean>}
21
+ */
22
+ isHead(key: string, token: string, nowMs: number): Promise<boolean>;
23
+ /** 대기열에서 이탈(획득 성공/타임아웃/취소 시). @param {string} key @param {string} member - join 이 돌려준 값. @returns {Promise<void>} */
24
+ leave(key: string, member: string): Promise<void>;
25
+ /** 현재 대기자 수. @param {string} key @returns {Promise<number>} */
26
+ size(key: string): Promise<number>;
27
+ #private;
28
+ }
29
+ /**
30
+ * - 이 헬퍼가 쓰는 ioredis 메서드 부분집합(테스트 fake 도 충족).
31
+ */
32
+ export type RedisLike = {
33
+ incr: (key: string) => Promise<number>;
34
+ zadd: (key: string, score: number, member: string) => Promise<any>;
35
+ zrem: (key: string, member: string) => Promise<any>;
36
+ zrange: (key: string, start: number, stop: number) => Promise<string[]>;
37
+ zcard: (key: string) => Promise<number>;
38
+ };