sonamu 0.9.20 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_virtual/_rolldown/runtime.js +36 -0
- package/dist/ai/agents/agent.js +5 -7
- package/dist/ai/agents/index.js +1 -2
- package/dist/ai/agents/types.js +1 -1
- package/dist/ai/index.js +1 -2
- package/dist/ai/providers/rtzr/api.js +2 -3
- package/dist/ai/providers/rtzr/error.js +14 -29
- package/dist/ai/providers/rtzr/index.js +1 -2
- package/dist/ai/providers/rtzr/model.js +13 -20
- package/dist/ai/providers/rtzr/options.js +2 -3
- package/dist/ai/providers/rtzr/provider.js +2 -3
- package/dist/ai/providers/rtzr/utils.js +12 -21
- package/dist/api/base-frame.js +4 -4
- package/dist/api/caster.js +21 -38
- package/dist/api/code-converters.js +41 -98
- package/dist/api/config.d.ts +1 -10
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +9 -8
- package/dist/api/context.js +2 -3
- package/dist/api/decorators.js +80 -116
- package/dist/api/index.js +2 -3
- package/dist/api/secret.js +6 -10
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +200 -387
- package/dist/api/validator.js +5 -8
- package/dist/api/websocket-helpers.js +21 -32
- package/dist/auth/audit-log/builders.js +2 -3
- package/dist/auth/audit-log/events.js +2 -2
- package/dist/auth/audit-log/plugin.js +30 -61
- package/dist/auth/audit-log-ingestor.js +19 -41
- package/dist/auth/auth-generator.js +16 -41
- package/dist/auth/better-auth-entities.js +3 -4
- package/dist/auth/index.js +2 -3
- package/dist/auth/knex-adapter.js +18 -45
- package/dist/auth/plugins/entity-definitions/admin.js +2 -2
- package/dist/auth/plugins/entity-definitions/anonymous.js +2 -2
- package/dist/auth/plugins/entity-definitions/api-key.js +2 -2
- package/dist/auth/plugins/entity-definitions/audit-log.js +2 -2
- package/dist/auth/plugins/entity-definitions/index.js +2 -3
- package/dist/auth/plugins/entity-definitions/jwt.js +2 -2
- package/dist/auth/plugins/entity-definitions/organization.js +2 -2
- package/dist/auth/plugins/entity-definitions/passkey.js +2 -2
- package/dist/auth/plugins/entity-definitions/phone-number.js +2 -2
- package/dist/auth/plugins/entity-definitions/sso.js +2 -2
- package/dist/auth/plugins/entity-definitions/two-factor.js +2 -2
- package/dist/auth/plugins/entity-definitions/types.js +1 -1
- package/dist/auth/plugins/entity-definitions/username.js +2 -2
- package/dist/auth/plugins/index.js +1 -2
- package/dist/auth/plugins/wrappers/admin.js +2 -3
- package/dist/auth/plugins/wrappers/anonymous.js +2 -3
- package/dist/auth/plugins/wrappers/api-key.js +2 -3
- package/dist/auth/plugins/wrappers/index.js +1 -2
- package/dist/auth/plugins/wrappers/jwt.js +2 -3
- package/dist/auth/plugins/wrappers/organization.js +2 -3
- package/dist/auth/plugins/wrappers/passkey.js +2 -3
- package/dist/auth/plugins/wrappers/phone-number.js +2 -3
- package/dist/auth/plugins/wrappers/sso.js +2 -3
- package/dist/auth/plugins/wrappers/two-factor.js +2 -3
- package/dist/auth/plugins/wrappers/username.js +2 -3
- package/dist/bin/build-config.js +2 -2
- package/dist/bin/cli.js +151 -258
- package/dist/bin/fixture.d.ts.map +1 -1
- package/dist/bin/fixture.js +55 -97
- package/dist/bin/hmr-hook-register.js +3 -3
- package/dist/bin/migrate-targets.d.ts +3 -0
- package/dist/bin/migrate-targets.d.ts.map +1 -0
- package/dist/bin/migrate-targets.js +11 -0
- package/dist/bin/test-command.js +25 -55
- package/dist/bin/ts-loader-register.js +5 -6
- package/dist/bin/ts-loader-registration.js +6 -13
- package/dist/cache/cache-manager.js +3 -4
- package/dist/cache/decorator.js +11 -21
- package/dist/cache/drivers.js +2 -3
- package/dist/cache/index.js +2 -3
- package/dist/cache/types.js +1 -1
- package/dist/cache-control/cache-control.js +21 -34
- package/dist/cache-control/types.js +1 -1
- package/dist/compress/compress.js +10 -10
- package/dist/compress/index.js +1 -2
- package/dist/compress/types.js +1 -1
- package/dist/cone/cone-generator.js +25 -63
- package/dist/database/_batch_update.js +26 -46
- package/dist/database/base-model.js +44 -97
- package/dist/database/base-model.types.js +1 -1
- package/dist/database/db.d.ts +8 -14
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/db.js +127 -72
- package/dist/database/knex.js +5 -8
- package/dist/database/puri-subset.types.js +1 -1
- package/dist/database/puri-wrapper.js +11 -15
- package/dist/database/puri.js +117 -234
- package/dist/database/puri.types.js +3 -4
- package/dist/database/transaction-context.js +4 -5
- package/dist/database/upsert-builder.js +109 -176
- package/dist/dict/en.d.ts +1 -0
- package/dist/dict/en.d.ts.map +1 -1
- package/dist/dict/en.js +4 -4
- package/dist/dict/index.js +2 -3
- package/dist/dict/ko.d.ts +1 -0
- package/dist/dict/ko.d.ts.map +1 -1
- package/dist/dict/ko.js +4 -4
- package/dist/dict/rc-keys.js +3 -4
- package/dist/dict/sd.js +8 -19
- package/dist/dict/sonamu-dictionary.js +141 -284
- package/dist/dict/types.js +1 -1
- package/dist/dict/utils.js +4 -5
- package/dist/entity/entity-manager.d.ts +2 -2
- package/dist/entity/entity-manager.js +34 -82
- package/dist/entity/entity-template-cone.js +33 -66
- package/dist/entity/entity.js +156 -310
- package/dist/env.d.ts +14 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +75 -0
- package/dist/exceptions/error-handler.js +2 -3
- package/dist/exceptions/so-exceptions.js +7 -5
- package/dist/filter/index.js +1 -2
- package/dist/filter/types.js +3 -4
- package/dist/filter/utils.js +21 -54
- package/dist/index.js +8 -7
- package/dist/logger/category.js +6 -12
- package/dist/logger/configure.js +23 -34
- package/dist/migration/code-generation.js +146 -314
- package/dist/migration/index-where-predicate.js +52 -144
- package/dist/migration/migration-set.js +19 -33
- package/dist/migration/migrator.d.ts +2 -0
- package/dist/migration/migrator.d.ts.map +1 -1
- package/dist/migration/migrator.js +69 -53
- package/dist/migration/postgresql-schema-reader.js +126 -225
- package/dist/migration/slack-confirm.d.ts +1 -0
- package/dist/migration/slack-confirm.d.ts.map +1 -1
- package/dist/migration/slack-confirm.js +28 -38
- package/dist/migration/types.js +1 -1
- package/dist/naite/messaging-types.js +1 -1
- package/dist/naite/naite-reporter.js +15 -32
- package/dist/naite/naite.js +43 -76
- package/dist/ssr/index.js +6 -9
- package/dist/ssr/registry.js +10 -18
- package/dist/ssr/renderer.js +10 -21
- package/dist/ssr/types.js +1 -1
- package/dist/storage/base-file.js +5 -10
- package/dist/storage/buffered-file.js +3 -4
- package/dist/storage/drivers.js +2 -3
- package/dist/storage/index.js +2 -3
- package/dist/storage/s3-driver.js +5 -9
- package/dist/storage/storage-manager.js +5 -5
- package/dist/storage/types.js +1 -1
- package/dist/storage/uploaded-file.js +4 -6
- package/dist/stream/index.js +1 -2
- package/dist/stream/sse.js +8 -13
- package/dist/stream/ws-audience-resolver.js +5 -5
- package/dist/stream/ws-audience.js +3 -4
- package/dist/stream/ws-cluster-bus.js +3 -4
- package/dist/stream/ws-core.js +1 -1
- package/dist/stream/ws-delivery.js +11 -25
- package/dist/stream/ws-local-connection-store.js +9 -18
- package/dist/stream/ws-presence-store.js +43 -97
- package/dist/stream/ws-registry.js +17 -22
- package/dist/stream/ws-telemetry-memory.js +38 -45
- package/dist/stream/ws-telemetry-trace.js +4 -6
- package/dist/stream/ws-telemetry.js +82 -135
- package/dist/stream/ws.js +47 -91
- package/dist/syncer/api-parser.js +81 -147
- package/dist/syncer/checksum.js +9 -20
- package/dist/syncer/code-generator.js +29 -47
- package/dist/syncer/entity-operations.js +17 -27
- package/dist/syncer/event-batcher.js +8 -15
- package/dist/syncer/file-patterns.js +3 -4
- package/dist/syncer/file-tracking.js +6 -10
- package/dist/syncer/index.js +1 -2
- package/dist/syncer/module-loader.js +10 -26
- package/dist/syncer/syncer-actions.js +19 -37
- package/dist/syncer/syncer.js +46 -98
- package/dist/syncer/watcher.js +12 -26
- package/dist/tasks/decorator.js +7 -11
- package/dist/tasks/step-wrapper.js +7 -8
- package/dist/tasks/workflow-manager.js +18 -25
- package/dist/template/entity-converter.js +40 -64
- package/dist/template/helpers.js +32 -63
- package/dist/template/implementations/entity.template.js +7 -11
- package/dist/template/implementations/entry-server.template.js +2 -3
- package/dist/template/implementations/generated.template.js +25 -51
- package/dist/template/implementations/generated_http.template.js +31 -58
- package/dist/template/implementations/generated_sso.template.js +45 -85
- package/dist/template/implementations/init_types.template.js +4 -7
- package/dist/template/implementations/model.template.js +5 -10
- package/dist/template/implementations/model_test.template.js +2 -3
- package/dist/template/implementations/queries.template.js +4 -7
- package/dist/template/implementations/sd.template.js +17 -35
- package/dist/template/implementations/services.template.js +18 -30
- package/dist/template/implementations/view_form.template.js +72 -125
- package/dist/template/implementations/view_id_all_select.template.js +2 -3
- package/dist/template/implementations/view_list.template.js +86 -143
- package/dist/template/implementations/view_search_input.template.js +2 -3
- package/dist/template/index.js +5 -8
- package/dist/template/template-manager.js +13 -26
- package/dist/template/template-types.js +2 -3
- package/dist/template/template.js +7 -11
- package/dist/template/zod-converter.js +173 -348
- package/dist/testing/_relation-graph.js +18 -37
- package/dist/testing/bootstrap.js +5 -8
- package/dist/testing/data-explorer.js +34 -78
- package/dist/testing/dev-test-routes.js +54 -60
- package/dist/testing/dev-vitest-manager.js +33 -84
- package/dist/testing/faker-mappings.js +3 -4
- package/dist/testing/fixture-generator.d.ts +2 -1
- package/dist/testing/fixture-generator.d.ts.map +1 -1
- package/dist/testing/fixture-generator.js +159 -321
- package/dist/testing/fixture-loader.js +2 -2
- package/dist/testing/fixture-manager.d.ts.map +1 -1
- package/dist/testing/fixture-manager.js +124 -227
- package/dist/testing/global-setup.d.ts.map +1 -1
- package/dist/testing/global-setup.js +29 -17
- package/dist/testing/index.js +1 -2
- package/dist/testing/naite-vitest-reporter.js +2 -3
- package/dist/testing/parallel-db-manager.js +5 -3
- package/dist/testing/vitest-helpers.d.ts.map +1 -1
- package/dist/testing/vitest-helpers.js +15 -12
- package/dist/types/types.d.ts +14 -14
- package/dist/types/types.js +27 -50
- package/dist/ui/ai-api.js +6 -11
- package/dist/ui/ai-client.js +86 -134
- package/dist/ui/api.js +99 -195
- package/dist/ui/cdd-service.js +78 -130
- package/dist/ui/cdd-types.js +1 -1
- package/dist/ui-web/assets/{index-Df8q-fhb.js → index-DFStGyd0.js} +49 -49
- package/dist/ui-web/assets/index-Dx4ap5i4.css +1 -0
- package/dist/ui-web/index.html +2 -2
- package/dist/utils/async-utils.js +13 -25
- package/dist/utils/class-name.js +3 -4
- package/dist/utils/console-util.js +11 -26
- package/dist/utils/controller.d.ts.map +1 -1
- package/dist/utils/controller.js +14 -12
- package/dist/utils/esm-utils.js +5 -8
- package/dist/utils/formatter.js +10 -22
- package/dist/utils/fs-utils.js +14 -25
- package/dist/utils/lodash-able.js +3 -4
- package/dist/utils/model.js +7 -14
- package/dist/utils/object-utils.js +41 -73
- package/dist/utils/path-utils.js +5 -9
- package/dist/utils/process-utils.js +4 -7
- package/dist/utils/sql-parser.js +6 -13
- package/dist/utils/type-utils.js +16 -26
- package/dist/utils/utils.js +18 -40
- package/dist/utils/zod-error.js +9 -16
- package/dist/vector/chunking.js +24 -37
- package/dist/vector/config.js +2 -2
- package/dist/vector/embedding.js +8 -19
- package/dist/vector/index.js +1 -2
- package/dist/vector/types.js +1 -1
- package/package.json +6 -6
- package/src/__tests__/env.test.ts +127 -0
- package/src/api/__tests__/config.test.ts +10 -1
- package/src/api/config.ts +4 -12
- package/src/api/sonamu.ts +14 -4
- package/src/bin/__tests__/migrate-targets.test.ts +28 -0
- package/src/bin/__tests__/test-command.test.ts +82 -1
- package/src/bin/cli.ts +9 -18
- package/src/bin/fixture.ts +5 -4
- package/src/bin/migrate-targets.ts +7 -0
- package/src/bin/test-command.ts +2 -2
- package/src/database/__tests__/db.test.ts +175 -0
- package/src/database/db.ts +193 -71
- package/src/dict/en.ts +2 -0
- package/src/dict/ko.ts +2 -0
- package/src/env.ts +123 -0
- package/src/migration/__tests__/migrator.test.ts +149 -0
- package/src/migration/migrator.ts +74 -17
- package/src/migration/slack-confirm.ts +21 -0
- package/src/skills/sonamu/database.md +1 -1
- package/src/skills/sonamu/testing-devrunner.md +1 -1
- package/src/stream/ws-telemetry-memory.ts +2 -2
- package/src/testing/fixture-generator.ts +2 -1
- package/src/testing/fixture-manager.ts +3 -4
- package/src/testing/global-setup.ts +42 -18
- package/src/testing/vitest-helpers.ts +14 -0
- package/src/utils/controller.ts +14 -7
- package/tsdown.api.config.ts +6 -0
- package/dist/_virtual/rolldown_runtime.js +0 -39
- package/dist/ui-web/assets/index-D4rYm-Xz.css +0 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type SonamuDBConfig } from "../database/db";
|
|
2
|
+
import { getSonamuEnvironment } from "../env";
|
|
3
|
+
|
|
4
|
+
export function getMigrateRunTargets(): (keyof SonamuDBConfig)[] {
|
|
5
|
+
const environment = getSonamuEnvironment();
|
|
6
|
+
return environment === "test" ? ["test", "fixture"] : [environment];
|
|
7
|
+
}
|
package/src/bin/test-command.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { loadConfig } from "../api/config";
|
|
|
7
7
|
import { type RunResult, type TestCaseResult } from "../testing";
|
|
8
8
|
import { findApiRootPath } from "../utils/utils";
|
|
9
9
|
|
|
10
|
-
async function
|
|
10
|
+
async function loadDevServerConfig(): Promise<SonamuConfig> {
|
|
11
11
|
const prevVitest = process.env.VITEST;
|
|
12
12
|
process.env.VITEST = "true";
|
|
13
13
|
try {
|
|
@@ -37,7 +37,7 @@ function resolveTestBaseUrl(config: SonamuConfig): {
|
|
|
37
37
|
export async function testCommand(): Promise<void> {
|
|
38
38
|
const args = process.argv.slice(3);
|
|
39
39
|
|
|
40
|
-
const config = await
|
|
40
|
+
const config = await loadDevServerConfig();
|
|
41
41
|
|
|
42
42
|
// process.argv 파싱: sonamu test [file...] --pattern "이름" --traces --status
|
|
43
43
|
const files: string[] = [];
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { type EnvironmentSnapshots } from "../../env";
|
|
4
|
+
import { DBClass } from "../db";
|
|
5
|
+
|
|
6
|
+
describe("DBClass.generateDBConfig", () => {
|
|
7
|
+
const originalEnv = { ...process.env };
|
|
8
|
+
const emptySnapshots: EnvironmentSnapshots = {
|
|
9
|
+
test: {},
|
|
10
|
+
development: {},
|
|
11
|
+
staging: {},
|
|
12
|
+
production: {},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
for (const key of Object.keys(process.env)) {
|
|
17
|
+
if (!(key in originalEnv)) {
|
|
18
|
+
delete process.env[key];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
Object.assign(process.env, originalEnv);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("keeps defaultOptions.connection values when matching SONAMU_DB env vars are absent", () => {
|
|
25
|
+
delete process.env.SONAMU_DB_HOST;
|
|
26
|
+
delete process.env.SONAMU_DB_PORT;
|
|
27
|
+
delete process.env.SONAMU_DB_USER;
|
|
28
|
+
delete process.env.SONAMU_DB_PASSWORD;
|
|
29
|
+
delete process.env.SONAMU_DB_NAME;
|
|
30
|
+
|
|
31
|
+
const dbConfig = new DBClass().generateDBConfig(
|
|
32
|
+
{
|
|
33
|
+
database: "pg",
|
|
34
|
+
defaultOptions: {
|
|
35
|
+
connection: {
|
|
36
|
+
host: "default-host",
|
|
37
|
+
port: 15432,
|
|
38
|
+
user: "default-user",
|
|
39
|
+
password: "default-password",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
"Miomock",
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
expect(dbConfig.development!.connection).toMatchObject({
|
|
47
|
+
host: "default-host",
|
|
48
|
+
port: 15432,
|
|
49
|
+
user: "default-user",
|
|
50
|
+
password: "default-password",
|
|
51
|
+
database: "miomock_development",
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("derives database names from projectName even when defaultOptions.connection.database is set", () => {
|
|
56
|
+
const dbConfig = new DBClass().generateDBConfig(
|
|
57
|
+
{
|
|
58
|
+
database: "pg",
|
|
59
|
+
defaultOptions: {
|
|
60
|
+
connection: {
|
|
61
|
+
database: "legacy_database",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
"Miomock",
|
|
66
|
+
emptySnapshots,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
expect(dbConfig.development!.connection).toMatchObject({
|
|
70
|
+
database: "miomock_development",
|
|
71
|
+
});
|
|
72
|
+
expect(dbConfig.production!.connection).toMatchObject({
|
|
73
|
+
database: "miomock_production",
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("fails fast when legacy database.name or database.environments keys are present", () => {
|
|
78
|
+
const legacyConfig = {
|
|
79
|
+
database: "pg",
|
|
80
|
+
name: "miomock",
|
|
81
|
+
environments: {
|
|
82
|
+
production: {},
|
|
83
|
+
},
|
|
84
|
+
} as Parameters<DBClass["generateDBConfig"]>[0];
|
|
85
|
+
|
|
86
|
+
expect(() => new DBClass().generateDBConfig(legacyConfig, "Miomock")).toThrow(
|
|
87
|
+
/database\.name and database\.environments were removed/,
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("lets readonly env vars override only the readonly connection fields", () => {
|
|
92
|
+
process.env.SONAMU_DB_HOST = "writer-host";
|
|
93
|
+
process.env.SONAMU_DB_USER = "writer-user";
|
|
94
|
+
process.env.SONAMU_DB_PASSWORD = "writer-password";
|
|
95
|
+
process.env.SONAMU_DB_READONLY_HOST = "readonly-host";
|
|
96
|
+
|
|
97
|
+
const dbConfig = new DBClass().generateDBConfig(
|
|
98
|
+
{
|
|
99
|
+
database: "pg",
|
|
100
|
+
defaultOptions: {
|
|
101
|
+
connection: {
|
|
102
|
+
port: 15432,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
"Miomock",
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
expect(dbConfig.development_readonly!.connection).toMatchObject({
|
|
110
|
+
host: "readonly-host",
|
|
111
|
+
port: 15432,
|
|
112
|
+
user: "writer-user",
|
|
113
|
+
password: "writer-password",
|
|
114
|
+
database: "miomock_development",
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("does not let current process connection values leak into other environment snapshots", () => {
|
|
119
|
+
const dbConfig = new DBClass().generateDBConfig(
|
|
120
|
+
{
|
|
121
|
+
database: "pg",
|
|
122
|
+
defaultOptions: {
|
|
123
|
+
connection: {
|
|
124
|
+
host: "development-host",
|
|
125
|
+
port: 15432,
|
|
126
|
+
user: "development-user",
|
|
127
|
+
password: "development-password",
|
|
128
|
+
ssl: true,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
"Miomock",
|
|
133
|
+
{
|
|
134
|
+
...emptySnapshots,
|
|
135
|
+
staging: {
|
|
136
|
+
SONAMU_DB_HOST: "staging-host",
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
expect(dbConfig.staging!.connection).toMatchObject({
|
|
142
|
+
host: "staging-host",
|
|
143
|
+
port: 5432,
|
|
144
|
+
user: "postgres",
|
|
145
|
+
database: "miomock_staging",
|
|
146
|
+
ssl: true,
|
|
147
|
+
});
|
|
148
|
+
expect(dbConfig.staging!.connection).not.toMatchObject({
|
|
149
|
+
password: "development-password",
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("does not reuse SONAMU_DB_NAME as the fixture database name", () => {
|
|
154
|
+
const dbConfig = new DBClass().generateDBConfig(
|
|
155
|
+
{
|
|
156
|
+
database: "pg",
|
|
157
|
+
defaultOptions: {},
|
|
158
|
+
},
|
|
159
|
+
"Miomock",
|
|
160
|
+
{
|
|
161
|
+
...emptySnapshots,
|
|
162
|
+
test: {
|
|
163
|
+
SONAMU_DB_NAME: "miomock_test",
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
expect(dbConfig.test!.connection).toMatchObject({
|
|
169
|
+
database: "miomock_test",
|
|
170
|
+
});
|
|
171
|
+
expect(dbConfig.fixture!.connection).toMatchObject({
|
|
172
|
+
database: "miomock_fixture",
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
package/src/database/db.ts
CHANGED
|
@@ -4,13 +4,10 @@ import { AsyncLocalStorage } from "async_hooks";
|
|
|
4
4
|
import { type Knex } from "knex";
|
|
5
5
|
|
|
6
6
|
import { type DatabaseConfig, type SonamuConfig } from "../api/config";
|
|
7
|
+
import { getSonamuEnvironment, type EnvironmentSnapshots, type SonamuEnvironment } from "../env";
|
|
7
8
|
import { createKnexInstance } from "./knex";
|
|
8
9
|
import { TransactionContext } from "./transaction-context";
|
|
9
10
|
|
|
10
|
-
/**
|
|
11
|
-
* 여러 설정 객체를 순차적으로 deep merge합니다.
|
|
12
|
-
* undefined/null인 인자는 무시됩니다.
|
|
13
|
-
*/
|
|
14
11
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
15
12
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16
13
|
}
|
|
@@ -44,20 +41,115 @@ function mergeConfigs<T extends object>(...configs: (Partial<T> | undefined | nu
|
|
|
44
41
|
return merged as T;
|
|
45
42
|
}
|
|
46
43
|
|
|
47
|
-
export type
|
|
44
|
+
export type SonamuMainDBPreset = "test" | "fixture" | SonamuEnvironment;
|
|
45
|
+
export type SonamuReadonlyDBPreset =
|
|
46
|
+
| "test_readonly"
|
|
47
|
+
| "development_readonly"
|
|
48
|
+
| "staging_readonly"
|
|
49
|
+
| "production_readonly";
|
|
50
|
+
export type SonamuDBPreset = SonamuMainDBPreset | SonamuReadonlyDBPreset;
|
|
51
|
+
export type DBPreset = "w" | "r" | SonamuDBPreset;
|
|
48
52
|
|
|
49
|
-
export type SonamuDBConfig =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
};
|
|
53
|
+
export type SonamuDBConfig = Record<SonamuDBPreset, Knex.Config>;
|
|
54
|
+
|
|
55
|
+
function isConcretePreset(value: DBPreset): value is SonamuDBPreset {
|
|
56
|
+
return value !== "w" && value !== "r";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getReadonlyPreset(environment: SonamuEnvironment): SonamuReadonlyDBPreset {
|
|
60
|
+
return `${environment}_readonly` as SonamuReadonlyDBPreset;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getProjectDatabaseBaseName(projectName?: string): string {
|
|
64
|
+
return (projectName ?? "sonamu")
|
|
65
|
+
.toLowerCase()
|
|
66
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
67
|
+
.replace(/^_+|_+$/g, "");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function numberFromEnv(value: string | undefined, fallback: number): number {
|
|
71
|
+
if (value === undefined || value === "") {
|
|
72
|
+
return fallback;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const parsed = Number(value);
|
|
76
|
+
if (Number.isNaN(parsed)) {
|
|
77
|
+
throw new Error(`Invalid database port: ${value}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return parsed;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function connectionFromEnv(options: {
|
|
84
|
+
baseName: string;
|
|
85
|
+
suffix: SonamuMainDBPreset;
|
|
86
|
+
baseConnection?: Knex.PgConnectionConfig;
|
|
87
|
+
prefix?: "SONAMU_DB_READONLY" | "SONAMU_DB_FIXTURE";
|
|
88
|
+
env?: NodeJS.ProcessEnv;
|
|
89
|
+
}): Knex.PgConnectionConfig {
|
|
90
|
+
const {
|
|
91
|
+
baseName,
|
|
92
|
+
suffix,
|
|
93
|
+
baseConnection = {},
|
|
94
|
+
prefix = "SONAMU_DB",
|
|
95
|
+
env = process.env,
|
|
96
|
+
} = options;
|
|
97
|
+
const read = (key: "HOST" | "PORT" | "USER" | "PASSWORD" | "NAME") => {
|
|
98
|
+
const prefixedValue = env[`${prefix}_${key}`];
|
|
99
|
+
if (prefixedValue !== undefined) {
|
|
100
|
+
return prefixedValue;
|
|
101
|
+
}
|
|
102
|
+
if (prefix === "SONAMU_DB_FIXTURE" && key === "NAME") {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return env[`SONAMU_DB_${key}`];
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
...baseConnection,
|
|
111
|
+
host: read("HOST") ?? baseConnection.host ?? "0.0.0.0",
|
|
112
|
+
port: numberFromEnv(read("PORT"), baseConnection.port ?? 5432),
|
|
113
|
+
user: read("USER") ?? baseConnection.user ?? "postgres",
|
|
114
|
+
password: read("PASSWORD") ?? baseConnection.password,
|
|
115
|
+
database: read("NAME") ?? `${baseName}_${suffix}`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function neutralizeEnvironmentConnectionFields(
|
|
120
|
+
connection: Knex.PgConnectionConfig | undefined,
|
|
121
|
+
): Knex.PgConnectionConfig | undefined {
|
|
122
|
+
if (connection === undefined) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const neutralConnection = { ...connection };
|
|
127
|
+
delete neutralConnection.host;
|
|
128
|
+
delete neutralConnection.port;
|
|
129
|
+
delete neutralConnection.user;
|
|
130
|
+
delete neutralConnection.password;
|
|
131
|
+
delete neutralConnection.database;
|
|
132
|
+
|
|
133
|
+
return neutralConnection;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function assertNoLegacyDatabaseConfig(config: SonamuConfig["database"]): void {
|
|
137
|
+
const legacyConfig = config as SonamuConfig["database"] & {
|
|
138
|
+
name?: unknown;
|
|
139
|
+
environments?: unknown;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
if (legacyConfig.name !== undefined || legacyConfig.environments !== undefined) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
"Sonamu database.name and database.environments were removed. Use SONAMU_DB_* dotenv variables instead.",
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
57
148
|
|
|
58
149
|
export class DBClass {
|
|
59
150
|
private wdb?: Knex;
|
|
60
151
|
private rdb?: Knex;
|
|
152
|
+
private presetDBs: Map<SonamuDBPreset, Knex> = new Map();
|
|
61
153
|
private workerDBs: Map<number, Knex> = new Map();
|
|
62
154
|
private currentConfig: SonamuDBConfig | null = null;
|
|
63
155
|
|
|
@@ -86,14 +178,21 @@ export class DBClass {
|
|
|
86
178
|
getDB(which: DBPreset): Knex {
|
|
87
179
|
const dbConfig = this.getCurrentConfig();
|
|
88
180
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
181
|
+
if (isConcretePreset(which)) {
|
|
182
|
+
if (!this.presetDBs.has(which)) {
|
|
183
|
+
this.presetDBs.set(which, createKnexInstance(dbConfig[which]));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const db = this.presetDBs.get(which);
|
|
187
|
+
assert(db, `DB preset ${which} not found`);
|
|
188
|
+
return db;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (getSonamuEnvironment() === "test") {
|
|
92
192
|
if (process.env.SONAMU_WORKER_DB === "true") {
|
|
93
193
|
return this.getWorkerDB(dbConfig);
|
|
94
194
|
}
|
|
95
195
|
|
|
96
|
-
// 기존 단일 테스트 로직
|
|
97
196
|
if (this.testTransaction) {
|
|
98
197
|
return this.testTransaction;
|
|
99
198
|
} else if (this.wdb) {
|
|
@@ -101,7 +200,6 @@ export class DBClass {
|
|
|
101
200
|
} else {
|
|
102
201
|
this.wdb = createKnexInstance({
|
|
103
202
|
...dbConfig.test,
|
|
104
|
-
// 단일 풀
|
|
105
203
|
pool: {
|
|
106
204
|
min: 1,
|
|
107
205
|
max: 1,
|
|
@@ -121,19 +219,13 @@ export class DBClass {
|
|
|
121
219
|
return this[instanceName];
|
|
122
220
|
}
|
|
123
221
|
|
|
124
|
-
/**
|
|
125
|
-
* 병렬 테스트에서 worker별 DB 인스턴스를 반환합니다.
|
|
126
|
-
* VITEST_POOL_ID 환경변수로 worker를 식별하여 해당 DB에 연결합니다.
|
|
127
|
-
*/
|
|
128
222
|
private getWorkerDB(dbConfig: SonamuDBConfig): Knex {
|
|
129
|
-
// 트랜잭션이 있으면 트랜잭션 반환
|
|
130
223
|
if (this.testTransaction) {
|
|
131
224
|
return this.testTransaction;
|
|
132
225
|
}
|
|
133
226
|
|
|
134
227
|
const workerId = parseInt(process.env.VITEST_POOL_ID ?? "1", 10);
|
|
135
228
|
|
|
136
|
-
// Worker별 DB 인스턴스 캐싱
|
|
137
229
|
if (!this.workerDBs.has(workerId)) {
|
|
138
230
|
const baseTestConfig = dbConfig.test;
|
|
139
231
|
const connection = baseTestConfig.connection as { database: string };
|
|
@@ -158,29 +250,24 @@ export class DBClass {
|
|
|
158
250
|
|
|
159
251
|
getDBConfig(which: DBPreset): Knex.Config {
|
|
160
252
|
const dbConfig = this.getCurrentConfig();
|
|
161
|
-
|
|
253
|
+
|
|
254
|
+
if (isConcretePreset(which)) {
|
|
255
|
+
return dbConfig[which];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const environment = getSonamuEnvironment();
|
|
259
|
+
if (environment === "test") {
|
|
260
|
+
const target = which === "w" ? dbConfig.test : dbConfig.test_readonly;
|
|
162
261
|
return {
|
|
163
|
-
...
|
|
164
|
-
// 단일 풀
|
|
262
|
+
...target,
|
|
165
263
|
pool: {
|
|
166
264
|
min: 1,
|
|
167
265
|
max: 1,
|
|
168
266
|
},
|
|
169
267
|
};
|
|
170
268
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
case "staging":
|
|
174
|
-
return which === "w"
|
|
175
|
-
? dbConfig.development_master
|
|
176
|
-
: (dbConfig.development_slave ?? dbConfig.development_master);
|
|
177
|
-
case "production":
|
|
178
|
-
return which === "w"
|
|
179
|
-
? dbConfig.production_master
|
|
180
|
-
: (dbConfig.production_slave ?? dbConfig.production_master);
|
|
181
|
-
default:
|
|
182
|
-
throw new Error(`현재 ENV ${process.env.NODE_ENV}에는 설정 가능한 DB설정이 없습니다.`);
|
|
183
|
-
}
|
|
269
|
+
|
|
270
|
+
return which === "w" ? dbConfig[environment] : dbConfig[getReadonlyPreset(environment)];
|
|
184
271
|
}
|
|
185
272
|
|
|
186
273
|
async destroy(): Promise<void> {
|
|
@@ -192,17 +279,27 @@ export class DBClass {
|
|
|
192
279
|
await this.rdb.destroy();
|
|
193
280
|
this.rdb = undefined;
|
|
194
281
|
}
|
|
195
|
-
|
|
282
|
+
for (const db of this.presetDBs.values()) {
|
|
283
|
+
await db.destroy();
|
|
284
|
+
}
|
|
285
|
+
this.presetDBs.clear();
|
|
196
286
|
for (const db of this.workerDBs.values()) {
|
|
197
287
|
await db.destroy();
|
|
198
288
|
}
|
|
199
289
|
this.workerDBs.clear();
|
|
200
290
|
}
|
|
201
291
|
|
|
202
|
-
public generateDBConfig(
|
|
292
|
+
public generateDBConfig(
|
|
293
|
+
config: SonamuConfig["database"],
|
|
294
|
+
projectName?: string,
|
|
295
|
+
snapshots?: EnvironmentSnapshots,
|
|
296
|
+
): SonamuDBConfig {
|
|
297
|
+
assertNoLegacyDatabaseConfig(config);
|
|
298
|
+
|
|
299
|
+
const baseName = getProjectDatabaseBaseName(projectName);
|
|
203
300
|
const defaultKnexConfig = mergeConfigs<Partial<DatabaseConfig>>(
|
|
204
301
|
{
|
|
205
|
-
client: "postgresql",
|
|
302
|
+
client: config.database === "pgnative" ? "pgnative" : "postgresql",
|
|
206
303
|
pool: {
|
|
207
304
|
min: 1,
|
|
208
305
|
max: 5,
|
|
@@ -210,42 +307,67 @@ export class DBClass {
|
|
|
210
307
|
migrations: {
|
|
211
308
|
directory: "./src/migrations",
|
|
212
309
|
},
|
|
213
|
-
connection: {
|
|
214
|
-
database: config.name,
|
|
215
|
-
},
|
|
216
310
|
},
|
|
217
311
|
config.defaultOptions,
|
|
218
312
|
);
|
|
313
|
+
const baseConnection = defaultKnexConfig.connection as Knex.PgConnectionConfig | undefined;
|
|
314
|
+
const environmentFallbackConnection = snapshots
|
|
315
|
+
? neutralizeEnvironmentConnectionFields(baseConnection)
|
|
316
|
+
: baseConnection;
|
|
317
|
+
|
|
318
|
+
const envForPreset = (preset: SonamuMainDBPreset): NodeJS.ProcessEnv => {
|
|
319
|
+
if (!snapshots) {
|
|
320
|
+
return process.env;
|
|
321
|
+
}
|
|
322
|
+
if (preset === "fixture") {
|
|
323
|
+
return snapshots.test;
|
|
324
|
+
}
|
|
325
|
+
return snapshots[preset];
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const mainConfig = (preset: SonamuMainDBPreset): Knex.Config =>
|
|
329
|
+
mergeConfigs(defaultKnexConfig, {
|
|
330
|
+
connection: connectionFromEnv({
|
|
331
|
+
baseName,
|
|
332
|
+
suffix: preset,
|
|
333
|
+
baseConnection: environmentFallbackConnection,
|
|
334
|
+
env: envForPreset(preset),
|
|
335
|
+
}),
|
|
336
|
+
});
|
|
337
|
+
const readonlyConfig = (environment: SonamuEnvironment): Knex.Config =>
|
|
338
|
+
mergeConfigs<Knex.Config>(defaultKnexConfig, mainConfig(environment), {
|
|
339
|
+
connection: connectionFromEnv({
|
|
340
|
+
baseName,
|
|
341
|
+
suffix: environment,
|
|
342
|
+
baseConnection: mainConfig(environment).connection as Knex.PgConnectionConfig,
|
|
343
|
+
prefix: "SONAMU_DB_READONLY",
|
|
344
|
+
env: envForPreset(environment),
|
|
345
|
+
}),
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const test = mainConfig("test");
|
|
219
349
|
|
|
220
|
-
// oxfmt-ignore -- 설정 구조 가독성을 위해 여러 줄로 유지
|
|
221
350
|
return {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
),
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
),
|
|
239
|
-
production_master: mergeConfigs(defaultKnexConfig, config.environments?.production),
|
|
240
|
-
production_slave: mergeConfigs(
|
|
241
|
-
defaultKnexConfig,
|
|
242
|
-
config.environments?.production,
|
|
243
|
-
config.environments?.production_slave,
|
|
244
|
-
),
|
|
351
|
+
test,
|
|
352
|
+
test_readonly: readonlyConfig("test"),
|
|
353
|
+
fixture: mergeConfigs(defaultKnexConfig, {
|
|
354
|
+
connection: connectionFromEnv({
|
|
355
|
+
baseName,
|
|
356
|
+
suffix: "fixture",
|
|
357
|
+
baseConnection: environmentFallbackConnection,
|
|
358
|
+
prefix: "SONAMU_DB_FIXTURE",
|
|
359
|
+
env: envForPreset("fixture"),
|
|
360
|
+
}),
|
|
361
|
+
}),
|
|
362
|
+
development: mainConfig("development"),
|
|
363
|
+
development_readonly: readonlyConfig("development"),
|
|
364
|
+
staging: mainConfig("staging"),
|
|
365
|
+
staging_readonly: readonlyConfig("staging"),
|
|
366
|
+
production: mainConfig("production"),
|
|
367
|
+
production_readonly: readonlyConfig("production"),
|
|
245
368
|
};
|
|
246
369
|
}
|
|
247
370
|
|
|
248
|
-
// Test 환경에서 트랜잭션 사용
|
|
249
371
|
public testTransaction: Knex.Transaction | null = null;
|
|
250
372
|
async createTestTransaction(): Promise<Knex.Transaction> {
|
|
251
373
|
const db = this.getDB("w");
|
package/src/dict/en.ts
CHANGED
|
@@ -80,6 +80,8 @@ export default {
|
|
|
80
80
|
"sonamu.error.keyNotFound": (key: string) => `Key not found: ${key}`,
|
|
81
81
|
"sonamu.error.migrationRejected": "Migration has been rejected",
|
|
82
82
|
"sonamu.error.slackConfirmNotConfigured": "Slack Confirm is not configured",
|
|
83
|
+
"sonamu.error.slackConfirmInvalidTargets": (targets: string, validTargets: string) =>
|
|
84
|
+
`Slack Confirm targets include unknown DB keys: ${targets}. Valid targets: ${validTargets}`,
|
|
83
85
|
"sonamu.error.devRunner.notEnabled":
|
|
84
86
|
"devRunner is not enabled. Set test.devRunner.enabled: true in sonamu.config.ts",
|
|
85
87
|
"sonamu.error.devRunner.notReady": "Vitest instance is not ready yet",
|
package/src/dict/ko.ts
CHANGED
|
@@ -81,6 +81,8 @@ export default {
|
|
|
81
81
|
"sonamu.error.keyNotFound": (key: string) => `키를 찾을 수 없습니다: ${key}`,
|
|
82
82
|
"sonamu.error.migrationRejected": "마이그레이션이 거절되었습니다",
|
|
83
83
|
"sonamu.error.slackConfirmNotConfigured": "Slack Confirm이 설정되지 않았습니다",
|
|
84
|
+
"sonamu.error.slackConfirmInvalidTargets": (targets: string, validTargets: string) =>
|
|
85
|
+
`Slack Confirm targets에 알 수 없는 DB 키가 포함되어 있습니다: ${targets}. 유효한 대상: ${validTargets}`,
|
|
84
86
|
"sonamu.error.devRunner.notEnabled":
|
|
85
87
|
"devRunner가 활성화되지 않았습니다. sonamu.config.ts에서 test.devRunner.enabled: true 설정이 필요합니다",
|
|
86
88
|
"sonamu.error.devRunner.notReady": "Vitest 인스턴스가 아직 준비되지 않았습니다",
|