sonamu 0.7.21 → 0.7.23
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/ai/agents/agent.d.ts +6 -1
- package/dist/ai/agents/agent.d.ts.map +1 -1
- package/dist/ai/agents/agent.js +20 -5
- package/dist/api/base-frame.d.ts +4 -0
- package/dist/api/base-frame.d.ts.map +1 -1
- package/dist/api/base-frame.js +9 -1
- package/dist/api/caster.d.ts.map +1 -1
- package/dist/api/caster.js +2 -2
- package/dist/api/config.d.ts +35 -3
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +1 -1
- package/dist/api/decorators.d.ts +4 -4
- package/dist/api/decorators.d.ts.map +1 -1
- package/dist/api/decorators.js +80 -18
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +2 -1
- package/dist/api/secret.d.ts +7 -0
- package/dist/api/secret.d.ts.map +1 -0
- package/dist/api/secret.js +17 -0
- package/dist/api/sonamu.d.ts +17 -8
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +265 -47
- package/dist/cache/cache-manager.d.ts +11 -0
- package/dist/cache/cache-manager.d.ts.map +1 -0
- package/dist/cache/cache-manager.js +22 -0
- package/dist/cache/decorator.d.ts +31 -0
- package/dist/cache/decorator.d.ts.map +1 -0
- package/dist/cache/decorator.js +86 -0
- package/dist/cache/drivers.d.ts +33 -0
- package/dist/cache/drivers.d.ts.map +1 -0
- package/dist/cache/drivers.js +36 -0
- package/dist/cache/index.d.ts +4 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +8 -0
- package/dist/cache/types.d.ts +28 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cache/types.js +6 -0
- package/dist/database/base-model.d.ts +4 -2
- package/dist/database/base-model.d.ts.map +1 -1
- package/dist/database/base-model.js +9 -4
- package/dist/database/code-generator.d.ts +3 -1
- package/dist/database/code-generator.d.ts.map +1 -1
- package/dist/database/code-generator.js +3 -2
- package/dist/database/db.d.ts +1 -1
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/db.js +5 -5
- package/dist/database/knex.d.ts +3 -0
- package/dist/database/knex.d.ts.map +1 -0
- package/dist/database/knex.js +29 -0
- package/dist/database/puri.types.d.ts.map +1 -1
- package/dist/database/puri.types.js +1 -1
- package/dist/database/upsert-builder.d.ts.map +1 -1
- package/dist/database/upsert-builder.js +49 -5
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/logger/category.d.ts +4 -0
- package/dist/logger/category.d.ts.map +1 -0
- package/dist/logger/category.js +34 -0
- package/dist/logger/configure.d.ts +9 -0
- package/dist/logger/configure.d.ts.map +1 -0
- package/dist/logger/configure.js +115 -0
- package/dist/migration/code-generation.d.ts +5 -1
- package/dist/migration/code-generation.d.ts.map +1 -1
- package/dist/migration/code-generation.js +13 -7
- package/dist/migration/migrator.d.ts +1 -1
- package/dist/migration/migrator.d.ts.map +1 -1
- package/dist/migration/migrator.js +7 -7
- package/dist/migration/postgresql-schema-reader.d.ts.map +1 -1
- package/dist/migration/postgresql-schema-reader.js +5 -3
- package/dist/naite/naite.d.ts +0 -4
- package/dist/naite/naite.d.ts.map +1 -1
- package/dist/naite/naite.js +11 -19
- package/dist/ssr/index.d.ts +4 -0
- package/dist/ssr/index.d.ts.map +1 -0
- package/dist/ssr/index.js +4 -0
- package/dist/ssr/registry.d.ts +10 -0
- package/dist/ssr/registry.d.ts.map +1 -0
- package/dist/ssr/registry.js +43 -0
- package/dist/ssr/renderer.d.ts +6 -0
- package/dist/ssr/renderer.d.ts.map +1 -0
- package/dist/ssr/renderer.js +70 -0
- package/dist/ssr/types.d.ts +19 -0
- package/dist/ssr/types.d.ts.map +1 -0
- package/dist/ssr/types.js +4 -0
- package/dist/syncer/syncer.d.ts +1 -0
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/syncer/syncer.js +58 -1
- package/dist/tasks/decorator.d.ts +1 -0
- package/dist/tasks/decorator.d.ts.map +1 -1
- package/dist/tasks/decorator.js +9 -7
- package/dist/tasks/step-wrapper.d.ts +5 -0
- package/dist/tasks/step-wrapper.d.ts.map +1 -1
- package/dist/tasks/step-wrapper.js +11 -6
- package/dist/tasks/workflow-manager.d.ts +2 -0
- package/dist/tasks/workflow-manager.d.ts.map +1 -1
- package/dist/tasks/workflow-manager.js +5 -2
- package/dist/template/implementations/entry-server.template.d.ts +17 -0
- package/dist/template/implementations/entry-server.template.d.ts.map +1 -0
- package/dist/template/implementations/entry-server.template.js +78 -0
- package/dist/template/implementations/model.template.d.ts.map +1 -1
- package/dist/template/implementations/model.template.js +5 -3
- package/dist/template/implementations/queries.template.d.ts +17 -0
- package/dist/template/implementations/queries.template.d.ts.map +1 -0
- package/dist/template/implementations/queries.template.js +83 -0
- package/dist/template/implementations/view_enums_select.template.d.ts.map +1 -1
- package/dist/template/implementations/view_enums_select.template.js +34 -20
- package/dist/template/implementations/view_form.template.d.ts +2 -1
- package/dist/template/implementations/view_form.template.d.ts.map +1 -1
- package/dist/template/implementations/view_form.template.js +301 -129
- package/dist/template/implementations/view_id_async_select.template.d.ts.map +1 -1
- package/dist/template/implementations/view_id_async_select.template.js +136 -57
- package/dist/template/implementations/view_list.template.d.ts +2 -0
- package/dist/template/implementations/view_list.template.d.ts.map +1 -1
- package/dist/template/implementations/view_list.template.js +392 -227
- package/dist/template/implementations/view_search_input.template.d.ts.map +1 -1
- package/dist/template/implementations/view_search_input.template.js +46 -30
- package/dist/template/zod-converter.d.ts.map +1 -1
- package/dist/template/zod-converter.js +2 -2
- package/dist/testing/bootstrap.d.ts +28 -0
- package/dist/testing/bootstrap.d.ts.map +1 -0
- package/dist/testing/bootstrap.js +120 -0
- package/dist/testing/fixture-loader.d.ts +21 -0
- package/dist/testing/fixture-loader.d.ts.map +1 -0
- package/dist/testing/fixture-loader.js +28 -0
- package/dist/testing/fixture-manager.d.ts +1 -1
- package/dist/testing/fixture-manager.d.ts.map +1 -1
- package/dist/testing/fixture-manager.js +7 -7
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/naite-vitest-reporter.d.ts +12 -0
- package/dist/testing/naite-vitest-reporter.d.ts.map +1 -0
- package/dist/testing/naite-vitest-reporter.js +17 -0
- package/dist/types/types.d.ts +5 -6
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +7 -8
- package/dist/ui/ai-client.d.ts +3 -1
- package/dist/ui/ai-client.d.ts.map +1 -1
- package/dist/ui/ai-client.js +27 -8
- package/dist/ui-web/assets/index-CTYv3qL6.js +92 -0
- package/dist/ui-web/index.html +1 -1
- package/package.json +43 -20
- package/src/ai/agents/agent.ts +38 -19
- package/src/api/base-frame.ts +8 -0
- package/src/api/caster.ts +6 -1
- package/src/api/config.ts +38 -4
- package/src/api/decorators.ts +106 -20
- package/src/api/index.ts +1 -0
- package/src/api/secret.ts +23 -0
- package/src/api/sonamu.ts +334 -61
- package/src/cache/cache-manager.ts +23 -0
- package/src/cache/decorator.ts +116 -0
- package/src/cache/drivers.ts +42 -0
- package/src/cache/index.ts +16 -0
- package/src/cache/types.ts +32 -0
- package/src/database/base-model.ts +7 -3
- package/src/database/code-generator.ts +3 -1
- package/src/database/db.ts +5 -5
- package/src/database/knex.ts +34 -0
- package/src/database/puri.types.ts +2 -3
- package/src/database/upsert-builder.ts +58 -4
- package/src/index.ts +4 -0
- package/src/logger/category.ts +42 -0
- package/src/logger/configure.ts +132 -0
- package/src/migration/code-generation.ts +19 -6
- package/src/migration/migrator.ts +7 -6
- package/src/migration/postgresql-schema-reader.ts +7 -2
- package/src/naite/naite.ts +10 -18
- package/src/shared/web.shared.ts.txt +1 -1
- package/src/ssr/index.ts +13 -0
- package/src/ssr/registry.ts +52 -0
- package/src/ssr/renderer.ts +105 -0
- package/src/ssr/types.ts +20 -0
- package/src/syncer/syncer.ts +59 -0
- package/src/tasks/decorator.ts +20 -4
- package/src/tasks/step-wrapper.ts +14 -5
- package/src/tasks/workflow-manager.ts +9 -1
- package/src/template/implementations/entry-server.template.ts +81 -0
- package/src/template/implementations/model.template.ts +4 -2
- package/src/template/implementations/queries.template.ts +111 -0
- package/src/template/implementations/view_enums_select.template.ts +33 -19
- package/src/template/implementations/view_form.template.ts +324 -145
- package/src/template/implementations/view_id_async_select.template.ts +145 -56
- package/src/template/implementations/view_list.template.ts +446 -236
- package/src/template/implementations/view_search_input.template.ts +45 -29
- package/src/template/zod-converter.ts +4 -1
- package/src/testing/bootstrap.ts +176 -0
- package/src/testing/fixture-loader.ts +28 -0
- package/src/testing/fixture-manager.ts +7 -6
- package/src/testing/index.ts +3 -0
- package/src/testing/naite-vitest-reporter.ts +18 -0
- package/src/types/types.ts +4 -5
- package/src/ui/ai-client.ts +82 -50
- package/dist/template/implementations/view_enums_dropdown.template.d.ts +0 -17
- package/dist/template/implementations/view_enums_dropdown.template.d.ts.map +0 -1
- package/dist/template/implementations/view_enums_dropdown.template.js +0 -50
- package/dist/ui-web/assets/index-B87IyofX.js +0 -92
- package/src/template/implementations/view_enums_dropdown.template.ts +0 -53
package/dist/ui-web/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>{{projectName}}: Sonamu UI</title>
|
|
7
|
-
<script type="module" crossorigin src="/sonamu-ui/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/sonamu-ui/assets/index-CTYv3qL6.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/sonamu-ui/assets/index-CpaB9P6g.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonamu",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.23",
|
|
4
4
|
"description": "Sonamu — TypeScript Fullstack API Framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -25,6 +25,18 @@
|
|
|
25
25
|
"import": "./dist/storage/index.js",
|
|
26
26
|
"types": "./dist/storage/index.d.ts"
|
|
27
27
|
},
|
|
28
|
+
"./ssr": {
|
|
29
|
+
"import": "./dist/ssr/index.js",
|
|
30
|
+
"types": "./dist/ssr/index.d.ts"
|
|
31
|
+
},
|
|
32
|
+
"./test": {
|
|
33
|
+
"import": "./dist/testing/index.js",
|
|
34
|
+
"types": "./dist/testing/index.d.ts"
|
|
35
|
+
},
|
|
36
|
+
"./cache": {
|
|
37
|
+
"import": "./dist/cache/index.js",
|
|
38
|
+
"types": "./dist/cache/index.d.ts"
|
|
39
|
+
},
|
|
28
40
|
"./ai/providers/rtzr": {
|
|
29
41
|
"import": "./dist/ai/providers/rtzr/index.js",
|
|
30
42
|
"types": "./dist/ai/providers/rtzr/index.d.ts"
|
|
@@ -53,27 +65,33 @@
|
|
|
53
65
|
".swcrc.project-default"
|
|
54
66
|
],
|
|
55
67
|
"dependencies": {
|
|
56
|
-
"@
|
|
57
|
-
"@
|
|
68
|
+
"@logtape/logtape": "1.3.5",
|
|
69
|
+
"@logtape/pretty": "1.3.5",
|
|
70
|
+
"@logtape/redaction": "1.3.5",
|
|
71
|
+
"@logtape/fastify": "1.3.5",
|
|
72
|
+
"@aws-sdk/client-s3": "^3.958.0",
|
|
73
|
+
"bentocache": "^1.5.0",
|
|
74
|
+
"@aws-sdk/s3-request-presigner": "^3.958.0",
|
|
58
75
|
"@biomejs/js-api": "^4.0.0",
|
|
59
76
|
"@biomejs/wasm-nodejs": "^2.3.7",
|
|
60
77
|
"@fastify/cors": "^8.1.0",
|
|
61
78
|
"@fastify/formbody": "^7.1.0",
|
|
79
|
+
"@fastify/middie": "^8.3.0",
|
|
62
80
|
"@fastify/multipart": "^8",
|
|
63
81
|
"@fastify/passport": "^2.2.0",
|
|
64
82
|
"@fastify/secure-session": "^7",
|
|
65
83
|
"@fastify/static": "^7",
|
|
66
|
-
"@swc/core": "^1.13.
|
|
84
|
+
"@swc/core": "^1.13.5",
|
|
67
85
|
"chalk": "^4.1.2",
|
|
68
86
|
"chokidar": "^4.0.3",
|
|
69
87
|
"date-fns-tz": "^3.2.0",
|
|
70
|
-
"dotenv": "^
|
|
88
|
+
"dotenv": "^17.2.2",
|
|
71
89
|
"fast-deep-equal": "^3.1.3",
|
|
72
|
-
"fastify": "^4
|
|
90
|
+
"fastify": "^4",
|
|
73
91
|
"fastify-qs": "^4.0.0",
|
|
74
92
|
"fastify-sse-v2": "^4.2.1",
|
|
75
93
|
"flydrive": "^1.3.0",
|
|
76
|
-
"inflection": "^
|
|
94
|
+
"inflection": "^3.0.2",
|
|
77
95
|
"knex": "^3.1.0",
|
|
78
96
|
"mime-types": "^3.0.1",
|
|
79
97
|
"minimatch": "^10.0.3",
|
|
@@ -84,39 +102,41 @@
|
|
|
84
102
|
"qs": "^6.11.0",
|
|
85
103
|
"radashi": "^12.2.0",
|
|
86
104
|
"tsicli": "^1.0.5",
|
|
105
|
+
"vite": "7.3.0",
|
|
87
106
|
"vitest": "^4.0.10",
|
|
88
107
|
"zod": "^4.1.12",
|
|
89
108
|
"@sonamu-kit/hmr-hook": "^0.4.1",
|
|
90
109
|
"@sonamu-kit/hmr-runner": "^0.1.1",
|
|
91
|
-
"@sonamu-kit/
|
|
92
|
-
"@sonamu-kit/
|
|
110
|
+
"@sonamu-kit/tasks": "^0.1.1",
|
|
111
|
+
"@sonamu-kit/ts-loader": "^2.1.3"
|
|
93
112
|
},
|
|
94
113
|
"devDependencies": {
|
|
95
|
-
"@biomejs/biome": "^2.3.
|
|
114
|
+
"@biomejs/biome": "^2.3.10",
|
|
96
115
|
"@swc/cli": "^0.7.8",
|
|
97
|
-
"@types/inflection": "^
|
|
116
|
+
"@types/inflection": "^2.0.0",
|
|
98
117
|
"@types/mime-types": "^3.0.1",
|
|
99
|
-
"@types/node": "
|
|
118
|
+
"@types/node": "24.10.1",
|
|
100
119
|
"@types/pg": "^8.15.6",
|
|
101
120
|
"@types/prompts": "^2.0.14",
|
|
102
121
|
"@types/qs": "^6.9.7",
|
|
103
|
-
"nodemon": "3.1.10",
|
|
122
|
+
"nodemon": "^3.1.10",
|
|
104
123
|
"tsup": "^8.1.0",
|
|
105
124
|
"typescript": "^5.9.3"
|
|
106
125
|
},
|
|
107
126
|
"peerDependencies": {
|
|
108
|
-
"@ai-sdk/
|
|
109
|
-
"@ai-sdk/
|
|
110
|
-
"@ai-sdk/provider
|
|
111
|
-
"@ai-sdk/
|
|
127
|
+
"@ai-sdk/anthropic": "^3.0.0",
|
|
128
|
+
"@ai-sdk/openai": "^3.0.0",
|
|
129
|
+
"@ai-sdk/provider": "^3.0.0",
|
|
130
|
+
"@ai-sdk/provider-utils": "^4.0.0",
|
|
112
131
|
"@swc/cli": "^0.7.8",
|
|
113
132
|
"@swc/core": "^1.13.5",
|
|
114
|
-
"ai": "6.0.1",
|
|
115
|
-
"fastify": "^4
|
|
133
|
+
"ai": "^6.0.1",
|
|
134
|
+
"fastify": "^4",
|
|
116
135
|
"knex": "^3.1.0",
|
|
117
136
|
"pgvector": "^0.2.1",
|
|
118
137
|
"typescript": "^5.9.3",
|
|
119
|
-
"voyageai": "^0.0.8"
|
|
138
|
+
"voyageai": "^0.0.8",
|
|
139
|
+
"ioredis": "^5.8.2"
|
|
120
140
|
},
|
|
121
141
|
"peerDependenciesMeta": {
|
|
122
142
|
"@ai-sdk/openai": {
|
|
@@ -139,6 +159,9 @@
|
|
|
139
159
|
},
|
|
140
160
|
"voyageai": {
|
|
141
161
|
"optional": true
|
|
162
|
+
},
|
|
163
|
+
"ioredis": {
|
|
164
|
+
"optional": true
|
|
142
165
|
}
|
|
143
166
|
},
|
|
144
167
|
"scripts": {
|
package/src/ai/agents/agent.ts
CHANGED
|
@@ -1,39 +1,47 @@
|
|
|
1
|
-
import type { Tool,
|
|
1
|
+
import type { Tool, ToolExecutionOptions } from "@ai-sdk/provider-utils";
|
|
2
2
|
import { tool } from "@ai-sdk/provider-utils";
|
|
3
|
+
import { getLogger, type Logger } from "@logtape/logtape";
|
|
3
4
|
import type { Agent, ToolSet } from "ai";
|
|
4
5
|
import { ToolLoopAgent } from "ai";
|
|
5
6
|
import { AsyncLocalStorage } from "async_hooks";
|
|
6
7
|
import inflection from "inflection";
|
|
8
|
+
import { convertDomainToCategory } from "../../logger/category";
|
|
7
9
|
import type { AgentConfig, RegisteredToolDefinition, ToolDecoratorOptions } from "./types";
|
|
8
10
|
|
|
9
11
|
const toolDefinitions: RegisteredToolDefinition[] = [];
|
|
10
12
|
|
|
11
13
|
// TODO(Haze, 251205): 텍스트가 아닌 structured output일 경우에 대한 지원이 필요
|
|
12
14
|
export class BaseAgentClass<TStore> {
|
|
15
|
+
protected readonly logger: Logger;
|
|
16
|
+
|
|
13
17
|
private _als = new AsyncLocalStorage<TStore>();
|
|
14
18
|
|
|
19
|
+
constructor(public readonly agentName: string = this.constructor.name) {
|
|
20
|
+
this.logger = getLogger(convertDomainToCategory(this.agentName, "agent"));
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
public get store() {
|
|
16
24
|
return this._als.getStore();
|
|
17
25
|
}
|
|
18
26
|
|
|
19
|
-
// NOTE: [schemaSymbol] 추론 불가로 인해 캐스팅
|
|
20
27
|
protected get toolSet() {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
acc
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
return toolDefinitions
|
|
29
|
+
.filter((def) => def.from === this.constructor.name)
|
|
30
|
+
.reduce<Record<string, Tool>>((acc, def) => {
|
|
31
|
+
acc[def.name] = tool({
|
|
32
|
+
description: def.description,
|
|
33
|
+
inputSchema: def.schema.input,
|
|
34
|
+
outputSchema: def.schema.output,
|
|
35
|
+
needsApproval: def.needsApproval ?? false,
|
|
36
|
+
toModelOutput: def.toModelOutput,
|
|
37
|
+
providerOptions: def.providerOptions,
|
|
38
|
+
execute: (input: unknown, options: ToolExecutionOptions) => {
|
|
39
|
+
const bound = def.method.bind(this);
|
|
40
|
+
return bound.length >= 2 ? bound(input, options) : bound(input);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
return acc;
|
|
44
|
+
}, {});
|
|
37
45
|
}
|
|
38
46
|
|
|
39
47
|
public get tools(): ToolSet {
|
|
@@ -61,10 +69,21 @@ export function tools<INPUT, OUTPUT = unknown>(options: ToolDecoratorOptions<INP
|
|
|
61
69
|
throw new Error("Target must be a subclass of BaseAgentClass");
|
|
62
70
|
}
|
|
63
71
|
|
|
64
|
-
const method = descriptor.value;
|
|
65
72
|
const modelName = target.constructor.name.match(/(.+)Class$/)?.[1];
|
|
66
73
|
const methodName = propertyKey;
|
|
67
74
|
|
|
75
|
+
const originalMethod = descriptor.value;
|
|
76
|
+
const method = async function (this: BaseAgentClass<unknown>, ...args: unknown[]) {
|
|
77
|
+
this.logger.debug("tools: {model}.{method} with args: {args}", {
|
|
78
|
+
model: modelName,
|
|
79
|
+
method: methodName,
|
|
80
|
+
args,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return originalMethod.apply(this, args);
|
|
84
|
+
};
|
|
85
|
+
descriptor.value = method;
|
|
86
|
+
|
|
68
87
|
const defaultPath =
|
|
69
88
|
modelName !== undefined
|
|
70
89
|
? `${inflection.camelize(
|
package/src/api/base-frame.ts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
+
import { getLogger, type Logger } from "@logtape/logtape";
|
|
1
2
|
import type { Knex } from "knex";
|
|
2
3
|
import type { DBPreset } from "../database/db";
|
|
3
4
|
// Static imports kept for non-async functions (getDB, getUpsertBuilder)
|
|
4
5
|
import { DB } from "../database/db";
|
|
5
6
|
import { UpsertBuilder } from "../database/upsert-builder";
|
|
7
|
+
import { convertDomainToCategory } from "../logger/category";
|
|
6
8
|
|
|
7
9
|
export abstract class BaseFrameClass {
|
|
10
|
+
protected readonly logger: Logger;
|
|
11
|
+
|
|
12
|
+
constructor(public readonly frameName: string = this.constructor.name) {
|
|
13
|
+
this.logger = getLogger(convertDomainToCategory(this.frameName, "frame"));
|
|
14
|
+
}
|
|
15
|
+
|
|
8
16
|
getDB(which: DBPreset): Knex {
|
|
9
17
|
return DB.getDB(which);
|
|
10
18
|
}
|
package/src/api/caster.ts
CHANGED
|
@@ -65,7 +65,12 @@ export function caster(zodType: $ZodType, raw: any): any {
|
|
|
65
65
|
} else if (zodType instanceof z.ZodNullable) {
|
|
66
66
|
// nullable
|
|
67
67
|
return caster(zodType.def.innerType, raw);
|
|
68
|
-
} else if (
|
|
68
|
+
} else if (
|
|
69
|
+
zodType instanceof z.ZodDate &&
|
|
70
|
+
raw !== null &&
|
|
71
|
+
raw !== undefined &&
|
|
72
|
+
new Date(raw).toString() !== "Invalid Date"
|
|
73
|
+
) {
|
|
69
74
|
// date
|
|
70
75
|
return new Date(raw);
|
|
71
76
|
} else {
|
package/src/api/config.ts
CHANGED
|
@@ -8,6 +8,8 @@ import type { FastifyInstance, FastifyReply, FastifyRequest, FastifyServerOption
|
|
|
8
8
|
import type { QsPluginOptions } from "fastify-qs";
|
|
9
9
|
import type { SsePluginOptions } from "fastify-sse-v2/lib/types";
|
|
10
10
|
import type { Knex } from "knex";
|
|
11
|
+
import type { CacheConfig } from "../cache/types";
|
|
12
|
+
import type { SonamuLoggingOptions } from "../logger/configure";
|
|
11
13
|
import type { StorageConfig } from "../storage/types";
|
|
12
14
|
import type { WorkflowOptions } from "../tasks/workflow-manager";
|
|
13
15
|
import type { Executable, SonamuFastifyConfig } from "../types/types";
|
|
@@ -17,7 +19,7 @@ export type DatabaseConfig = Omit<Knex.Config, "connection"> & {
|
|
|
17
19
|
connection?: Knex.PgConnectionConfig;
|
|
18
20
|
};
|
|
19
21
|
|
|
20
|
-
export type SonamuConfig = {
|
|
22
|
+
export type SonamuConfig<TSinkId extends string = string, TFilterId extends string = string> = {
|
|
21
23
|
projectName?: string;
|
|
22
24
|
|
|
23
25
|
api: {
|
|
@@ -32,8 +34,8 @@ export type SonamuConfig = {
|
|
|
32
34
|
};
|
|
33
35
|
|
|
34
36
|
database: {
|
|
35
|
-
// 데이터베이스
|
|
36
|
-
database?: "
|
|
37
|
+
// 데이터베이스(pg는 pg 모듈, pgnative는 pg-native 모듈의 설치가 필요합니다.)
|
|
38
|
+
database?: "pg" | "pgnative";
|
|
37
39
|
// 기본 데이터베이스 이름
|
|
38
40
|
name: string;
|
|
39
41
|
// 모든 환경에 적용될 기본 Knex 옵션
|
|
@@ -49,6 +51,7 @@ export type SonamuConfig = {
|
|
|
49
51
|
};
|
|
50
52
|
};
|
|
51
53
|
|
|
54
|
+
logging?: false | SonamuLoggingOptions<TSinkId, TFilterId>;
|
|
52
55
|
server: SonamuServerOptions;
|
|
53
56
|
tasks?: SonamuTaskOptions;
|
|
54
57
|
};
|
|
@@ -57,7 +60,7 @@ export type SonamuServerOptions = {
|
|
|
57
60
|
// 프로젝트 외부에서 접근할 수 있는 URL. 기본값은 {server.listen.host}:{server.listen.port} 입니다.
|
|
58
61
|
baseUrl?: string;
|
|
59
62
|
|
|
60
|
-
fastify?: FastifyServerOptions
|
|
63
|
+
fastify?: Omit<FastifyServerOptions, "logger">;
|
|
61
64
|
|
|
62
65
|
listen?: {
|
|
63
66
|
port: number;
|
|
@@ -104,6 +107,27 @@ export type SonamuServerOptions = {
|
|
|
104
107
|
*/
|
|
105
108
|
storage?: StorageConfig;
|
|
106
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Cache 설정 (BentoCache 기반)
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```typescript
|
|
115
|
+
* import { drivers, store } from "sonamu/cache";
|
|
116
|
+
*
|
|
117
|
+
* cache: {
|
|
118
|
+
* default: 'main',
|
|
119
|
+
* stores: {
|
|
120
|
+
* main: store()
|
|
121
|
+
* .useL1Layer(drivers.memory({ maxSize: '100mb' }))
|
|
122
|
+
* .useL2Layer(drivers.redis({ connection: redisConnection }))
|
|
123
|
+
* .useBus(drivers.redisBus({ connection: redisConnection }))
|
|
124
|
+
* },
|
|
125
|
+
* ttl: '1h',
|
|
126
|
+
* }
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
cache?: CacheConfig;
|
|
130
|
+
|
|
107
131
|
lifecycle?: {
|
|
108
132
|
onStart?: (server: FastifyInstance) => Promise<void> | void;
|
|
109
133
|
onShutdown?: (server: FastifyInstance) => Promise<void> | void;
|
|
@@ -121,6 +145,16 @@ export type SonamuTaskOptions = {
|
|
|
121
145
|
) => Context | Promise<Context>;
|
|
122
146
|
};
|
|
123
147
|
|
|
148
|
+
export type SonamuSSROptions = {
|
|
149
|
+
/**
|
|
150
|
+
* Hydration 전략
|
|
151
|
+
* - 'none': 항상 새로 렌더링 (hydration mismatch 걱정 없음, 권장)
|
|
152
|
+
* - 'full': React hydration 시도 (서버 HTML 재사용, mismatch 주의)
|
|
153
|
+
* @default 'none'
|
|
154
|
+
*/
|
|
155
|
+
hydration?: "none" | "full";
|
|
156
|
+
};
|
|
157
|
+
|
|
124
158
|
// NOTE(Haze, 251209): config에는 T, Promise<T>, () => T, () => Promise<T>가 모두 올 수 있어야 함.
|
|
125
159
|
export function defineConfig(config: Executable<SonamuConfig>): Promise<SonamuConfig> {
|
|
126
160
|
if (typeof config === "function") {
|
package/src/api/decorators.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { getLogger } from "@logtape/logtape";
|
|
1
2
|
import assert from "assert";
|
|
2
3
|
import type { HTTPMethods } from "fastify";
|
|
3
4
|
import inflection from "inflection";
|
|
4
5
|
import { isEqual } from "radashi";
|
|
5
6
|
import type { z } from "zod";
|
|
6
|
-
import
|
|
7
|
+
import { BaseModelClass } from "../database/base-model";
|
|
7
8
|
import { DB } from "../database/db";
|
|
8
9
|
import {
|
|
9
10
|
PuriTransactionWrapper,
|
|
@@ -11,7 +12,9 @@ import {
|
|
|
11
12
|
type TransactionalOptions,
|
|
12
13
|
} from "../database/puri-wrapper";
|
|
13
14
|
import { UpsertBuilder } from "../database/upsert-builder";
|
|
15
|
+
import { convertDomainToCategory } from "../logger/category";
|
|
14
16
|
import type { ApiParam, ApiParamType } from "../types/types";
|
|
17
|
+
import { BaseFrameClass } from "./base-frame";
|
|
15
18
|
import type { UploadContext } from "./context";
|
|
16
19
|
import { Sonamu } from "./sonamu";
|
|
17
20
|
|
|
@@ -102,7 +105,7 @@ export function api(options: ApiDecoratorOptions = {}) {
|
|
|
102
105
|
...options,
|
|
103
106
|
};
|
|
104
107
|
|
|
105
|
-
return (target: DecoratorTarget, propertyKey: string) => {
|
|
108
|
+
return (target: DecoratorTarget, propertyKey: string, descriptor: PropertyDescriptor) => {
|
|
106
109
|
const modelName = target.constructor.name.match(/(.+)Class$/)?.[1];
|
|
107
110
|
assert(
|
|
108
111
|
modelName,
|
|
@@ -142,11 +145,38 @@ export function api(options: ApiDecoratorOptions = {}) {
|
|
|
142
145
|
options,
|
|
143
146
|
});
|
|
144
147
|
}
|
|
148
|
+
|
|
149
|
+
const originalMethod = descriptor.value;
|
|
150
|
+
descriptor.value = async function (this: BaseModelClass | BaseFrameClass, ...args: unknown[]) {
|
|
151
|
+
if (this instanceof BaseModelClass) {
|
|
152
|
+
getLogger(convertDomainToCategory(this.modelName, "model")).debug(
|
|
153
|
+
"api: {httpMethod} {model}.{method}",
|
|
154
|
+
{
|
|
155
|
+
httpMethod: options.httpMethod,
|
|
156
|
+
model: modelName,
|
|
157
|
+
method: methodName,
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (this instanceof BaseFrameClass) {
|
|
163
|
+
getLogger(convertDomainToCategory(this.frameName, "frame")).debug(
|
|
164
|
+
"api: {httpMethod} {model}.{method}",
|
|
165
|
+
{
|
|
166
|
+
httpMethod: options.httpMethod,
|
|
167
|
+
model: modelName,
|
|
168
|
+
method: methodName,
|
|
169
|
+
},
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return originalMethod.apply(this, args);
|
|
174
|
+
};
|
|
145
175
|
};
|
|
146
176
|
}
|
|
147
177
|
|
|
148
178
|
export function stream(options: StreamDecoratorOptions) {
|
|
149
|
-
return (target: DecoratorTarget, propertyKey: string) => {
|
|
179
|
+
return (target: DecoratorTarget, propertyKey: string, descriptor: PropertyDescriptor) => {
|
|
150
180
|
const modelName = target.constructor.name.match(/(.+)Class$/)?.[1];
|
|
151
181
|
assert(
|
|
152
182
|
modelName,
|
|
@@ -198,25 +228,53 @@ export function stream(options: StreamDecoratorOptions) {
|
|
|
198
228
|
streamOptions: options,
|
|
199
229
|
});
|
|
200
230
|
}
|
|
231
|
+
|
|
232
|
+
const originalMethod = descriptor.value;
|
|
233
|
+
descriptor.value = async function (this: BaseModelClass | BaseFrameClass, ...args: unknown[]) {
|
|
234
|
+
if (this instanceof BaseModelClass) {
|
|
235
|
+
getLogger(convertDomainToCategory(this.modelName, "model")).debug(
|
|
236
|
+
"stream: {model}.{method}",
|
|
237
|
+
{
|
|
238
|
+
model: modelName,
|
|
239
|
+
method: methodName,
|
|
240
|
+
},
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (this instanceof BaseFrameClass) {
|
|
245
|
+
getLogger(convertDomainToCategory(this.frameName, "frame")).debug(
|
|
246
|
+
"stream: {model}.{method}",
|
|
247
|
+
{
|
|
248
|
+
model: modelName,
|
|
249
|
+
method: methodName,
|
|
250
|
+
},
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return originalMethod.apply(this, args);
|
|
255
|
+
};
|
|
201
256
|
};
|
|
202
257
|
}
|
|
203
258
|
|
|
204
259
|
export function transactional(options: TransactionalOptions = {}) {
|
|
205
260
|
const { isolation, readOnly, dbPreset = "w" } = options;
|
|
206
261
|
|
|
207
|
-
return (
|
|
262
|
+
return (target: DecoratorTarget, propertyKey: string, descriptor: PropertyDescriptor) => {
|
|
208
263
|
const originalMethod = descriptor.value;
|
|
264
|
+
const modelName = target.constructor.name.match(/(.+)Class$/)?.[1];
|
|
265
|
+
assert(
|
|
266
|
+
modelName,
|
|
267
|
+
`modelName is required on @stream decorator on ${target.constructor.name}.${propertyKey}`,
|
|
268
|
+
);
|
|
269
|
+
const methodName = propertyKey;
|
|
209
270
|
|
|
210
271
|
descriptor.value = async function (this: BaseModelClass, ...args: unknown[]) {
|
|
211
|
-
|
|
272
|
+
this.logger.debug("transactional: {model}.{method}", {
|
|
273
|
+
model: modelName,
|
|
274
|
+
method: methodName,
|
|
275
|
+
});
|
|
212
276
|
|
|
213
|
-
|
|
214
|
-
if (existingContext) {
|
|
215
|
-
// 해당 preset의 트랜잭션이 이미 있으면 재사용
|
|
216
|
-
if (existingContext.getTransaction(dbPreset)) {
|
|
217
|
-
return originalMethod.apply(this, args);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
277
|
+
const existingContext = DB.transactionStorage.getStore();
|
|
220
278
|
|
|
221
279
|
// AsyncLocalStorage 컨텍스트 없거나 해당 preset의 트랜잭션이 없으면 새로 시작
|
|
222
280
|
const startTransaction = async () => {
|
|
@@ -224,6 +282,7 @@ export function transactional(options: TransactionalOptions = {}) {
|
|
|
224
282
|
|
|
225
283
|
return puri.knex.transaction(
|
|
226
284
|
async (trx) => {
|
|
285
|
+
this.logger.debug("new transaction context: {dbPreset}", { dbPreset });
|
|
227
286
|
const trxWrapper = new PuriTransactionWrapper(trx, new UpsertBuilder());
|
|
228
287
|
// TransactionContext에 트랜잭션 저장
|
|
229
288
|
DB.getTransactionContext().setTransaction(dbPreset, trxWrapper);
|
|
@@ -232,6 +291,7 @@ export function transactional(options: TransactionalOptions = {}) {
|
|
|
232
291
|
return await originalMethod.apply(this, args);
|
|
233
292
|
} finally {
|
|
234
293
|
// 트랜잭션 제거
|
|
294
|
+
this.logger.debug("delete transaction context: {dbPreset}", { dbPreset });
|
|
235
295
|
DB.getTransactionContext().deleteTransaction(dbPreset);
|
|
236
296
|
}
|
|
237
297
|
},
|
|
@@ -242,10 +302,16 @@ export function transactional(options: TransactionalOptions = {}) {
|
|
|
242
302
|
// AsyncLocalStorage 컨텍스트가 없으면 새로 생성
|
|
243
303
|
if (!existingContext) {
|
|
244
304
|
return DB.runWithTransaction(startTransaction);
|
|
245
|
-
} else {
|
|
246
|
-
// 컨텍스트는 있지만 이 preset의 트랜잭션은 없는 경우 (같은 컨텍스트 내에서 실행)
|
|
247
|
-
return startTransaction();
|
|
248
305
|
}
|
|
306
|
+
|
|
307
|
+
// 이미 AsyncLocalStorage 컨텍스트 안에 있는지 확인 후 해당 preset의 트랜잭션이 이미 있으면 재사용
|
|
308
|
+
if (existingContext?.getTransaction(dbPreset)) {
|
|
309
|
+
this.logger.debug("reuse transaction context: {dbPreset}", { dbPreset });
|
|
310
|
+
return originalMethod.apply(this, args);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// 컨텍스트는 있지만 이 preset의 트랜잭션은 없는 경우 (같은 컨텍스트 내에서 실행)
|
|
314
|
+
return startTransaction();
|
|
249
315
|
};
|
|
250
316
|
|
|
251
317
|
return descriptor;
|
|
@@ -258,14 +324,14 @@ export function transactional(options: TransactionalOptions = {}) {
|
|
|
258
324
|
* @returns
|
|
259
325
|
*/
|
|
260
326
|
export function upload(options: UploadDecoratorOptions = {}) {
|
|
261
|
-
return (
|
|
327
|
+
return (target: DecoratorTarget, propertyKey: string, descriptor: PropertyDescriptor) => {
|
|
262
328
|
const originalMethod = descriptor.value;
|
|
263
|
-
const modelName =
|
|
329
|
+
const modelName = target.constructor.name.match(/(.+)Class$/)?.[1];
|
|
264
330
|
assert(
|
|
265
331
|
modelName,
|
|
266
|
-
`modelName is required on @upload decorator on ${
|
|
332
|
+
`modelName is required on @upload decorator on ${target.constructor.name}.${propertyKey}`,
|
|
267
333
|
);
|
|
268
|
-
const methodName =
|
|
334
|
+
const methodName = propertyKey;
|
|
269
335
|
|
|
270
336
|
// registeredApis에서 해당 API 찾아서 uploadOptions 추가
|
|
271
337
|
const existingApi = registeredApis.find(
|
|
@@ -287,7 +353,27 @@ export function upload(options: UploadDecoratorOptions = {}) {
|
|
|
287
353
|
});
|
|
288
354
|
}
|
|
289
355
|
|
|
290
|
-
descriptor.value = async function (this: BaseModelClass, ...args: unknown[]) {
|
|
356
|
+
descriptor.value = async function (this: BaseModelClass | BaseFrameClass, ...args: unknown[]) {
|
|
357
|
+
if (this instanceof BaseModelClass) {
|
|
358
|
+
getLogger(convertDomainToCategory(this.modelName, "model")).debug(
|
|
359
|
+
"upload: {model}.{method}",
|
|
360
|
+
{
|
|
361
|
+
model: modelName,
|
|
362
|
+
method: methodName,
|
|
363
|
+
},
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (this instanceof BaseFrameClass) {
|
|
368
|
+
getLogger(convertDomainToCategory(this.frameName, "frame")).debug(
|
|
369
|
+
"upload: {model}.{method}",
|
|
370
|
+
{
|
|
371
|
+
model: modelName,
|
|
372
|
+
method: methodName,
|
|
373
|
+
},
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
291
377
|
const { request } = Sonamu.getContext();
|
|
292
378
|
const uploadContext: UploadContext = {
|
|
293
379
|
file: undefined,
|
package/src/api/index.ts
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type SonamuSecrets = {
|
|
2
|
+
anthropic_api_key?: string;
|
|
3
|
+
voyage_api_key?: string;
|
|
4
|
+
openai_api_key?: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function getSecrets(): SonamuSecrets {
|
|
8
|
+
const secrets: SonamuSecrets = {};
|
|
9
|
+
const secretKeys: (keyof SonamuSecrets)[] = [
|
|
10
|
+
"anthropic_api_key",
|
|
11
|
+
"voyage_api_key",
|
|
12
|
+
"openai_api_key",
|
|
13
|
+
] as const;
|
|
14
|
+
|
|
15
|
+
for (const key of secretKeys) {
|
|
16
|
+
const envKey = key.toUpperCase();
|
|
17
|
+
if (envKey in process.env) {
|
|
18
|
+
secrets[key] = process.env[envKey];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return secrets;
|
|
23
|
+
}
|