mega-framework 0.1.6 → 0.1.8
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 +9 -0
- package/bin/mega-ws-hub.js +2 -2
- package/package.json +33 -9
- package/sample/crud/.env +10 -1
- package/sample/crud/.env.example +10 -1
- package/sample/crud/.mega/journal/history/20260612092543-create-users.json +261 -0
- package/sample/crud/.mega/journal/snapshot.json +261 -0
- package/sample/crud/apps/main/controllers/auth-controller.js +22 -14
- package/sample/crud/apps/main/controllers/web-controller.js +7 -5
- package/sample/crud/apps/main/locales/server/en.json +12 -1
- package/sample/crud/apps/main/locales/server/ko.json +12 -1
- package/sample/crud/apps/main/migrations/20260606000001-create-users.js +91 -13
- package/sample/crud/apps/main/migrations/20260606000002-create-boards.js +165 -0
- package/sample/crud/apps/main/migrations/20260606000003-create-logs.js +107 -0
- package/sample/crud/apps/main/models/log-partition-model.js +105 -0
- package/sample/crud/apps/main/models/note-model.js +79 -0
- package/sample/crud/apps/main/models/user-level-model.js +24 -0
- package/sample/crud/apps/main/models/user-model.js +146 -0
- package/sample/crud/apps/main/models/user-type-model.js +21 -0
- package/sample/crud/apps/main/models/wallet-model.js +24 -0
- package/sample/crud/apps/main/routes/users.js +55 -10
- package/sample/crud/apps/main/schedules/log-partition-schedule.js +33 -0
- package/sample/crud/apps/main/services/auth-service.js +39 -24
- package/sample/crud/apps/main/services/log-partition-service.js +101 -0
- package/sample/crud/apps/main/services/note-service.js +6 -6
- package/sample/crud/apps/main/services/redis-demo-service.js +3 -3
- package/sample/crud/apps/main/services/user-service.js +62 -21
- package/sample/crud/apps/main/views/auth/login.ejs +6 -6
- package/sample/crud/apps/main/views/auth/register.ejs +46 -5
- package/sample/crud/apps/main/views/users/edit.ejs +42 -5
- package/sample/crud/apps/main/views/users/list.ejs +6 -2
- package/sample/crud/apps/main/views/users/new.ejs +56 -4
- package/sample/crud/docs/log_partition_design.mm.md +23 -0
- package/sample/crud/mega.config.js +10 -2
- package/sample/crud/package.json +3 -3
- package/sample/crud/scripts/start-ws-hub.sh +20 -6
- package/sample/simple/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/sample/simple/package.json +2 -2
- package/src/adapters/adapter-manager.js +2 -1
- package/src/adapters/adapter-options.js +44 -3
- package/src/adapters/file-adapter.js +9 -5
- package/src/adapters/file-session-adapter.js +4 -3
- package/src/adapters/maria-adapter.js +33 -7
- package/src/adapters/mega-cache-adapter.js +83 -6
- package/src/adapters/mega-db-adapter.js +10 -1
- package/src/adapters/mongo-adapter.js +40 -8
- package/src/adapters/postgres-adapter.js +33 -6
- package/src/adapters/redis-adapter.js +7 -3
- package/src/adapters/sqlite-adapter.js +26 -3
- package/src/cli/commands/console-cmd.js +3 -1
- package/src/cli/commands/new.js +13 -3
- package/src/cli/commands/scaffold.js +173 -33
- package/src/cli/generators/index.js +140 -3
- package/src/cli/index.js +437 -155
- package/src/cli/watch.js +188 -0
- package/src/core/ajv-mapper.js +30 -3
- package/src/core/boot.js +464 -245
- package/src/core/cluster-metrics.js +13 -4
- package/src/core/ctx-builder.js +65 -3
- package/src/core/envelope.js +119 -12
- package/src/core/hub-link.js +89 -18
- package/src/core/i18n.js +11 -1
- package/src/core/index.js +7 -3
- package/src/core/mega-app.js +253 -505
- package/src/core/mega-cluster.js +4 -1
- package/src/core/mega-server.js +40 -9
- package/src/core/migration/dialect-registry.js +107 -0
- package/src/core/migration/dialects/README.md +62 -0
- package/src/core/migration/dialects/maria.js +496 -0
- package/src/core/migration/dialects/mongo.js +824 -0
- package/src/core/migration/dialects/postgres.js +563 -0
- package/src/core/migration/dialects/sqlite.js +476 -0
- package/src/core/migration/differ.js +456 -0
- package/src/core/migration/generate.js +508 -0
- package/src/core/migration/journal.js +167 -0
- package/src/core/migration/model-scan.js +84 -0
- package/src/core/migration/mongo-migration-db.js +97 -0
- package/src/core/migration/schema-builder.js +400 -0
- package/src/core/migration/schema-validator.js +315 -0
- package/src/core/migration-lock.js +205 -0
- package/src/core/migration-runner.js +166 -38
- package/src/core/multipart.js +28 -5
- package/src/core/pipeline.js +131 -0
- package/src/core/router.js +70 -65
- package/src/core/scope-registry.js +1 -0
- package/src/core/security.js +70 -12
- package/src/core/session-store.js +14 -1
- package/src/core/workers-manager.js +12 -1
- package/src/core/ws-cluster.js +10 -3
- package/src/core/ws-message.js +48 -4
- package/src/core/ws-presence.js +636 -0
- package/src/core/ws-roster.js +50 -8
- package/src/core/ws-upgrade.js +223 -12
- package/src/index.js +1 -1
- package/src/lib/hub-protocol.js +29 -0
- package/src/lib/mega-circuit-breaker.js +5 -3
- package/src/lib/mega-health.js +35 -4
- package/src/lib/mega-job-queue.js +151 -34
- package/src/lib/mega-job.js +37 -1
- package/src/lib/mega-metrics.js +31 -13
- package/src/lib/mega-plugin.js +34 -3
- package/src/lib/mega-schedule.js +40 -22
- package/src/lib/mega-shutdown.js +114 -39
- package/src/lib/mega-tracing.js +66 -19
- package/src/lib/mega-worker.js +33 -6
- package/src/lib/otel-resource.js +36 -0
- package/src/{cli → lib}/ws-hub.js +139 -15
- package/src/models/crud-sql-builder.js +133 -0
- package/src/models/mega-model.js +82 -2
- package/src/models/model-crud.js +483 -0
- package/src/models/mongo-crud.js +285 -0
- package/templates/adr/code.tpl +23 -0
- package/templates/model/code-mongo.tpl +35 -0
- package/templates/model/code.tpl +15 -1
- package/templates/model/test-mongo.tpl +38 -0
- package/templates/model/test.tpl +4 -0
- package/types/adapters/adapter-manager.d.ts +95 -0
- package/types/adapters/adapter-options.d.ts +93 -0
- package/types/adapters/file-adapter.d.ts +105 -0
- package/types/adapters/file-session-adapter.d.ts +103 -0
- package/types/adapters/index.d.ts +20 -0
- package/types/adapters/maria-adapter.d.ts +117 -0
- package/types/adapters/mega-adapter.d.ts +215 -0
- package/types/adapters/mega-bus-adapter.d.ts +45 -0
- package/types/adapters/mega-cache-adapter.d.ts +73 -0
- package/types/adapters/mega-db-adapter.d.ts +50 -0
- package/types/adapters/mega-lock-adapter.d.ts +62 -0
- package/types/adapters/mega-log-sink-adapter.d.ts +15 -0
- package/types/adapters/mega-session-adapter.d.ts +32 -0
- package/types/adapters/mongo-adapter.d.ts +150 -0
- package/types/adapters/nats-adapter.d.ts +108 -0
- package/types/adapters/postgres-adapter.d.ts +141 -0
- package/types/adapters/redis-adapter.d.ts +78 -0
- package/types/adapters/redis-session-adapter.d.ts +82 -0
- package/types/adapters/redlock-adapter.d.ts +149 -0
- package/types/adapters/registry.d.ts +46 -0
- package/types/adapters/sqlite-adapter.d.ts +112 -0
- package/types/auth/index.d.ts +24 -0
- package/types/cli/commands/console-cmd.d.ts +37 -0
- package/types/cli/commands/new.d.ts +16 -0
- package/types/cli/commands/routes.d.ts +36 -0
- package/types/cli/commands/scaffold.d.ts +78 -0
- package/types/cli/commands/test-cmd.d.ts +14 -0
- package/types/cli/generators/index.d.ts +122 -0
- package/types/cli/index.d.ts +234 -0
- package/types/cli/template-engine.d.ts +40 -0
- package/types/cli/watch.d.ts +59 -0
- package/types/core/ajv-mapper.d.ts +27 -0
- package/types/core/boot.d.ts +233 -0
- package/types/core/cluster-metrics.d.ts +52 -0
- package/types/core/config-loader.d.ts +13 -0
- package/types/core/config-validator.d.ts +30 -0
- package/types/core/ctx-builder.d.ts +103 -0
- package/types/core/envelope.d.ts +79 -0
- package/types/core/error-mapper.d.ts +17 -0
- package/types/core/formbody.d.ts +41 -0
- package/types/core/hub-link.d.ts +266 -0
- package/types/core/i18n.d.ts +178 -0
- package/types/core/index.d.ts +28 -0
- package/types/core/mega-app.d.ts +529 -0
- package/types/core/mega-cluster.d.ts +104 -0
- package/types/core/mega-server.d.ts +91 -0
- package/types/core/mega-service.d.ts +31 -0
- package/types/core/migration/dialect-registry.d.ts +22 -0
- package/types/core/migration/dialects/maria.d.ts +99 -0
- package/types/core/migration/dialects/mongo.d.ts +89 -0
- package/types/core/migration/dialects/postgres.d.ts +117 -0
- package/types/core/migration/dialects/sqlite.d.ts +111 -0
- package/types/core/migration/differ.d.ts +47 -0
- package/types/core/migration/generate.d.ts +56 -0
- package/types/core/migration/journal.d.ts +52 -0
- package/types/core/migration/model-scan.d.ts +19 -0
- package/types/core/migration/mongo-migration-db.d.ts +7 -0
- package/types/core/migration/schema-builder.d.ts +197 -0
- package/types/core/migration/schema-validator.d.ts +20 -0
- package/types/core/migration-lock.d.ts +33 -0
- package/types/core/migration-runner.d.ts +101 -0
- package/types/core/multipart.d.ts +86 -0
- package/types/core/openapi.d.ts +62 -0
- package/types/core/pipeline.d.ts +93 -0
- package/types/core/router.d.ts +159 -0
- package/types/core/routes-loader.d.ts +21 -0
- package/types/core/scope-registry.d.ts +14 -0
- package/types/core/security.d.ts +77 -0
- package/types/core/services-loader.d.ts +27 -0
- package/types/core/session-cleanup-schedule.d.ts +19 -0
- package/types/core/session-store.d.ts +25 -0
- package/types/core/session.d.ts +77 -0
- package/types/core/static-assets.d.ts +73 -0
- package/types/core/template.d.ts +106 -0
- package/types/core/workers-manager.d.ts +79 -0
- package/types/core/ws-cluster.d.ts +208 -0
- package/types/core/ws-compression.d.ts +112 -0
- package/types/core/ws-controller.d.ts +65 -0
- package/types/core/ws-message.d.ts +106 -0
- package/types/core/ws-presence.d.ts +273 -0
- package/types/core/ws-roster.d.ts +108 -0
- package/types/core/ws-upgrade.d.ts +260 -0
- package/types/errors/config-error.d.ts +10 -0
- package/types/errors/http-errors.d.ts +120 -0
- package/types/errors/index.d.ts +3 -0
- package/types/errors/mega-error.d.ts +32 -0
- package/types/index.d.ts +39 -0
- package/types/lib/asp/config.d.ts +49 -0
- package/types/lib/asp/crypto.d.ts +43 -0
- package/types/lib/asp/errors.d.ts +30 -0
- package/types/lib/asp/nonce-cache.d.ts +52 -0
- package/types/lib/asp/plugin.d.ts +30 -0
- package/types/lib/asp/ws-terminator.d.ts +45 -0
- package/types/lib/env-mapper.d.ts +14 -0
- package/types/lib/hub-protocol.d.ts +106 -0
- package/types/lib/index.d.ts +22 -0
- package/types/lib/logger/telegram-core.d.ts +104 -0
- package/types/lib/logger/telegram-transport.d.ts +45 -0
- package/types/lib/mega-brute-force.d.ts +66 -0
- package/types/lib/mega-circuit-breaker.d.ts +243 -0
- package/types/lib/mega-cron.d.ts +66 -0
- package/types/lib/mega-hash.d.ts +32 -0
- package/types/lib/mega-health.d.ts +48 -0
- package/types/lib/mega-job-queue.d.ts +188 -0
- package/types/lib/mega-job-worker.d.ts +130 -0
- package/types/lib/mega-job.d.ts +145 -0
- package/types/lib/mega-logger.d.ts +45 -0
- package/types/lib/mega-metrics.d.ts +285 -0
- package/types/lib/mega-plugin.d.ts +245 -0
- package/types/lib/mega-retry.d.ts +85 -0
- package/types/lib/mega-schedule.d.ts +260 -0
- package/types/lib/mega-shutdown.d.ts +135 -0
- package/types/lib/mega-tracing.d.ts +224 -0
- package/types/lib/mega-worker.d.ts +129 -0
- package/types/lib/otel-resource.d.ts +16 -0
- package/types/lib/worker-runner/process-entry.d.ts +1 -0
- package/types/lib/worker-runner/task-dispatch.d.ts +28 -0
- package/types/lib/worker-runner/thread-entry.d.ts +1 -0
- package/types/lib/ws-hub.d.ts +259 -0
- package/types/models/crud-sql-builder.d.ts +48 -0
- package/types/models/index.d.ts +1 -0
- package/types/models/mega-model.d.ts +138 -0
- package/types/models/model-crud.d.ts +82 -0
- package/types/models/mongo-crud.d.ts +59 -0
- package/types/test/index.d.ts +84 -0
- package/.env +0 -127
- package/sample/crud/apps/main/migrations/20260606000002-add-auth-to-users.js +0 -30
- package/sample/crud/apps/main/models/note.js +0 -71
- package/sample/crud/apps/main/models/user.js +0 -86
- package/sample/crud/package-lock.json +0 -5665
- package/sample/crud/yarn.lock +0 -2142
- package/sample/simple/package-lock.json +0 -1851
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} WorkerHandle - 풀 안의 워커 1개 핸들.
|
|
3
|
+
* @property {number} id - 핸들 식별자(로그/디버그).
|
|
4
|
+
* @property {Worker | import('node:child_process').ChildProcess} native - 실제 thread/process.
|
|
5
|
+
* @property {number | null} current - 처리 중인 taskId(없으면 null).
|
|
6
|
+
* @property {boolean} down - 종료/크래시 처리됨(중복 처리 가드).
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} WorkerTask - 디스패치 대기/진행 중인 task 1건.
|
|
10
|
+
* @property {number} taskId
|
|
11
|
+
* @property {string} taskName
|
|
12
|
+
* @property {any} args
|
|
13
|
+
* @property {{ timeoutMs?: number }} opts
|
|
14
|
+
* @property {(value: any) => void} resolve
|
|
15
|
+
* @property {(err: Error) => void} reject
|
|
16
|
+
* @property {WorkerHandle | null} handle - 배정된 워커(미배정 null).
|
|
17
|
+
* @property {ReturnType<typeof setTimeout> | null} timer - 타임아웃 타이머.
|
|
18
|
+
* @property {boolean} isSettled
|
|
19
|
+
* @property {(() => void) | null} _notify - stop() 의 완료 대기 깨우기.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* CPU 워커 풀. 서브클래스가 `static name`/`static taskFile`/`static mode`/`static poolSize` 를 선언하고,
|
|
23
|
+
* 호출자는 `run(taskName, args, opts)` 로 task 를 디스패치한다.
|
|
24
|
+
*/
|
|
25
|
+
export class MegaWorker extends EventEmitter<any> {
|
|
26
|
+
/**
|
|
27
|
+
* @param {object} [args]
|
|
28
|
+
* @param {string} [args.projectRoot] - `static taskFile` 상대경로 해석 기준(디폴트 process.cwd()).
|
|
29
|
+
* @throws {MegaConfigError} static 설정(taskFile/mode/poolSize)이 잘못됐을 때 — 부팅 fail-fast.
|
|
30
|
+
*/
|
|
31
|
+
constructor({ projectRoot }?: {
|
|
32
|
+
projectRoot?: string;
|
|
33
|
+
});
|
|
34
|
+
/** @returns {string} 등록 키(= `static name` 오버라이드 또는 클래스명). */
|
|
35
|
+
get name(): string;
|
|
36
|
+
/** @returns {'thread' | 'process'} 실행 모드(디폴트 'thread'). */
|
|
37
|
+
get mode(): "thread" | "process";
|
|
38
|
+
/** @returns {number} 풀 크기. */
|
|
39
|
+
get poolSize(): number;
|
|
40
|
+
/** @returns {number} 윈도우({@link MegaWorker#restartWindowMs}) 내 crash 교체 허용 횟수. */
|
|
41
|
+
get maxRestarts(): number;
|
|
42
|
+
/** @returns {number} crash 교체 판정 슬라이딩 윈도우(ms). `static restartWindowMs` 로 조정. */
|
|
43
|
+
get restartWindowMs(): number;
|
|
44
|
+
/** @returns {boolean} start() 후 stop() 전이면 true. */
|
|
45
|
+
get isStarted(): boolean;
|
|
46
|
+
/** @param {'dispatch'|'done'|'fail'|'crash'|'stopped'} event @param {(payload: any) => void} listener @returns {this} */
|
|
47
|
+
on(event: "dispatch" | "done" | "fail" | "crash" | "stopped", listener: (payload: any) => void): this;
|
|
48
|
+
/** @param {'dispatch'|'done'|'fail'|'crash'|'stopped'} event @param {(payload: any) => void} listener @returns {this} */
|
|
49
|
+
once(event: "dispatch" | "done" | "fail" | "crash" | "stopped", listener: (payload: any) => void): this;
|
|
50
|
+
/** @param {'dispatch'|'done'|'fail'|'crash'|'stopped'} event @param {(payload: any) => void} listener @returns {this} */
|
|
51
|
+
off(event: "dispatch" | "done" | "fail" | "crash" | "stopped", listener: (payload: any) => void): this;
|
|
52
|
+
/**
|
|
53
|
+
* 워커 풀을 띄운다(`poolSize` 개). 각 워커가 taskFile 을 로드해 `{ ready:true }` 를 보낼 때까지 대기한다
|
|
54
|
+
* (race 방지). 멱등 — 이미 start 됐으면 무시. taskFile 로드 실패는 spawn 핸드셰이크에서 fail-fast.
|
|
55
|
+
* @returns {Promise<this>}
|
|
56
|
+
* @throws {MegaError} 전원 spawn 실패 시 근본 원인(`worker.spawn_failed`)을 전파. 풀 **일부만** 떠도
|
|
57
|
+
* 성공분을 terminate 한 뒤 `worker.start_partial` 로 fail-fast(M-1 — 누수 방지).
|
|
58
|
+
*/
|
|
59
|
+
start(): Promise<this>;
|
|
60
|
+
/**
|
|
61
|
+
* task 를 워커에 디스패치한다. 유휴 워커가 있으면 즉시, 없으면 큐 대기 후 가용 시 디스패치.
|
|
62
|
+
* @param {string} taskName - taskFile 이 export 한 함수명.
|
|
63
|
+
* @param {any} [args] - 함수 인자(structured clone/IPC 가능한 데이터만 — 함수·클래스 인스턴스 X). 직렬화
|
|
64
|
+
* 불가 인자는 디스패치 시점에 `worker.invalid_args` 로 reject 된다(fail-fast — H-1/M-2, ADR-124 보강).
|
|
65
|
+
* @param {{ timeoutMs?: number }} [opts] - timeoutMs 초과 시 `worker.task_timeout` reject + 워커 교체.
|
|
66
|
+
* @returns {Promise<any>} task 함수 반환값.
|
|
67
|
+
* @throws {MegaError} not_started / stopped / invalid_task / pool_exhausted (즉시 reject) /
|
|
68
|
+
* invalid_args (직렬화 불가 인자 — 디스패치 시점 reject).
|
|
69
|
+
*/
|
|
70
|
+
run(taskName: string, args?: any, opts?: {
|
|
71
|
+
timeoutMs?: number;
|
|
72
|
+
}): Promise<any>;
|
|
73
|
+
/**
|
|
74
|
+
* graceful shutdown — 새 run() 거부 + 큐 대기분 `worker.stopped` reject + in-flight 완료 대기 → terminate.
|
|
75
|
+
* `MegaShutdown` 정리 경로에서 호출된다(workers-manager 가 `register('workers:stop', ...)` 배선).
|
|
76
|
+
* @returns {Promise<this>}
|
|
77
|
+
*/
|
|
78
|
+
stop(): Promise<this>;
|
|
79
|
+
#private;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* - 풀 안의 워커 1개 핸들.
|
|
83
|
+
*/
|
|
84
|
+
export type WorkerHandle = {
|
|
85
|
+
/**
|
|
86
|
+
* - 핸들 식별자(로그/디버그).
|
|
87
|
+
*/
|
|
88
|
+
id: number;
|
|
89
|
+
/**
|
|
90
|
+
* - 실제 thread/process.
|
|
91
|
+
*/
|
|
92
|
+
native: Worker | import("node:child_process").ChildProcess;
|
|
93
|
+
/**
|
|
94
|
+
* - 처리 중인 taskId(없으면 null).
|
|
95
|
+
*/
|
|
96
|
+
current: number | null;
|
|
97
|
+
/**
|
|
98
|
+
* - 종료/크래시 처리됨(중복 처리 가드).
|
|
99
|
+
*/
|
|
100
|
+
down: boolean;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* - 디스패치 대기/진행 중인 task 1건.
|
|
104
|
+
*/
|
|
105
|
+
export type WorkerTask = {
|
|
106
|
+
taskId: number;
|
|
107
|
+
taskName: string;
|
|
108
|
+
args: any;
|
|
109
|
+
opts: {
|
|
110
|
+
timeoutMs?: number;
|
|
111
|
+
};
|
|
112
|
+
resolve: (value: any) => void;
|
|
113
|
+
reject: (err: Error) => void;
|
|
114
|
+
/**
|
|
115
|
+
* - 배정된 워커(미배정 null).
|
|
116
|
+
*/
|
|
117
|
+
handle: WorkerHandle | null;
|
|
118
|
+
/**
|
|
119
|
+
* - 타임아웃 타이머.
|
|
120
|
+
*/
|
|
121
|
+
timer: ReturnType<typeof setTimeout> | null;
|
|
122
|
+
isSettled: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* - stop() 의 완료 대기 깨우기.
|
|
125
|
+
*/
|
|
126
|
+
_notify: (() => void) | null;
|
|
127
|
+
};
|
|
128
|
+
import { EventEmitter } from 'node:events';
|
|
129
|
+
import { Worker } from 'node:worker_threads';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 공통 입력 → OTel Resource. 시크릿은 attributes 에 싣지 말 것(exporter 로 평문 전송됨).
|
|
3
|
+
*
|
|
4
|
+
* @param {object} opts
|
|
5
|
+
* @param {string} opts.serviceName - **필수**(호출부가 선검증). `service.name` 속성.
|
|
6
|
+
* @param {string} [opts.version] - `service.version`.
|
|
7
|
+
* @param {string} [opts.environment] - `deployment.environment.name`.
|
|
8
|
+
* @param {Record<string, any>} [opts.attributes] - 추가 resource 속성(머지).
|
|
9
|
+
* @returns {import('@opentelemetry/resources').Resource}
|
|
10
|
+
*/
|
|
11
|
+
export function buildOtelResource({ serviceName, version, environment, attributes }: {
|
|
12
|
+
serviceName: string;
|
|
13
|
+
version?: string;
|
|
14
|
+
environment?: string;
|
|
15
|
+
attributes?: Record<string, any>;
|
|
16
|
+
}): import("@opentelemetry/resources").Resource;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* taskFile 모듈을 동적 import 한다. 절대경로를 file:// URL 로 변환해 OS·ESM 호환성을 보장한다.
|
|
3
|
+
* @param {string} taskFile - 워커 진입 모듈 절대경로.
|
|
4
|
+
* @returns {Promise<Record<string, any>>} 모듈 네임스페이스(task 함수들).
|
|
5
|
+
*/
|
|
6
|
+
export function loadTaskModule(taskFile: string): Promise<Record<string, any>>;
|
|
7
|
+
/**
|
|
8
|
+
* Error 를 스레드/프로세스 경계 너머로 보낼 수 있게 평탄화한다 (structuredClone/IPC 는 Error 인스턴스의
|
|
9
|
+
* name/message/stack 을 보존하지 않으므로 명시 직렬화). 부모가 이를 받아 다시 Error 로 복원한다.
|
|
10
|
+
* @param {unknown} err
|
|
11
|
+
* @returns {{ name: string, message: string, stack?: string, code?: string }}
|
|
12
|
+
*/
|
|
13
|
+
export function serializeError(err: unknown): {
|
|
14
|
+
name: string;
|
|
15
|
+
message: string;
|
|
16
|
+
stack?: string;
|
|
17
|
+
code?: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* 부모가 보낸 task 디스패치 메시지 1건을 처리한다 — 모듈에서 `taskName` 함수를 찾아 실행하고 결과/에러를
|
|
21
|
+
* 부모로 돌려보낸다. 미존재 task·실행 실패를 묵살하지 않고 `{ ok:false, error }` 로 명시 보고.
|
|
22
|
+
*
|
|
23
|
+
* @param {Record<string, any>} mod - loadTaskModule 결과.
|
|
24
|
+
* @param {any} msg - 부모 메시지 `{ id, taskName, args }`.
|
|
25
|
+
* @param {(reply: any) => void} send - 부모로 회신하는 채널(`parentPort.postMessage` / `process.send`).
|
|
26
|
+
* @returns {Promise<void>}
|
|
27
|
+
*/
|
|
28
|
+
export function handleTaskMessage(mod: Record<string, any>, msg: any, send: (reply: any) => void): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 진입점 — env 로 설정 읽어 hub 기동 (bin/mega-ws-hub.js 에서 호출).
|
|
3
|
+
*
|
|
4
|
+
* env: MEGA_WSHUB_TOKENS (필수, 콤마구분 acceptedTokens), MEGA_WSHUB_PORT (기본 3100),
|
|
5
|
+
* MEGA_WSHUB_HOST (기본 0.0.0.0), MEGA_WSHUB_HEARTBEAT_MS (선택), MEGA_WSHUB_MAX_PAYLOAD (선택, bytes),
|
|
6
|
+
* MEGA_WSHUB_COMPRESSION ('true' 면 압축 ON, ADR-078 디폴트값으로),
|
|
7
|
+
* MEGA_WSHUB_COMPRESSION_THRESHOLD (선택, bytes — 압축 ON 일 때만 적용).
|
|
8
|
+
* 향후 mega.config.js 의 wsHub 블록 로딩으로 대체 (ADR-068) — 그 시점에 6 필드 전체가
|
|
9
|
+
* config 로 전달된다. 지금 CLI 는 env 한정이라 enabled + threshold 만 노출(나머지는 ADR-078 디폴트).
|
|
10
|
+
*
|
|
11
|
+
* SIGTERM/SIGINT 시 MegaShutdown 이 hub.stop({ drain: true })(bridge 소켓 4503 + wss.close)을
|
|
12
|
+
* 실행한다(L2 + drain) — 독립 hub 종료는 "다른 인스턴스로 재연결" 신호(4503)가 맞다(ADR-098).
|
|
13
|
+
*
|
|
14
|
+
* @returns {Promise<MegaWsHub>}
|
|
15
|
+
*/
|
|
16
|
+
export function runWsHubCli(): Promise<MegaWsHub>;
|
|
17
|
+
/** 기본 heartbeat 주기 (ms, 04-data-models MegaWsHubConfig.heartbeatMs 기본값). */
|
|
18
|
+
export const DEFAULT_HEARTBEAT_MS: 25000;
|
|
19
|
+
/** 기본 최대 프레임 크기 (bytes, L3). 1 MiB — 정상 envelope 은 수 KB 이므로 넉넉하다. */
|
|
20
|
+
export const DEFAULT_MAX_PAYLOAD_BYTES: 1048576;
|
|
21
|
+
/**
|
|
22
|
+
* bridge 별 송신 버퍼(bufferedAmount) 기본 상한(바이트) — 16 MiB. 초과 = 느린 bridge 가 fan-out 을 못
|
|
23
|
+
* 따라오는 것 → terminate(백프레셔). 클라↔bridge 종단(`ws-upgrade.js` DEFAULT_MAX_BUFFERED_BYTES)과 대칭.
|
|
24
|
+
*/
|
|
25
|
+
export const DEFAULT_MAX_BUFFERED_BYTES: number;
|
|
26
|
+
export class MegaWsHub {
|
|
27
|
+
/**
|
|
28
|
+
* @param {Object} [opts]
|
|
29
|
+
* @param {string[]} [opts.acceptedTokens] - Bridge Bearer 토큰 화이트리스트 (비거나 누락 시 throw).
|
|
30
|
+
* @param {number} [opts.heartbeatMs=25000] - register_ok 로 알려줄 heartbeat 주기.
|
|
31
|
+
* @param {number} [opts.maxPayloadBytes=1048576] - WS 프레임 최대 크기(L3). 초과 시 ws 가 1009 close.
|
|
32
|
+
* @param {number} [opts.maxBufferedBytes=16777216] - bridge 별 송신 버퍼(bufferedAmount) 상한(바이트).
|
|
33
|
+
* 초과한 bridge 는 느린 소비자로 보고 terminate 한다 — fan-out 허브에서 bridge 1개가 느려도 모든
|
|
34
|
+
* 채널 메시지가 그 소켓 버퍼(힙)에 무한 적재돼 OOM 으로 가는 것을 막는다(백프레셔).
|
|
35
|
+
* @param {string} [opts.hubId] - hub 식별자. 기본 ULID 자동 생성.
|
|
36
|
+
* @param {import('../core/ws-compression.js').WsCompressionConfig} [opts.compression] - Bridge↔Hub
|
|
37
|
+
* link per-message deflate 압축(ADR-078 / wsHub.compression). 디폴트 OFF.
|
|
38
|
+
* bridge(MegaHubLink)와 양쪽이 협상해야 활성. 잘못된 threshold/windowBits 면 즉시 throw.
|
|
39
|
+
* @param {{ warn?: Function, debug?: Function, info?: Function, error?: Function }} [opts.logger]
|
|
40
|
+
*/
|
|
41
|
+
constructor({ acceptedTokens, heartbeatMs, maxPayloadBytes, maxBufferedBytes, hubId, compression, logger }?: {
|
|
42
|
+
acceptedTokens?: string[];
|
|
43
|
+
heartbeatMs?: number;
|
|
44
|
+
maxPayloadBytes?: number;
|
|
45
|
+
maxBufferedBytes?: number;
|
|
46
|
+
hubId?: string;
|
|
47
|
+
compression?: import("../core/ws-compression.js").WsCompressionConfig;
|
|
48
|
+
logger?: {
|
|
49
|
+
warn?: Function;
|
|
50
|
+
debug?: Function;
|
|
51
|
+
info?: Function;
|
|
52
|
+
error?: Function;
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
_acceptedTokens: string[];
|
|
56
|
+
_heartbeatMs: number;
|
|
57
|
+
_maxPayloadBytes: number;
|
|
58
|
+
_maxBufferedBytes: number;
|
|
59
|
+
_hubId: string;
|
|
60
|
+
/** @type {false | Object} WebSocketServer perMessageDeflate 로 전달(start). */
|
|
61
|
+
_perMessageDeflate: false | Object;
|
|
62
|
+
_log: {
|
|
63
|
+
warn?: Function;
|
|
64
|
+
debug?: Function;
|
|
65
|
+
info?: Function;
|
|
66
|
+
error?: Function;
|
|
67
|
+
};
|
|
68
|
+
/** @type {WebSocketServer | null} */
|
|
69
|
+
_wss: WebSocketServer | null;
|
|
70
|
+
/** heartbeat liveness 체크 interval (M3). @type {ReturnType<typeof setInterval> | null} */
|
|
71
|
+
_livenessTimer: ReturnType<typeof setInterval> | null;
|
|
72
|
+
/** 등록된 bridge 연결. connId → { socket, lastSeen, protocolVersion, presenceFanout }. @type {Map<string, { socket: import('ws').WebSocket, lastSeen: number, protocolVersion?: number, presenceFanout?: boolean }>} */
|
|
73
|
+
_bridges: Map<string, {
|
|
74
|
+
socket: import("ws").WebSocket;
|
|
75
|
+
lastSeen: number;
|
|
76
|
+
protocolVersion?: number;
|
|
77
|
+
presenceFanout?: boolean;
|
|
78
|
+
}>;
|
|
79
|
+
/** presence. sessionId → { bridgeConnId, userId, channels:Set, metadata }. @type {Map<string, { bridgeConnId: string, userId: string, channels: Set<string>, metadata: Object }>} */
|
|
80
|
+
_sessions: Map<string, {
|
|
81
|
+
bridgeConnId: string;
|
|
82
|
+
userId: string;
|
|
83
|
+
channels: Set<string>;
|
|
84
|
+
metadata: Object;
|
|
85
|
+
}>;
|
|
86
|
+
/** channel → sessionId 집합. @type {Map<string, Set<string>>} */
|
|
87
|
+
_channelSessions: Map<string, Set<string>>;
|
|
88
|
+
/** userId → sessionId 집합 (DIRECT fan-out, ADR-035). @type {Map<string, Set<string>>} */
|
|
89
|
+
_userSessions: Map<string, Set<string>>;
|
|
90
|
+
/**
|
|
91
|
+
* 역인덱스: channel → (bridgeConnId → 그 bridge 의 채널 내 세션 수). BROADCAST 의 bridge 선정을
|
|
92
|
+
* 세션 수 무관 O(bridge 수)로 만든다 — 멤버 10k 채널에서 메시지당 수백 µs 가 µs 대로 떨어진다.
|
|
93
|
+
* `_addSession`/`_removeSession` 이 증분 유지(카운트 0 → 키 제거), 메모리는 채널×bridge 수준.
|
|
94
|
+
* @type {Map<string, Map<string, number>>}
|
|
95
|
+
*/
|
|
96
|
+
_channelBridges: Map<string, Map<string, number>>;
|
|
97
|
+
/** hub 식별자. */
|
|
98
|
+
get hubId(): string;
|
|
99
|
+
/** 등록된 bridge 수 (테스트/관측용). */
|
|
100
|
+
get bridgeCount(): number;
|
|
101
|
+
/** 현재 presence 세션 수 (테스트/관측용). */
|
|
102
|
+
get sessionCount(): number;
|
|
103
|
+
/**
|
|
104
|
+
* hub 시작.
|
|
105
|
+
* @param {{ port?: number, host?: string }} [opts]
|
|
106
|
+
* @returns {Promise<{ port: number, host: string }>} 실제 바인딩 주소.
|
|
107
|
+
*/
|
|
108
|
+
start({ port, host }?: {
|
|
109
|
+
port?: number;
|
|
110
|
+
host?: string;
|
|
111
|
+
}): Promise<{
|
|
112
|
+
port: number;
|
|
113
|
+
host: string;
|
|
114
|
+
}>;
|
|
115
|
+
/** 현재 바인딩 주소 (테스트용). */
|
|
116
|
+
address(): {
|
|
117
|
+
port: number;
|
|
118
|
+
host: string;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* 연결 1건 처리. register 전엔 hub.register 만 허용. 인증 통과 시 presence 라우팅 활성.
|
|
122
|
+
* @param {import('ws').WebSocket} socket
|
|
123
|
+
* @param {import('node:http').IncomingMessage} _req
|
|
124
|
+
* @private
|
|
125
|
+
*/
|
|
126
|
+
private _onConnection;
|
|
127
|
+
/**
|
|
128
|
+
* hub.register 처리 — Bearer 검증(timing-safe). 성공 시 bridge 등록 + register_ok.
|
|
129
|
+
* @param {string} connId
|
|
130
|
+
* @param {import('ws').WebSocket} socket
|
|
131
|
+
* @param {Object} msg - 검증된 hub.register envelope.
|
|
132
|
+
* @returns {boolean} 등록 성공 여부(연결의 isRegistered 갱신용).
|
|
133
|
+
* @private
|
|
134
|
+
*/
|
|
135
|
+
private _handleRegister;
|
|
136
|
+
/**
|
|
137
|
+
* 등록된 bridge 의 수신 메시지 라우팅 (12-타입).
|
|
138
|
+
* @param {string} connId
|
|
139
|
+
* @param {import('ws').WebSocket} socket
|
|
140
|
+
* @param {Object} msg - 검증된 hub 메시지.
|
|
141
|
+
* @returns {void}
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
private _route;
|
|
145
|
+
/**
|
|
146
|
+
* presence 에 세션 추가.
|
|
147
|
+
*
|
|
148
|
+
* sessionId 가 이미 존재하면(같은 세션 재JOIN, 또는 채널 변경) 먼저 옛 매핑을 정리한 뒤 재삽입한다
|
|
149
|
+
* — 그러지 않으면 옛 채널 인덱스(`_channelSessions`)에 stale 엔트리가 남는다(채널이 바뀐 경우).
|
|
150
|
+
* 옛 매핑이 **다른 bridge** 소속이면 "sessionId 전역 유일" 계약(ADR-059) 위반이므로 warn 로그를 남기고
|
|
151
|
+
* last-writer 로 재배정한다 — silent 덮어쓰기 금지(L-3). 이렇게 하면 옛 bridge 의 채널 인덱스가
|
|
152
|
+
* 제거되어, 옛 bridge 의 `_handleBridgeGone` 이 재배정된 세션을 잘못 지우는 일도 없어진다.
|
|
153
|
+
*
|
|
154
|
+
* @param {string} connId
|
|
155
|
+
* @param {{ userId: string, sessionId: string, channels: string[], metadata?: Object }} entry
|
|
156
|
+
* @private
|
|
157
|
+
*/
|
|
158
|
+
private _addSession;
|
|
159
|
+
/**
|
|
160
|
+
* channel→bridge 역인덱스 증분 갱신. 카운트가 0 이 되면 키를 지워 인덱스가 stale 하지 않게 한다.
|
|
161
|
+
* @param {string} channel @param {string} connId @param {1|-1} delta
|
|
162
|
+
* @private
|
|
163
|
+
*/
|
|
164
|
+
private _bumpChannelBridge;
|
|
165
|
+
/**
|
|
166
|
+
* presence 에서 세션 제거. 빈 인덱스는 정리.
|
|
167
|
+
* @param {string} sessionId
|
|
168
|
+
* @returns {boolean} 실제 제거 여부.
|
|
169
|
+
* @private
|
|
170
|
+
*/
|
|
171
|
+
private _removeSession;
|
|
172
|
+
/**
|
|
173
|
+
* bridge 연결 종료 시 — 그 bridge 의 모든 세션 제거 + 다른 bridge 에 bulk_leave 통지.
|
|
174
|
+
* @param {string} connId
|
|
175
|
+
* @private
|
|
176
|
+
*/
|
|
177
|
+
private _handleBridgeGone;
|
|
178
|
+
/**
|
|
179
|
+
* origin 을 제외한 모든 등록 bridge 로 송신. envelope 는 1회만 직렬화(L5).
|
|
180
|
+
* BULK_LEAVE(bridge-gone 정리 통지)처럼 전 bridge 가 받아야 하는 통지에 쓴다.
|
|
181
|
+
* @param {string} exceptConnId
|
|
182
|
+
* @param {Object} envelope
|
|
183
|
+
* @private
|
|
184
|
+
*/
|
|
185
|
+
private _fanOutToOthers;
|
|
186
|
+
/**
|
|
187
|
+
* presence(JOIN/LEAVE/METADATA) fan-out — `presence-fanout` capability 를 선언한 bridge 에만 송신.
|
|
188
|
+
* roster 가 redis 로 분리(ADR-177)된 뒤 표준 bridge 는 이 타입들을 no-op 으로 버리므로, 옵트인하지
|
|
189
|
+
* 않은 bridge 에는 보내지 않는다 — 세션 churn × bridge 수 만큼의 죽은 프레임을 제거한다.
|
|
190
|
+
* 구버전 bridge 호환: hub presence 를 실제로 쓰려는 bridge 는 capability 로 명시 선언한다.
|
|
191
|
+
* @param {string} exceptConnId
|
|
192
|
+
* @param {Object} envelope
|
|
193
|
+
* @private
|
|
194
|
+
*/
|
|
195
|
+
private _fanOutPresence;
|
|
196
|
+
/**
|
|
197
|
+
* 한 채널의 세션을 가진 bridge 들로 fan-out (origin 제외, 중복 bridge 1회). 직렬화 1회(L5).
|
|
198
|
+
*
|
|
199
|
+
* `exceptSessionIds` 가 있으면 해당 세션은 bridge 선정에서 제외한다(ADR-098). 어떤 bridge 의
|
|
200
|
+
* 채널 세션이 **전부** 제외 대상이면 그 bridge 는 통째로 스킵된다(불필요한 전송 절약). 일부만 제외면
|
|
201
|
+
* bridge 는 받아서 로컬에서 해당 세션만 건너뛴다(envelope 의 payload.exceptSessionIds 가 그대로 전달됨).
|
|
202
|
+
*
|
|
203
|
+
* @param {string} channel
|
|
204
|
+
* @param {string} exceptConnId
|
|
205
|
+
* @param {Object} envelope
|
|
206
|
+
* @param {string[]} [exceptSessionIds] - fan-out 에서 뺄 세션 목록(BROADCAST payload).
|
|
207
|
+
* @private
|
|
208
|
+
*/
|
|
209
|
+
private _fanOutChannel;
|
|
210
|
+
/**
|
|
211
|
+
* 한 user 의 세션을 가진 bridge 들로 fan-out (DIRECT, ADR-035). origin 제외(L7) — origin 은
|
|
212
|
+
* local 직접 전달하므로 hub 가 되돌리지 않는다(BROADCAST 와 동일 패턴). 직렬화 1회(L5).
|
|
213
|
+
* @param {string} userId
|
|
214
|
+
* @param {Object} envelope
|
|
215
|
+
* @param {string} [exceptConnId] - 제외할 origin bridge connId.
|
|
216
|
+
* @private
|
|
217
|
+
*/
|
|
218
|
+
private _fanOutUser;
|
|
219
|
+
/**
|
|
220
|
+
* connId 로 직렬화된 프레임 송신 (등록된 bridge 한정).
|
|
221
|
+
* @param {string} connId
|
|
222
|
+
* @param {string} serialized - 이미 JSON.stringify 된 envelope.
|
|
223
|
+
* @private
|
|
224
|
+
*/
|
|
225
|
+
private _sendTo;
|
|
226
|
+
/**
|
|
227
|
+
* 단일 envelope 송신 — 직렬화 후 위임 (register_ok/error/heartbeat 등 1:1 송신용).
|
|
228
|
+
* @param {import('ws').WebSocket} socket
|
|
229
|
+
* @param {Object} envelope
|
|
230
|
+
* @private
|
|
231
|
+
*/
|
|
232
|
+
private _safeSend;
|
|
233
|
+
/**
|
|
234
|
+
* 직렬화된 프레임 소켓 송신 — 닫히는 중 등 실패는 비치명적(이유+로그).
|
|
235
|
+
* @param {import('ws').WebSocket} socket
|
|
236
|
+
* @param {string} serialized
|
|
237
|
+
* @private
|
|
238
|
+
*/
|
|
239
|
+
private _sendSerialized;
|
|
240
|
+
/**
|
|
241
|
+
* heartbeat liveness 감시(M3) — heartbeatMs*2 이상 HEARTBEAT 미수신 bridge 를 정리 후 terminate.
|
|
242
|
+
* half-open 연결(TCP 는 살아있으나 상대가 죽음)이 presence 에 영구 잔존하는 것을 막는다.
|
|
243
|
+
* @private
|
|
244
|
+
*/
|
|
245
|
+
private _checkLiveness;
|
|
246
|
+
/**
|
|
247
|
+
* hub 종료 — liveness 타이머 정리 + 모든 bridge 연결 닫고 서버 close.
|
|
248
|
+
*
|
|
249
|
+
* `drain: true` 면 bridge 소켓을 close code **4503**(DRAIN, ADR-098) 로 닫는다 — bridge 가 "다른 hub
|
|
250
|
+
* 인스턴스로 재연결하라" 로 해석한다(LB/mesh 회전). 기본(false)은 1001(GOING_AWAY) — 일반 종료.
|
|
251
|
+
*
|
|
252
|
+
* @param {{ drain?: boolean }} [opts]
|
|
253
|
+
* @returns {Promise<void>}
|
|
254
|
+
*/
|
|
255
|
+
stop({ drain }?: {
|
|
256
|
+
drain?: boolean;
|
|
257
|
+
}): Promise<void>;
|
|
258
|
+
}
|
|
259
|
+
import { WebSocketServer } from 'ws';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} CrudDialect - dialect 모듈의 DML facet(ADR-212) 일부.
|
|
3
|
+
* @property {(name: string) => string} quoteIdent
|
|
4
|
+
* @property {(i: number) => string} placeholder
|
|
5
|
+
*/
|
|
6
|
+
/** 컬럼이 schema 화이트리스트에 있는지 — 없으면 throw. @param {string} col @param {Set<string>} cols @param {string} model @param {string} where */
|
|
7
|
+
export function assertColumn(col: string, cols: Set<string>, model: string, where: string): void;
|
|
8
|
+
/** limit/offset 정수 검증 — 음수/비정수 throw, 통과하면 그 정수 반환. @param {unknown} v @param {string} name @param {string} model @returns {number} */
|
|
9
|
+
export function assertNonNegInt(v: unknown, name: string, model: string): number;
|
|
10
|
+
/**
|
|
11
|
+
* filter → WHERE 조각(접두 'WHERE' 없음) + params + 다음 placeholder 인덱스.
|
|
12
|
+
* 등호 AND + `IN`(배열) + `IS NULL`(null) 만(ADR-212 경계). `undefined` 값은 throw, 빈 배열은 `1=0`(매칭 0).
|
|
13
|
+
*
|
|
14
|
+
* @param {Record<string, any>} filter
|
|
15
|
+
* @param {Set<string>} cols - schema 컬럼 화이트리스트
|
|
16
|
+
* @param {CrudDialect} dialect
|
|
17
|
+
* @param {string} model - 에러 메시지용
|
|
18
|
+
* @param {number} [startIndex=1] - placeholder 시작 인덱스(SET 뒤 WHERE 처럼 이어붙일 때)
|
|
19
|
+
* @returns {{ clause: string, params: any[], nextIndex: number }}
|
|
20
|
+
*/
|
|
21
|
+
export function buildWhere(filter: Record<string, any>, cols: Set<string>, dialect: CrudDialect, model: string, startIndex?: number): {
|
|
22
|
+
clause: string;
|
|
23
|
+
params: any[];
|
|
24
|
+
nextIndex: number;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* orderBy 옵션 → `ORDER BY ...` 조각(없으면 ''). 컬럼 화이트리스트 + dir enum.
|
|
28
|
+
* @param {string | Array<{ column: string, dir?: 'asc'|'desc' }> | undefined} orderBy
|
|
29
|
+
* @param {Set<string>} cols @param {CrudDialect} dialect @param {string} model
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
export function buildOrderBy(orderBy: string | Array<{
|
|
33
|
+
column: string;
|
|
34
|
+
dir?: "asc" | "desc";
|
|
35
|
+
}> | undefined, cols: Set<string>, dialect: CrudDialect, model: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* select 옵션 → 컬럼 목록 조각(`*` 또는 인용 컬럼). 화이트리스트 검증.
|
|
38
|
+
* @param {string[] | undefined} select @param {Set<string>} cols @param {CrudDialect} dialect @param {string} model
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
export function buildSelectList(select: string[] | undefined, cols: Set<string>, dialect: CrudDialect, model: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* - dialect 모듈의 DML facet(ADR-212) 일부.
|
|
44
|
+
*/
|
|
45
|
+
export type CrudDialect = {
|
|
46
|
+
quoteIdent: (name: string) => string;
|
|
47
|
+
placeholder: (i: number) => string;
|
|
48
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MegaModel } from "./mega-model.js";
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
export class MegaModel {
|
|
2
|
+
/**
|
|
3
|
+
* 어댑터 globalKey — `mega.config.js` 의 `services.databases.<key>` 와 정확히 일치 (ADR-061).
|
|
4
|
+
* 서브클래스가 박는다. 비어 있으면 db/withTransaction 접근 시 throw.
|
|
5
|
+
* @type {string}
|
|
6
|
+
*/
|
|
7
|
+
static adapter: string;
|
|
8
|
+
/**
|
|
9
|
+
* 데이터 소스 식별자. SQL 의 table 또는 MongoDB 의 collection 모두 본 필드에 명시 (ADR-081).
|
|
10
|
+
* 어댑터별 native 호출 시 의미는 동일. 비어 있으면 db/withTransaction 접근 시 throw.
|
|
11
|
+
* @type {string}
|
|
12
|
+
*/
|
|
13
|
+
static table: string;
|
|
14
|
+
/**
|
|
15
|
+
* 자동 lookup — `adapter` 키로 어댑터의 driver native handle 반환
|
|
16
|
+
* (pg Pool / MongoClient / mariadb Pool / better-sqlite3 Database, ADR-009).
|
|
17
|
+
*
|
|
18
|
+
* 어댑터가 아직 `connect()` 전이면 어댑터 베이스가 `adapter.not_connected` 를 throw 한다
|
|
19
|
+
* (08-class-specs §3.2 불변식 — native 는 연결 후에만 유효).
|
|
20
|
+
*
|
|
21
|
+
* @returns {any} driver native handle.
|
|
22
|
+
*/
|
|
23
|
+
static get db(): any;
|
|
24
|
+
/**
|
|
25
|
+
* 명시적 트랜잭션 경계 (ADR-010). 어댑터의 `withTransaction` 에 위임한다 — 성공 시 commit,
|
|
26
|
+
* `fn` 이 throw 하면 driver native rollback 후 원본 에러 re-throw. **자동 request-scoped
|
|
27
|
+
* 트랜잭션은 만들지 않는다** (ADR-010).
|
|
28
|
+
*
|
|
29
|
+
* `fn` 이 받는 인자는 **트랜잭션 컨텍스트의 native handle**(예: pg client) 이다. 같은 모델의
|
|
30
|
+
* 다른 메서드(`this.db.query`)는 트랜잭션 **밖** 글로벌 handle 을 쓰므로, 트랜잭션 안에서는
|
|
31
|
+
* `fn` 인자를 명시적으로 써야 격리가 유지된다 (08-class-specs §3.1 — 사용자 책임).
|
|
32
|
+
*
|
|
33
|
+
* nested 트랜잭션 정책(postgres SAVEPOINT / Mongo throw)은 **구체 어댑터 책임**이다.
|
|
34
|
+
* 베이스는 위임만 한다.
|
|
35
|
+
*
|
|
36
|
+
* `fn` 인자 수는 어댑터별로 다르다 — SQL 어댑터는 `(client)` 1개, Mongo 어댑터는 `(db, session)`
|
|
37
|
+
* 2개를 넘긴다(ADR-108). 따라서 가변 인자로 타입을 둔다(어댑터가 인자 형태의 정본).
|
|
38
|
+
*
|
|
39
|
+
* `opts.isolation`(ADR-190) — SQL 격리수준 옵트인. driver 별 지원·제약은 어댑터가 정본
|
|
40
|
+
* (postgres/maria=top-level 만, sqlite='serializable' 만, mongodb=미지원 throw).
|
|
41
|
+
*
|
|
42
|
+
* @template T
|
|
43
|
+
* @param {(...args: any[]) => Promise<T>} fn - 트랜잭션 컨텍스트 native handle(들)을 받는 콜백.
|
|
44
|
+
* @param {{ isolation?: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable' }} [opts] - 트랜잭션 옵션(ADR-190).
|
|
45
|
+
* @returns {Promise<T>}
|
|
46
|
+
*/
|
|
47
|
+
static withTransaction<T>(fn: (...args: any[]) => Promise<T>, opts?: {
|
|
48
|
+
isolation?: "read uncommitted" | "read committed" | "repeatable read" | "serializable";
|
|
49
|
+
}): Promise<T>;
|
|
50
|
+
/**
|
|
51
|
+
* **계측된 쿼리** — `this.db.query`(native handle 직접 호출, 트레이싱 미통과)와 달리 어댑터의
|
|
52
|
+
* `query` 도메인 메서드에 위임해 자동 span(`<driver>.query`)·메트릭·stats 에 잡힌다(ADR-138).
|
|
53
|
+
* 진행 중 트랜잭션이 있으면 어댑터가 같은 connection 으로 실행하므로, `withTransaction(async (tx) => …)`
|
|
54
|
+
* 콜백 안에서 `Model.query(sql, params)` 를 부르면 격리가 유지되고 span 도 트랜잭션 span 의 자식이 된다
|
|
55
|
+
* (native `tx.query`는 계측 미통과 escape hatch). SQL 어댑터(postgres/maria/sqlite) 전용 — Document
|
|
56
|
+
* DB(mongo)는 `adapter.not_implemented`.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} sql - 파라미터화된 SQL(placeholder 보존 — 값 인터폴레이션 금지).
|
|
59
|
+
* @param {any[]} [params] - placeholder 바인딩 값.
|
|
60
|
+
* @returns {Promise<any>} driver native 쿼리 결과(어댑터별 형태 — ADR-009).
|
|
61
|
+
*/
|
|
62
|
+
static query(sql: string, params?: any[]): Promise<any>;
|
|
63
|
+
/** 단건 조회(없으면 null). filter 는 등호 AND + 배열 IN + null IS NULL. @param {Record<string, any>} filter @param {{ select?: string[] }} [opts] @returns {Promise<any|null>} */
|
|
64
|
+
static findOne(filter: Record<string, any>, opts?: {
|
|
65
|
+
select?: string[];
|
|
66
|
+
}): Promise<any | null>;
|
|
67
|
+
/** PK 단건 조회(단일 PK 필요, 없으면 model.no_primary_key). @param {any} id @param {{ select?: string[] }} [opts] @returns {Promise<any|null>} */
|
|
68
|
+
static findById(id: any, opts?: {
|
|
69
|
+
select?: string[];
|
|
70
|
+
}): Promise<any | null>;
|
|
71
|
+
/** 다건 조회(기본 limit 없음 — 명시할 때만, ADR-212 #2). @param {Record<string, any>} [filter] @param {{ select?: string[], orderBy?: string | Array<{ column: string, dir?: 'asc'|'desc' }>, limit?: number, offset?: number }} [opts] @returns {Promise<any[]>} */
|
|
72
|
+
static findMany(filter?: Record<string, any>, opts?: {
|
|
73
|
+
select?: string[];
|
|
74
|
+
orderBy?: string | Array<{
|
|
75
|
+
column: string;
|
|
76
|
+
dir?: "asc" | "desc";
|
|
77
|
+
}>;
|
|
78
|
+
limit?: number;
|
|
79
|
+
offset?: number;
|
|
80
|
+
}): Promise<any[]>;
|
|
81
|
+
/** 조건 매칭 행 수. @param {Record<string, any>} [filter] @returns {Promise<number>} */
|
|
82
|
+
static count(filter?: Record<string, any>): Promise<number>;
|
|
83
|
+
/** 조건 매칭 행 존재 여부. @param {Record<string, any>} filter @returns {Promise<boolean>} */
|
|
84
|
+
static exists(filter: Record<string, any>): Promise<boolean>;
|
|
85
|
+
/** 페이지 조회. total 은 `{ withTotal: true }` 일 때만 추가 count(ADR-212 #4). @param {Record<string, any>} filter @param {{ select?: string[], orderBy?: any, limit: number, offset?: number, withTotal?: boolean }} opts @returns {Promise<{ rows: any[], limit: number, offset: number, total?: number }>} */
|
|
86
|
+
static paginate(filter: Record<string, any>, opts: {
|
|
87
|
+
select?: string[];
|
|
88
|
+
orderBy?: any;
|
|
89
|
+
limit: number;
|
|
90
|
+
offset?: number;
|
|
91
|
+
withTotal?: boolean;
|
|
92
|
+
}): Promise<{
|
|
93
|
+
rows: any[];
|
|
94
|
+
limit: number;
|
|
95
|
+
offset: number;
|
|
96
|
+
total?: number;
|
|
97
|
+
}>;
|
|
98
|
+
/** 단건 삽입. 기본 새 id 반환, `{ returning: true }` 면 레코드(ADR-212 #1). @param {Record<string, any>} data @param {{ returning?: boolean }} [opts] @returns {Promise<any>} */
|
|
99
|
+
static insertOne(data: Record<string, any>, opts?: {
|
|
100
|
+
returning?: boolean;
|
|
101
|
+
}): Promise<any>;
|
|
102
|
+
/** 다건 삽입. 기본 `{ count }`, `{ returning: true }` 면 레코드 배열(maria 미지원→throw). @param {Record<string, any>[]} rows @param {{ returning?: boolean }} [opts] @returns {Promise<{ count: number } | any[]>} */
|
|
103
|
+
static insertMany(rows: Record<string, any>[], opts?: {
|
|
104
|
+
returning?: boolean;
|
|
105
|
+
}): Promise<{
|
|
106
|
+
count: number;
|
|
107
|
+
} | any[]>;
|
|
108
|
+
/** 정확히 한 행 갱신(>1 매칭→롤백 후 model.multiple_matches, ADR-212 #3). 변경 행 수 반환. @param {Record<string, any>} filter @param {Record<string, any>} patch @returns {Promise<number>} */
|
|
109
|
+
static updateOne(filter: Record<string, any>, patch: Record<string, any>): Promise<number>;
|
|
110
|
+
/** 다건 갱신. 빈 filter 는 차단 — 전체 갱신은 `{ all: true }`. @param {Record<string, any>} filter @param {Record<string, any>} patch @param {{ all?: boolean }} [opts] @returns {Promise<number>} */
|
|
111
|
+
static updateMany(filter: Record<string, any>, patch: Record<string, any>, opts?: {
|
|
112
|
+
all?: boolean;
|
|
113
|
+
}): Promise<number>;
|
|
114
|
+
/** 정확히 한 행 삭제(>1 매칭→롤백 후 model.multiple_matches). 삭제 행 수 반환. @param {Record<string, any>} filter @returns {Promise<number>} */
|
|
115
|
+
static deleteOne(filter: Record<string, any>): Promise<number>;
|
|
116
|
+
/** 다건 삭제. 빈 filter 는 차단 — 전체 삭제는 `{ all: true }`. @param {Record<string, any>} filter @param {{ all?: boolean }} [opts] @returns {Promise<number>} */
|
|
117
|
+
static deleteMany(filter: Record<string, any>, opts?: {
|
|
118
|
+
all?: boolean;
|
|
119
|
+
}): Promise<number>;
|
|
120
|
+
/** upsert(INSERT … ON CONFLICT/DUPLICATE KEY UPDATE). `opts.conflict` 는 PK/unique 컬럼 필수. @param {Record<string, any>} data @param {{ conflict: string[], update?: string[], returning?: boolean }} opts @returns {Promise<any>} */
|
|
121
|
+
static upsert(data: Record<string, any>, opts: {
|
|
122
|
+
conflict: string[];
|
|
123
|
+
update?: string[];
|
|
124
|
+
returning?: boolean;
|
|
125
|
+
}): Promise<any>;
|
|
126
|
+
/**
|
|
127
|
+
* `adapter`/`table` 정합성을 검증하고 글로벌 매니저에서 어댑터(MegaDbAdapter)를 잡아 반환한다.
|
|
128
|
+
*
|
|
129
|
+
* 정본은 "부팅 시 검증"(08-class-specs §3.1 라이프사이클, ADR-067)을 말하지만, 중앙 부팅
|
|
130
|
+
* 오케스트레이터(`mega serve` CLI)가 아직 미배선이라(ADR-102 미결 항목) **접근 시점 lazy 검증**
|
|
131
|
+
* 으로 둔다 — `ctx.db(alias)` 가 미등록 별명을 즉시 throw 하는 것과 같은 패턴(ADR-102).
|
|
132
|
+
* 오케스트레이터가 생기면 부팅 시 일괄 검증을 추가한다(ADR-104).
|
|
133
|
+
*
|
|
134
|
+
* @protected
|
|
135
|
+
* @returns {import('../adapters/mega-db-adapter.js').MegaDbAdapter}
|
|
136
|
+
*/
|
|
137
|
+
protected static _resolveAdapter(): import("../adapters/mega-db-adapter.js").MegaDbAdapter;
|
|
138
|
+
}
|