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
|
@@ -13,6 +13,21 @@
|
|
|
13
13
|
<form method="post" action="/register" novalidate>
|
|
14
14
|
<input type="hidden" name="_csrf" value="<%= csrfToken %>" />
|
|
15
15
|
|
|
16
|
+
<div class="mb-3">
|
|
17
|
+
<label for="username" class="form-label"><%= t('field_username', '아이디') %></label>
|
|
18
|
+
<input
|
|
19
|
+
type="text"
|
|
20
|
+
class="form-control <%= invalid && invalid.username ? 'is-invalid' : '' %>"
|
|
21
|
+
id="username"
|
|
22
|
+
name="username"
|
|
23
|
+
value="<%= values && values.username ? values.username : '' %>"
|
|
24
|
+
placeholder="<%= t('field_username_ph', '예: hong123') %>"
|
|
25
|
+
autocomplete="username"
|
|
26
|
+
required
|
|
27
|
+
/>
|
|
28
|
+
<div class="invalid-feedback"><%= t('field_username_required', '아이디를 입력하세요.') %></div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
16
31
|
<div class="mb-3">
|
|
17
32
|
<label for="name" class="form-label"><%= t('field_name', '이름') %></label>
|
|
18
33
|
<input
|
|
@@ -29,18 +44,44 @@
|
|
|
29
44
|
</div>
|
|
30
45
|
|
|
31
46
|
<div class="mb-3">
|
|
32
|
-
<label for="
|
|
47
|
+
<label for="nickname" class="form-label"><%= t('field_nickname', '별명') %></label>
|
|
48
|
+
<input
|
|
49
|
+
type="text"
|
|
50
|
+
class="form-control <%= invalid && invalid.nickname ? 'is-invalid' : '' %>"
|
|
51
|
+
id="nickname"
|
|
52
|
+
name="nickname"
|
|
53
|
+
value="<%= values && values.nickname ? values.nickname : '' %>"
|
|
54
|
+
placeholder="<%= t('field_nickname_ph', '예: 길동이') %>"
|
|
55
|
+
autocomplete="nickname"
|
|
56
|
+
required
|
|
57
|
+
/>
|
|
58
|
+
<div class="invalid-feedback"><%= t('field_nickname_required', '별명을 입력하세요.') %></div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="mb-3">
|
|
62
|
+
<label for="email" class="form-label"><%= t('field_email', '이메일') %> <span class="text-body-secondary small">(<%= t('optional', '선택') %>)</span></label>
|
|
33
63
|
<input
|
|
34
64
|
type="email"
|
|
35
|
-
class="form-control
|
|
65
|
+
class="form-control"
|
|
36
66
|
id="email"
|
|
37
67
|
name="email"
|
|
38
68
|
value="<%= values && values.email ? values.email : '' %>"
|
|
39
69
|
placeholder="<%= t('field_email_ph', '예: hong@example.com') %>"
|
|
40
|
-
autocomplete="
|
|
41
|
-
|
|
70
|
+
autocomplete="email"
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div class="mb-3">
|
|
75
|
+
<label for="phone_number" class="form-label"><%= t('field_phone', '전화번호') %> <span class="text-body-secondary small">(<%= t('optional', '선택') %>)</span></label>
|
|
76
|
+
<input
|
|
77
|
+
type="tel"
|
|
78
|
+
class="form-control"
|
|
79
|
+
id="phone_number"
|
|
80
|
+
name="phone_number"
|
|
81
|
+
value="<%= values && values.phone_number ? values.phone_number : '' %>"
|
|
82
|
+
placeholder="<%= t('field_phone_ph', '예: 010-1234-5678') %>"
|
|
83
|
+
autocomplete="tel"
|
|
42
84
|
/>
|
|
43
|
-
<div class="invalid-feedback"><%= t('field_email_required', '올바른 이메일을 입력하세요.') %></div>
|
|
44
85
|
</div>
|
|
45
86
|
|
|
46
87
|
<div class="mb-4">
|
|
@@ -11,6 +11,19 @@
|
|
|
11
11
|
<form method="post" action="/admin/users/<%= values.id %>" novalidate>
|
|
12
12
|
<input type="hidden" name="_csrf" value="<%= csrfToken %>" />
|
|
13
13
|
|
|
14
|
+
<div class="mb-3">
|
|
15
|
+
<label for="username" class="form-label"><%= t('field_username', '아이디') %></label>
|
|
16
|
+
<input
|
|
17
|
+
type="text"
|
|
18
|
+
class="form-control"
|
|
19
|
+
id="username"
|
|
20
|
+
value="<%= values && values.username ? values.username : '' %>"
|
|
21
|
+
readonly
|
|
22
|
+
disabled
|
|
23
|
+
/>
|
|
24
|
+
<div class="form-text"><%= t('field_username_readonly', '아이디는 변경할 수 없습니다.') %></div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
14
27
|
<div class="mb-3">
|
|
15
28
|
<label for="name" class="form-label"><%= t('field_name', '이름') %></label>
|
|
16
29
|
<input
|
|
@@ -25,18 +38,42 @@
|
|
|
25
38
|
<div class="invalid-feedback"><%= t('field_name_required', '이름을 입력하세요.') %></div>
|
|
26
39
|
</div>
|
|
27
40
|
|
|
28
|
-
<div class="mb-
|
|
29
|
-
<label for="
|
|
41
|
+
<div class="mb-3">
|
|
42
|
+
<label for="nickname" class="form-label"><%= t('field_nickname', '별명') %></label>
|
|
43
|
+
<input
|
|
44
|
+
type="text"
|
|
45
|
+
class="form-control <%= invalid && invalid.nickname ? 'is-invalid' : '' %>"
|
|
46
|
+
id="nickname"
|
|
47
|
+
name="nickname"
|
|
48
|
+
value="<%= values && values.nickname ? values.nickname : '' %>"
|
|
49
|
+
placeholder="<%= t('field_nickname_ph', '예: 길동이') %>"
|
|
50
|
+
required
|
|
51
|
+
/>
|
|
52
|
+
<div class="invalid-feedback"><%= t('field_nickname_required', '별명을 입력하세요.') %></div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div class="mb-3">
|
|
56
|
+
<label for="email" class="form-label"><%= t('field_email', '이메일') %> <span class="text-body-secondary small">(<%= t('optional', '선택') %>)</span></label>
|
|
30
57
|
<input
|
|
31
58
|
type="email"
|
|
32
|
-
class="form-control
|
|
59
|
+
class="form-control"
|
|
33
60
|
id="email"
|
|
34
61
|
name="email"
|
|
35
62
|
value="<%= values && values.email ? values.email : '' %>"
|
|
36
63
|
placeholder="<%= t('field_email_ph', '예: hong@example.com') %>"
|
|
37
|
-
required
|
|
38
64
|
/>
|
|
39
|
-
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div class="mb-4">
|
|
68
|
+
<label for="phone_number" class="form-label"><%= t('field_phone', '전화번호') %> <span class="text-body-secondary small">(<%= t('optional', '선택') %>)</span></label>
|
|
69
|
+
<input
|
|
70
|
+
type="tel"
|
|
71
|
+
class="form-control"
|
|
72
|
+
id="phone_number"
|
|
73
|
+
name="phone_number"
|
|
74
|
+
value="<%= values && values.phone_number ? values.phone_number : '' %>"
|
|
75
|
+
placeholder="<%= t('field_phone_ph', '예: 010-1234-5678') %>"
|
|
76
|
+
/>
|
|
40
77
|
</div>
|
|
41
78
|
|
|
42
79
|
<div class="d-flex gap-2">
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
<thead>
|
|
25
25
|
<tr>
|
|
26
26
|
<th scope="col" class="text-end" style="width: 5rem"><%= t('col_id', 'ID') %></th>
|
|
27
|
+
<th scope="col"><%= t('col_username', '아이디') %></th>
|
|
27
28
|
<th scope="col"><%= t('col_name', '이름') %></th>
|
|
29
|
+
<th scope="col"><%= t('col_nickname', '별명') %></th>
|
|
28
30
|
<th scope="col"><%= t('col_email', '이메일') %></th>
|
|
29
31
|
<th scope="col"><%= t('col_created', '생성일') %></th>
|
|
30
32
|
<th scope="col" class="text-end"><%= t('col_actions', '관리') %></th>
|
|
@@ -34,8 +36,10 @@
|
|
|
34
36
|
<% users.forEach(function (u) { %>
|
|
35
37
|
<tr>
|
|
36
38
|
<td class="text-end text-body-secondary"><%= u.id %></td>
|
|
37
|
-
<td class="fw-medium"><%= u.
|
|
38
|
-
<td><%= u.
|
|
39
|
+
<td class="fw-medium"><%= u.username %></td>
|
|
40
|
+
<td><%= u.name %></td>
|
|
41
|
+
<td><%= u.nickname %></td>
|
|
42
|
+
<td class="text-body-secondary"><%= u.email || '-' %></td>
|
|
39
43
|
<td class="text-body-secondary small"><%= u.created_at %></td>
|
|
40
44
|
<td class="text-end text-nowrap">
|
|
41
45
|
<a href="/admin/users/<%= u.id %>/edit" class="btn btn-sm btn-outline-secondary"><%= t('action_edit', '수정') %></a>
|
|
@@ -11,6 +11,21 @@
|
|
|
11
11
|
<form method="post" action="/admin/users" novalidate>
|
|
12
12
|
<input type="hidden" name="_csrf" value="<%= csrfToken %>" />
|
|
13
13
|
|
|
14
|
+
<div class="mb-3">
|
|
15
|
+
<label for="username" class="form-label"><%= t('field_username', '아이디') %></label>
|
|
16
|
+
<input
|
|
17
|
+
type="text"
|
|
18
|
+
class="form-control <%= invalid && invalid.username ? 'is-invalid' : '' %>"
|
|
19
|
+
id="username"
|
|
20
|
+
name="username"
|
|
21
|
+
value="<%= values && values.username ? values.username : '' %>"
|
|
22
|
+
placeholder="<%= t('field_username_ph', '예: hong123') %>"
|
|
23
|
+
autocomplete="off"
|
|
24
|
+
required
|
|
25
|
+
/>
|
|
26
|
+
<div class="invalid-feedback"><%= t('field_username_required', '아이디를 입력하세요.') %></div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
14
29
|
<div class="mb-3">
|
|
15
30
|
<label for="name" class="form-label"><%= t('field_name', '이름') %></label>
|
|
16
31
|
<input
|
|
@@ -25,18 +40,55 @@
|
|
|
25
40
|
<div class="invalid-feedback"><%= t('field_name_required', '이름을 입력하세요.') %></div>
|
|
26
41
|
</div>
|
|
27
42
|
|
|
28
|
-
<div class="mb-
|
|
29
|
-
<label for="
|
|
43
|
+
<div class="mb-3">
|
|
44
|
+
<label for="nickname" class="form-label"><%= t('field_nickname', '별명') %></label>
|
|
45
|
+
<input
|
|
46
|
+
type="text"
|
|
47
|
+
class="form-control <%= invalid && invalid.nickname ? 'is-invalid' : '' %>"
|
|
48
|
+
id="nickname"
|
|
49
|
+
name="nickname"
|
|
50
|
+
value="<%= values && values.nickname ? values.nickname : '' %>"
|
|
51
|
+
placeholder="<%= t('field_nickname_ph', '예: 길동이') %>"
|
|
52
|
+
required
|
|
53
|
+
/>
|
|
54
|
+
<div class="invalid-feedback"><%= t('field_nickname_required', '별명을 입력하세요.') %></div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div class="mb-3">
|
|
58
|
+
<label for="email" class="form-label"><%= t('field_email', '이메일') %> <span class="text-body-secondary small">(<%= t('optional', '선택') %>)</span></label>
|
|
30
59
|
<input
|
|
31
60
|
type="email"
|
|
32
|
-
class="form-control
|
|
61
|
+
class="form-control"
|
|
33
62
|
id="email"
|
|
34
63
|
name="email"
|
|
35
64
|
value="<%= values && values.email ? values.email : '' %>"
|
|
36
65
|
placeholder="<%= t('field_email_ph', '예: hong@example.com') %>"
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div class="mb-3">
|
|
70
|
+
<label for="phone_number" class="form-label"><%= t('field_phone', '전화번호') %> <span class="text-body-secondary small">(<%= t('optional', '선택') %>)</span></label>
|
|
71
|
+
<input
|
|
72
|
+
type="tel"
|
|
73
|
+
class="form-control"
|
|
74
|
+
id="phone_number"
|
|
75
|
+
name="phone_number"
|
|
76
|
+
value="<%= values && values.phone_number ? values.phone_number : '' %>"
|
|
77
|
+
placeholder="<%= t('field_phone_ph', '예: 010-1234-5678') %>"
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div class="mb-4">
|
|
82
|
+
<label for="password" class="form-label"><%= t('field_password', '비밀번호') %></label>
|
|
83
|
+
<input
|
|
84
|
+
type="password"
|
|
85
|
+
class="form-control <%= invalid && invalid.password ? 'is-invalid' : '' %>"
|
|
86
|
+
id="password"
|
|
87
|
+
name="password"
|
|
88
|
+
autocomplete="new-password"
|
|
37
89
|
required
|
|
38
90
|
/>
|
|
39
|
-
<div class="invalid-
|
|
91
|
+
<div class="form-text <%= invalid && invalid.password ? 'text-danger' : '' %>"><%= t('field_password_hint', '최소 8자 이상 입력하세요.') %></div>
|
|
40
92
|
</div>
|
|
41
93
|
|
|
42
94
|
<div class="d-flex gap-2">
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# 🏗️ Log Table Partitioning Architecture
|
|
2
|
+
|
|
3
|
+
## 📦 LogPartitionSchedule
|
|
4
|
+
- **Role**: 로그 테이블 파티션 자동 관리 스케줄러
|
|
5
|
+
- **Responsibilities**
|
|
6
|
+
- 매일 특정 시각(예: 새벽 2시)에 실행
|
|
7
|
+
- 미래(다음 달, 다다음 달) 파티션 테이블 자동 생성
|
|
8
|
+
- 과거(1개월 이전) 오래된 파티션 테이블 자동 분리(Detach) 및 삭제(Drop)
|
|
9
|
+
- **Concurrency Control**
|
|
10
|
+
- `static lock = { lock: 'log-partition', ttl: 60000 }` (Redlock을 활용하여 스케줄러 인스턴스 간 중복 실행 방지)
|
|
11
|
+
|
|
12
|
+
## 📦 Database Partitioned Tables
|
|
13
|
+
- **Role**: 시간 기반 범위 파티셔닝(Range Partitioning by `created_at`)을 통한 로그 데이터 관리
|
|
14
|
+
- **Properties**
|
|
15
|
+
- `action_logs` (PARTITION BY RANGE (created_at))
|
|
16
|
+
- `wallet_logs` (PARTITION BY RANGE (created_at))
|
|
17
|
+
- `detail_logs` (PARTITION BY RANGE (created_at))
|
|
18
|
+
- **Responsibilities**
|
|
19
|
+
- 대량 로그 유입 시 쓰기 성능 향상 및 Vacuum 오버헤드 완화
|
|
20
|
+
- 보관 주기(1개월) 만료 데이터의 물리적 삭제 성능 최적화 (DROP PARTITION)
|
|
21
|
+
- **Constraints**
|
|
22
|
+
- Primary Key에 파티션 키 `created_at` 포함 필수: `PRIMARY KEY (id, created_at)`
|
|
23
|
+
- Unique Index에 파티션 키 `created_at` 포함 필수: `UNIQUE (uuid, created_at)` 등
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* App-only 키(databases 별명 등)는 apps/<name>/app.config.js 로.
|
|
5
5
|
*/
|
|
6
6
|
import { CronCounterSchedule } from './apps/main/schedules/cron-counter-schedule.js'
|
|
7
|
+
import { LogPartitionSchedule } from './apps/main/schedules/log-partition-schedule.js'
|
|
7
8
|
import { EmailJob } from './apps/main/jobs/email-job.js'
|
|
8
9
|
import { HashWorker } from './apps/main/workers/hash-worker.js'
|
|
9
10
|
|
|
@@ -135,7 +136,7 @@ export default {
|
|
|
135
136
|
// },
|
|
136
137
|
|
|
137
138
|
// 정기 작업(ADR-028/118) — `mega scheduler` 프로세스가 등록·실행한다(config.schedules, ADR-123).
|
|
138
|
-
schedules: [CronCounterSchedule],
|
|
139
|
+
schedules: [CronCounterSchedule, LogPartitionSchedule],
|
|
139
140
|
|
|
140
141
|
// 영속 잡(ADR-028/119) — `mega worker` 프로세스(ecosystem instances:2)가 소비한다(config.jobs, ADR-123).
|
|
141
142
|
jobs: [EmailJob],
|
|
@@ -168,10 +169,17 @@ export default {
|
|
|
168
169
|
// (예시·미사용) liveness/readiness 경로 — 기본 '/health' · '/health/ready'.
|
|
169
170
|
// paths: { live: '/health', ready: '/health/ready' },
|
|
170
171
|
// (예시·미사용) 메트릭 resource service.name — 미지정 시 server.serviceName → MEGA_OTEL_SERVICE_NAME 폴백.
|
|
171
|
-
|
|
172
|
+
serviceName: 'sample-crud',
|
|
172
173
|
},
|
|
173
174
|
|
|
174
175
|
// OpenTelemetry 분산 트레이싱 — **config 블록이 아니라 .env 의 MEGA_OTEL_\*** 로 설정한다
|
|
175
176
|
// (MegaTracing.fromEnv, boot.js). MEGA_OTEL_ENABLED='true' + MEGA_OTEL_SERVICE_NAME 필수.
|
|
176
177
|
// (`tracing` 키는 스키마엔 있으나 현재 부팅이 소비하지 않음 — 죽은 설정 회피 위해 블록을 두지 않음.)
|
|
178
|
+
|
|
179
|
+
// dev watch(`mega start --watch`) ignore — .env 의 WATCH_IGNORE(콤마 구분 glob)가 정본이다
|
|
180
|
+
// (ADR-220, 숨은 코드 디폴트 없음). 폴더명을 바꾼 프로젝트는 .env 의 그 줄만 고치면 된다.
|
|
181
|
+
// 미설정이면 ignore 0 — 모든 변경이 재시작 대상(기동 시 경고 1줄).
|
|
182
|
+
watch: {
|
|
183
|
+
ignore: (process.env.WATCH_IGNORE ?? '').split(',').map((p) => p.trim()).filter(Boolean),
|
|
184
|
+
},
|
|
177
185
|
}
|
package/sample/crud/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"node": ">=20"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"dev": "mega start --watch",
|
|
10
|
+
"dev": "NODE_ENV=development mega start --watch",
|
|
11
11
|
"start": "NODE_ENV=production mega start",
|
|
12
12
|
"migrate": "mega migrate",
|
|
13
13
|
"migrate:down": "mega migrate:down",
|
|
@@ -23,6 +23,6 @@
|
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"concurrently": "^9.0.0",
|
|
26
|
-
"vitest": "^4.
|
|
26
|
+
"vitest": "^4.1.8"
|
|
27
27
|
}
|
|
28
|
-
}
|
|
28
|
+
}
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
# sample/crud — WS Hub 서버 기동 스크립트 (ADR-032/176)
|
|
4
4
|
#
|
|
5
5
|
# 별도 hub 프로세스(`mega-ws-hub` 바이너리, mega-framework bin)를 localhost:3100 에 띄운다.
|
|
6
|
-
# 앱(`
|
|
6
|
+
# 앱(`npm run dev` = `mega start`)이 app.config 의 `bridgeHub` 로 이 허브에 **자동 연결**한다(ADR-176).
|
|
7
7
|
#
|
|
8
8
|
# ⚠️ `mega-ws-hub` 는 `mega start` 와 달리 `.env` 를 자동 로드하지 않는다(직접 process.env 만 읽음,
|
|
9
|
-
# src/
|
|
10
|
-
# 읽는 env(src/
|
|
9
|
+
# src/lib/ws-hub.js). 그래서 Node 20.6+ 의 `--env-file` 로 .env 를 주입한다.
|
|
10
|
+
# 읽는 env(src/lib/ws-hub.js runWsHubCli): MEGA_WSHUB_TOKENS(필수, 콤마구분) /
|
|
11
11
|
# MEGA_WSHUB_PORT(기본 3100) / MEGA_WSHUB_HOST(기본 0.0.0.0) / MEGA_WSHUB_HEARTBEAT_MS.
|
|
12
12
|
#
|
|
13
13
|
# 사용:
|
|
@@ -22,15 +22,29 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
|
22
22
|
cd "$ROOT"
|
|
23
23
|
|
|
24
24
|
ENV_FILE=".env"
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
# mega-ws-hub 바이너리 탐색 — 두 설치 형상을 모두 지원한다:
|
|
27
|
+
# ① 독립 프로젝트(mega new 스캐폴드): 자기 node_modules 에 설치됨.
|
|
28
|
+
# ② 모노레포 workspaces(ADR-197): 의존성이 레포 루트로 hoist 되어 sample/crud 에는 node_modules 가
|
|
29
|
+
# 없다(정상). 루트의 mega-framework self-link 를 따라간다.
|
|
30
|
+
BIN=""
|
|
31
|
+
for candidate in \
|
|
32
|
+
"node_modules/mega-framework/bin/mega-ws-hub.js" \
|
|
33
|
+
"../../node_modules/mega-framework/bin/mega-ws-hub.js"; do
|
|
34
|
+
if [ -f "$candidate" ]; then
|
|
35
|
+
BIN="$candidate"
|
|
36
|
+
break
|
|
37
|
+
fi
|
|
38
|
+
done
|
|
26
39
|
|
|
27
40
|
# 사전 점검 — 누락 시 silent 진행 금지, 이유를 명확히 알린다(P4/P7).
|
|
28
41
|
[ -f "$ENV_FILE" ] || {
|
|
29
42
|
echo "✗ $ROOT/$ENV_FILE 가 없습니다 — MEGA_WSHUB_TOKENS 등 허브 설정이 필요합니다." >&2
|
|
30
43
|
exit 1
|
|
31
44
|
}
|
|
32
|
-
[ -
|
|
33
|
-
echo "✗
|
|
45
|
+
[ -n "$BIN" ] || {
|
|
46
|
+
echo "✗ mega-ws-hub.js 를 찾지 못했습니다(로컬/루트 node_modules 모두) — 'npm install' 로 mega-framework 를 설치하세요." >&2
|
|
47
|
+
echo " (모노레포에서는 레포 루트에서 실행해야 합니다. yarn 은 미지원 — npm 사용.)" >&2
|
|
34
48
|
exit 1
|
|
35
49
|
}
|
|
36
50
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"4.1.8","results":[[":test/apps/main/index.test.js",{"duration":1.6905000000000001,"failed":false}]]}
|
|
@@ -127,7 +127,8 @@ export function buildFromGlobalConfig(globalConfig, { registerShutdownHook = tru
|
|
|
127
127
|
if (registerShutdownHook) {
|
|
128
128
|
// 재빌드 안전 — 항상 1개만 유지(MegaApp 의 hublink hook 패턴과 동일).
|
|
129
129
|
MegaShutdown.unregister(SHUTDOWN_HOOK)
|
|
130
|
-
|
|
130
|
+
// 'adapters' stage — 서버/잡/앱 정리(어댑터를 쓰는 정리)가 모두 끝난 뒤 disconnect 된다.
|
|
131
|
+
MegaShutdown.register(SHUTDOWN_HOOK, async () => disconnectAll(), { stage: 'adapters' })
|
|
131
132
|
}
|
|
132
133
|
}
|
|
133
134
|
|
|
@@ -47,6 +47,36 @@ function invalidOption(message, details) {
|
|
|
47
47
|
return new MegaValidationError('adapter.invalid_option', message, { details })
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* `withTransaction(fn, opts)` 의 isolation 값 → 표준 SQL 조각 (ADR-190).
|
|
52
|
+
* 화이트리스트 매핑이라 SQL 인젝션이 구조적으로 불가능하다(임의 문자열은 throw).
|
|
53
|
+
* @type {Record<string, string>}
|
|
54
|
+
*/
|
|
55
|
+
const TX_ISOLATION_SQL = {
|
|
56
|
+
'read uncommitted': 'READ UNCOMMITTED',
|
|
57
|
+
'read committed': 'READ COMMITTED',
|
|
58
|
+
'repeatable read': 'REPEATABLE READ',
|
|
59
|
+
serializable: 'SERIALIZABLE',
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 트랜잭션 격리수준 옵션 검증 + SQL 조각 변환 (ADR-190). `undefined` 는 driver 디폴트 위임(undefined 반환).
|
|
64
|
+
* @param {unknown} isolation - `withTransaction` 의 `opts.isolation`.
|
|
65
|
+
* @param {string} driver - 에러 메시지용 driver 이름.
|
|
66
|
+
* @returns {string | undefined} `SET TRANSACTION ISOLATION LEVEL <조각>` 에 쓸 SQL 조각.
|
|
67
|
+
* @throws {MegaValidationError} `adapter.invalid_option` - 화이트리스트 외 값.
|
|
68
|
+
*/
|
|
69
|
+
export function resolveTxIsolation(isolation, driver) {
|
|
70
|
+
if (isolation === undefined) return undefined
|
|
71
|
+
if (typeof isolation !== 'string' || !Object.prototype.hasOwnProperty.call(TX_ISOLATION_SQL, isolation)) {
|
|
72
|
+
throw invalidOption(
|
|
73
|
+
`${driver}: withTransaction "isolation" must be one of 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable'. Got ${JSON.stringify(isolation)}.`,
|
|
74
|
+
{ driver, option: 'isolation', value: isolation },
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
return TX_ISOLATION_SQL[isolation]
|
|
78
|
+
}
|
|
79
|
+
|
|
50
80
|
/**
|
|
51
81
|
* 양의 정수(> 0) 검증 (undefined 통과). 위반 시 `adapter.invalid_option` throw.
|
|
52
82
|
* @param {string} name @param {unknown} value @param {Record<string, unknown>} [extra] @returns {void}
|
|
@@ -135,6 +165,9 @@ export function resolveConnection(config, { driver, dbKey = 'database', dbConfli
|
|
|
135
165
|
return out
|
|
136
166
|
}
|
|
137
167
|
|
|
168
|
+
/** pool acquire 대기 한도 프레임워크 디폴트(ms) — 명시 0 으로 드라이버 무한 대기 옵트인(ADR-216). */
|
|
169
|
+
export const DEFAULT_ACQUIRE_TIMEOUT_MS = 10_000
|
|
170
|
+
|
|
138
171
|
/**
|
|
139
172
|
* pg 풀 매핑 — 값이 null 이면 미지원(throw), `{ key, divideBy? }` 면 키 이름 변경(+단위 변환).
|
|
140
173
|
* @type {Record<string, { key: string, divideBy?: number } | null>}
|
|
@@ -186,11 +219,11 @@ export const MONGO_POOL_SPEC = {
|
|
|
186
219
|
* @returns {Record<string, number>} 드라이버 풀 옵션 객체(빈 객체 가능).
|
|
187
220
|
*/
|
|
188
221
|
export function normalizePool(pool, spec, driver) {
|
|
189
|
-
if (pool === undefined) return {}
|
|
190
|
-
assertPlainObject('pool', pool, { driver })
|
|
191
222
|
/** @type {Record<string, number>} */
|
|
192
223
|
const out = {}
|
|
193
|
-
|
|
224
|
+
if (pool !== undefined) assertPlainObject('pool', pool, { driver })
|
|
225
|
+
const entries = pool === undefined ? [] : Object.entries(/** @type {Record<string, unknown>} */ (pool))
|
|
226
|
+
for (const [key, value] of entries) {
|
|
194
227
|
if (value === undefined) continue
|
|
195
228
|
const map = spec[key]
|
|
196
229
|
if (map === undefined) {
|
|
@@ -204,5 +237,13 @@ export function normalizePool(pool, spec, driver) {
|
|
|
204
237
|
const num = /** @type {number} */ (value)
|
|
205
238
|
out[map.key] = map.divideBy ? Math.floor(num / map.divideBy) : num
|
|
206
239
|
}
|
|
240
|
+
// 프레임워크 디폴트(ADR-216 G2 H-1): acquire 무한 대기(pg connectionTimeoutMillis=0 ·
|
|
241
|
+
// mongo waitQueueTimeoutMS=0 드라이버 디폴트)는 연결 leak 1건을 전체 서비스 행으로 키운다 —
|
|
242
|
+
// 미지정 시 10s 를 기본 적용한다(maria 드라이버 디폴트 10s 와 정렬). 명시 `acquireTimeoutMs: 0`
|
|
243
|
+
// 은 드라이버 무한 대기 의미 그대로 통과(무한 옵트인).
|
|
244
|
+
const acquireMap = spec.acquireTimeoutMs
|
|
245
|
+
if (acquireMap !== null && acquireMap !== undefined && out[acquireMap.key] === undefined) {
|
|
246
|
+
out[acquireMap.key] = DEFAULT_ACQUIRE_TIMEOUT_MS
|
|
247
|
+
}
|
|
207
248
|
return out
|
|
208
249
|
}
|
|
@@ -75,6 +75,8 @@ const KNOWN_OPTIONS = new Set(['serializer', 'extension'])
|
|
|
75
75
|
* @property {string} [basePath] - 캐시 파일 저장 디렉토리 (필수).
|
|
76
76
|
* @property {string} [dir] - `basePath` 의 별칭 (ADR-082 정합, 하위 호환).
|
|
77
77
|
* @property {{ serializer?: 'json' | 'raw', extension?: string }} [options]
|
|
78
|
+
* @property {string} [namespace] - 캐시 키 자동 prefix `mega:cache:<namespace>:` (ADR-064/213, 베이스 처리).
|
|
79
|
+
* @property {number} [defaultTtlSec] - `set` ttl 미지정 시 디폴트(초). 0 = 무한 옵트인. (ADR-216, 베이스 처리)
|
|
78
80
|
*/
|
|
79
81
|
|
|
80
82
|
/**
|
|
@@ -94,7 +96,8 @@ export class MegaFileAdapter extends MegaCacheAdapter {
|
|
|
94
96
|
#extension
|
|
95
97
|
|
|
96
98
|
/**
|
|
97
|
-
* @param {FileConfig} [config] - services.caches.<key> 설정.
|
|
99
|
+
* @param {FileConfig} [config] - services.caches.<key> 설정. 베이스(MegaCacheAdapter)의
|
|
100
|
+
* `namespace`(ADR-064 자동 prefix)/`defaultTtlSec`(ADR-216) 도 여기서 받는다.
|
|
98
101
|
* @throws {MegaValidationError} `adapter.basepath_required` - basePath/dir 누락.
|
|
99
102
|
* @throws {MegaValidationError} `adapter.invalid_option` - 옵션 타입/미지원 키 오류.
|
|
100
103
|
*/
|
|
@@ -245,7 +248,7 @@ export class MegaFileAdapter extends MegaCacheAdapter {
|
|
|
245
248
|
*/
|
|
246
249
|
async get(key) {
|
|
247
250
|
return this._instrument('get', { key }, async () => {
|
|
248
|
-
const path = this.#pathFor(key)
|
|
251
|
+
const path = this.#pathFor(this._cacheKey(key))
|
|
249
252
|
let raw
|
|
250
253
|
try {
|
|
251
254
|
raw = await readFile(path, 'utf8')
|
|
@@ -278,6 +281,7 @@ export class MegaFileAdapter extends MegaCacheAdapter {
|
|
|
278
281
|
// TTL·직렬화 검증은 의도적으로 `_instrument` **밖**(fail-fast). 잘못된 인자는 디스크 I/O·hook·stats
|
|
279
282
|
// 이전에 거부 — 프로그래밍 오류를 instrumented 호출 통계에 섞지 않는다(L-1, redis 어댑터와 동일 결정).
|
|
280
283
|
this._assertTtl(ttl)
|
|
284
|
+
ttl = this._resolveTtl(ttl, key) // 미지정 → defaultTtlSec(ADR-216). 디폴트 값은 생성자에서 검증됨.
|
|
281
285
|
if (this.#serializer === 'raw' && typeof value !== 'string') {
|
|
282
286
|
throw new MegaValidationError('cache.unserializable', `file set("${key}"): serializer='raw' requires a string value (got ${typeof value}).`, {
|
|
283
287
|
details: { key, type: typeof value, serializer: 'raw' },
|
|
@@ -300,7 +304,7 @@ export class MegaFileAdapter extends MegaCacheAdapter {
|
|
|
300
304
|
value,
|
|
301
305
|
expiresAt: ttl !== undefined ? Date.now() + ttl * 1000 : null,
|
|
302
306
|
}
|
|
303
|
-
await this.#atomicWrite(this.#pathFor(key), envelope)
|
|
307
|
+
await this.#atomicWrite(this.#pathFor(this._cacheKey(key)), envelope)
|
|
304
308
|
})
|
|
305
309
|
}
|
|
306
310
|
|
|
@@ -312,7 +316,7 @@ export class MegaFileAdapter extends MegaCacheAdapter {
|
|
|
312
316
|
async del(key) {
|
|
313
317
|
return this._instrument('del', { key }, async () => {
|
|
314
318
|
try {
|
|
315
|
-
await unlink(this.#pathFor(key))
|
|
319
|
+
await unlink(this.#pathFor(this._cacheKey(key)))
|
|
316
320
|
} catch (err) {
|
|
317
321
|
// ENOENT = 이미 없음(del idempotent — 정상). 그 외 I/O 에러는 전파(무차별 삼킴 X).
|
|
318
322
|
if (/** @type {any} */ (err)?.code !== 'ENOENT') throw err
|
|
@@ -331,7 +335,7 @@ export class MegaFileAdapter extends MegaCacheAdapter {
|
|
|
331
335
|
*/
|
|
332
336
|
async has(key) {
|
|
333
337
|
return this._instrument('has', { key }, async () => {
|
|
334
|
-
const path = this.#pathFor(key)
|
|
338
|
+
const path = this.#pathFor(this._cacheKey(key))
|
|
335
339
|
let raw
|
|
336
340
|
try {
|
|
337
341
|
raw = await readFile(path, 'utf8')
|
|
@@ -199,11 +199,12 @@ export class MegaFileSessionAdapter extends MegaSessionAdapter {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
/**
|
|
202
|
-
* 누적 통계 + file 세션 특화.
|
|
203
|
-
*
|
|
202
|
+
* 누적 통계 + file 세션 특화. `cleanupIntervalMs` 노출(0=내부 타이머 off) — 만료 스캔 주기의
|
|
203
|
+
* 적용 여부를 운영/테스트가 확인하는 단일 창구(ADR-215).
|
|
204
|
+
* @returns {ReturnType<import('./mega-adapter.js').MegaAdapter['getStats']> & { driver: string, basePath: string, ttlMs: number, cleanupIntervalMs: number }}
|
|
204
205
|
*/
|
|
205
206
|
getStats() {
|
|
206
|
-
return { ...super.getStats(), driver: 'file', basePath: this.#basePath, ttlMs: this.#ttlMs }
|
|
207
|
+
return { ...super.getStats(), driver: 'file', basePath: this.#basePath, ttlMs: this.#ttlMs, cleanupIntervalMs: this.#cleanupIntervalMs }
|
|
207
208
|
}
|
|
208
209
|
|
|
209
210
|
/**
|