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.
Files changed (200) hide show
  1. package/dist/ai/agents/agent.d.ts +6 -1
  2. package/dist/ai/agents/agent.d.ts.map +1 -1
  3. package/dist/ai/agents/agent.js +20 -5
  4. package/dist/api/base-frame.d.ts +4 -0
  5. package/dist/api/base-frame.d.ts.map +1 -1
  6. package/dist/api/base-frame.js +9 -1
  7. package/dist/api/caster.d.ts.map +1 -1
  8. package/dist/api/caster.js +2 -2
  9. package/dist/api/config.d.ts +35 -3
  10. package/dist/api/config.d.ts.map +1 -1
  11. package/dist/api/config.js +1 -1
  12. package/dist/api/decorators.d.ts +4 -4
  13. package/dist/api/decorators.d.ts.map +1 -1
  14. package/dist/api/decorators.js +80 -18
  15. package/dist/api/index.d.ts +1 -0
  16. package/dist/api/index.d.ts.map +1 -1
  17. package/dist/api/index.js +2 -1
  18. package/dist/api/secret.d.ts +7 -0
  19. package/dist/api/secret.d.ts.map +1 -0
  20. package/dist/api/secret.js +17 -0
  21. package/dist/api/sonamu.d.ts +17 -8
  22. package/dist/api/sonamu.d.ts.map +1 -1
  23. package/dist/api/sonamu.js +265 -47
  24. package/dist/cache/cache-manager.d.ts +11 -0
  25. package/dist/cache/cache-manager.d.ts.map +1 -0
  26. package/dist/cache/cache-manager.js +22 -0
  27. package/dist/cache/decorator.d.ts +31 -0
  28. package/dist/cache/decorator.d.ts.map +1 -0
  29. package/dist/cache/decorator.js +86 -0
  30. package/dist/cache/drivers.d.ts +33 -0
  31. package/dist/cache/drivers.d.ts.map +1 -0
  32. package/dist/cache/drivers.js +36 -0
  33. package/dist/cache/index.d.ts +4 -0
  34. package/dist/cache/index.d.ts.map +1 -0
  35. package/dist/cache/index.js +8 -0
  36. package/dist/cache/types.d.ts +28 -0
  37. package/dist/cache/types.d.ts.map +1 -0
  38. package/dist/cache/types.js +6 -0
  39. package/dist/database/base-model.d.ts +4 -2
  40. package/dist/database/base-model.d.ts.map +1 -1
  41. package/dist/database/base-model.js +9 -4
  42. package/dist/database/code-generator.d.ts +3 -1
  43. package/dist/database/code-generator.d.ts.map +1 -1
  44. package/dist/database/code-generator.js +3 -2
  45. package/dist/database/db.d.ts +1 -1
  46. package/dist/database/db.d.ts.map +1 -1
  47. package/dist/database/db.js +5 -5
  48. package/dist/database/knex.d.ts +3 -0
  49. package/dist/database/knex.d.ts.map +1 -0
  50. package/dist/database/knex.js +29 -0
  51. package/dist/database/puri.types.d.ts.map +1 -1
  52. package/dist/database/puri.types.js +1 -1
  53. package/dist/database/upsert-builder.d.ts.map +1 -1
  54. package/dist/database/upsert-builder.js +49 -5
  55. package/dist/index.d.ts +4 -0
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +4 -1
  58. package/dist/logger/category.d.ts +4 -0
  59. package/dist/logger/category.d.ts.map +1 -0
  60. package/dist/logger/category.js +34 -0
  61. package/dist/logger/configure.d.ts +9 -0
  62. package/dist/logger/configure.d.ts.map +1 -0
  63. package/dist/logger/configure.js +115 -0
  64. package/dist/migration/code-generation.d.ts +5 -1
  65. package/dist/migration/code-generation.d.ts.map +1 -1
  66. package/dist/migration/code-generation.js +13 -7
  67. package/dist/migration/migrator.d.ts +1 -1
  68. package/dist/migration/migrator.d.ts.map +1 -1
  69. package/dist/migration/migrator.js +7 -7
  70. package/dist/migration/postgresql-schema-reader.d.ts.map +1 -1
  71. package/dist/migration/postgresql-schema-reader.js +5 -3
  72. package/dist/naite/naite.d.ts +0 -4
  73. package/dist/naite/naite.d.ts.map +1 -1
  74. package/dist/naite/naite.js +11 -19
  75. package/dist/ssr/index.d.ts +4 -0
  76. package/dist/ssr/index.d.ts.map +1 -0
  77. package/dist/ssr/index.js +4 -0
  78. package/dist/ssr/registry.d.ts +10 -0
  79. package/dist/ssr/registry.d.ts.map +1 -0
  80. package/dist/ssr/registry.js +43 -0
  81. package/dist/ssr/renderer.d.ts +6 -0
  82. package/dist/ssr/renderer.d.ts.map +1 -0
  83. package/dist/ssr/renderer.js +70 -0
  84. package/dist/ssr/types.d.ts +19 -0
  85. package/dist/ssr/types.d.ts.map +1 -0
  86. package/dist/ssr/types.js +4 -0
  87. package/dist/syncer/syncer.d.ts +1 -0
  88. package/dist/syncer/syncer.d.ts.map +1 -1
  89. package/dist/syncer/syncer.js +58 -1
  90. package/dist/tasks/decorator.d.ts +1 -0
  91. package/dist/tasks/decorator.d.ts.map +1 -1
  92. package/dist/tasks/decorator.js +9 -7
  93. package/dist/tasks/step-wrapper.d.ts +5 -0
  94. package/dist/tasks/step-wrapper.d.ts.map +1 -1
  95. package/dist/tasks/step-wrapper.js +11 -6
  96. package/dist/tasks/workflow-manager.d.ts +2 -0
  97. package/dist/tasks/workflow-manager.d.ts.map +1 -1
  98. package/dist/tasks/workflow-manager.js +5 -2
  99. package/dist/template/implementations/entry-server.template.d.ts +17 -0
  100. package/dist/template/implementations/entry-server.template.d.ts.map +1 -0
  101. package/dist/template/implementations/entry-server.template.js +78 -0
  102. package/dist/template/implementations/model.template.d.ts.map +1 -1
  103. package/dist/template/implementations/model.template.js +5 -3
  104. package/dist/template/implementations/queries.template.d.ts +17 -0
  105. package/dist/template/implementations/queries.template.d.ts.map +1 -0
  106. package/dist/template/implementations/queries.template.js +83 -0
  107. package/dist/template/implementations/view_enums_select.template.d.ts.map +1 -1
  108. package/dist/template/implementations/view_enums_select.template.js +34 -20
  109. package/dist/template/implementations/view_form.template.d.ts +2 -1
  110. package/dist/template/implementations/view_form.template.d.ts.map +1 -1
  111. package/dist/template/implementations/view_form.template.js +301 -129
  112. package/dist/template/implementations/view_id_async_select.template.d.ts.map +1 -1
  113. package/dist/template/implementations/view_id_async_select.template.js +136 -57
  114. package/dist/template/implementations/view_list.template.d.ts +2 -0
  115. package/dist/template/implementations/view_list.template.d.ts.map +1 -1
  116. package/dist/template/implementations/view_list.template.js +392 -227
  117. package/dist/template/implementations/view_search_input.template.d.ts.map +1 -1
  118. package/dist/template/implementations/view_search_input.template.js +46 -30
  119. package/dist/template/zod-converter.d.ts.map +1 -1
  120. package/dist/template/zod-converter.js +2 -2
  121. package/dist/testing/bootstrap.d.ts +28 -0
  122. package/dist/testing/bootstrap.d.ts.map +1 -0
  123. package/dist/testing/bootstrap.js +120 -0
  124. package/dist/testing/fixture-loader.d.ts +21 -0
  125. package/dist/testing/fixture-loader.d.ts.map +1 -0
  126. package/dist/testing/fixture-loader.js +28 -0
  127. package/dist/testing/fixture-manager.d.ts +1 -1
  128. package/dist/testing/fixture-manager.d.ts.map +1 -1
  129. package/dist/testing/fixture-manager.js +7 -7
  130. package/dist/testing/index.d.ts +4 -0
  131. package/dist/testing/index.d.ts.map +1 -0
  132. package/dist/testing/index.js +5 -0
  133. package/dist/testing/naite-vitest-reporter.d.ts +12 -0
  134. package/dist/testing/naite-vitest-reporter.d.ts.map +1 -0
  135. package/dist/testing/naite-vitest-reporter.js +17 -0
  136. package/dist/types/types.d.ts +5 -6
  137. package/dist/types/types.d.ts.map +1 -1
  138. package/dist/types/types.js +7 -8
  139. package/dist/ui/ai-client.d.ts +3 -1
  140. package/dist/ui/ai-client.d.ts.map +1 -1
  141. package/dist/ui/ai-client.js +27 -8
  142. package/dist/ui-web/assets/index-CTYv3qL6.js +92 -0
  143. package/dist/ui-web/index.html +1 -1
  144. package/package.json +43 -20
  145. package/src/ai/agents/agent.ts +38 -19
  146. package/src/api/base-frame.ts +8 -0
  147. package/src/api/caster.ts +6 -1
  148. package/src/api/config.ts +38 -4
  149. package/src/api/decorators.ts +106 -20
  150. package/src/api/index.ts +1 -0
  151. package/src/api/secret.ts +23 -0
  152. package/src/api/sonamu.ts +334 -61
  153. package/src/cache/cache-manager.ts +23 -0
  154. package/src/cache/decorator.ts +116 -0
  155. package/src/cache/drivers.ts +42 -0
  156. package/src/cache/index.ts +16 -0
  157. package/src/cache/types.ts +32 -0
  158. package/src/database/base-model.ts +7 -3
  159. package/src/database/code-generator.ts +3 -1
  160. package/src/database/db.ts +5 -5
  161. package/src/database/knex.ts +34 -0
  162. package/src/database/puri.types.ts +2 -3
  163. package/src/database/upsert-builder.ts +58 -4
  164. package/src/index.ts +4 -0
  165. package/src/logger/category.ts +42 -0
  166. package/src/logger/configure.ts +132 -0
  167. package/src/migration/code-generation.ts +19 -6
  168. package/src/migration/migrator.ts +7 -6
  169. package/src/migration/postgresql-schema-reader.ts +7 -2
  170. package/src/naite/naite.ts +10 -18
  171. package/src/shared/web.shared.ts.txt +1 -1
  172. package/src/ssr/index.ts +13 -0
  173. package/src/ssr/registry.ts +52 -0
  174. package/src/ssr/renderer.ts +105 -0
  175. package/src/ssr/types.ts +20 -0
  176. package/src/syncer/syncer.ts +59 -0
  177. package/src/tasks/decorator.ts +20 -4
  178. package/src/tasks/step-wrapper.ts +14 -5
  179. package/src/tasks/workflow-manager.ts +9 -1
  180. package/src/template/implementations/entry-server.template.ts +81 -0
  181. package/src/template/implementations/model.template.ts +4 -2
  182. package/src/template/implementations/queries.template.ts +111 -0
  183. package/src/template/implementations/view_enums_select.template.ts +33 -19
  184. package/src/template/implementations/view_form.template.ts +324 -145
  185. package/src/template/implementations/view_id_async_select.template.ts +145 -56
  186. package/src/template/implementations/view_list.template.ts +446 -236
  187. package/src/template/implementations/view_search_input.template.ts +45 -29
  188. package/src/template/zod-converter.ts +4 -1
  189. package/src/testing/bootstrap.ts +176 -0
  190. package/src/testing/fixture-loader.ts +28 -0
  191. package/src/testing/fixture-manager.ts +7 -6
  192. package/src/testing/index.ts +3 -0
  193. package/src/testing/naite-vitest-reporter.ts +18 -0
  194. package/src/types/types.ts +4 -5
  195. package/src/ui/ai-client.ts +82 -50
  196. package/dist/template/implementations/view_enums_dropdown.template.d.ts +0 -17
  197. package/dist/template/implementations/view_enums_dropdown.template.d.ts.map +0 -1
  198. package/dist/template/implementations/view_enums_dropdown.template.js +0 -50
  199. package/dist/ui-web/assets/index-B87IyofX.js +0 -92
  200. package/src/template/implementations/view_enums_dropdown.template.ts +0 -53
@@ -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-B87IyofX.js"></script>
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.21",
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
- "@aws-sdk/client-s3": "^3.921.0",
57
- "@aws-sdk/s3-request-presigner": "^3.921.0",
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.19",
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": "^16.0.2",
88
+ "dotenv": "^17.2.2",
71
89
  "fast-deep-equal": "^3.1.3",
72
- "fastify": "^4.23.2",
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": "^1.13.2",
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/ts-loader": "^2.1.3",
92
- "@sonamu-kit/tasks": "^0.1.1"
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.7",
114
+ "@biomejs/biome": "^2.3.10",
96
115
  "@swc/cli": "^0.7.8",
97
- "@types/inflection": "^1.13.0",
116
+ "@types/inflection": "^2.0.0",
98
117
  "@types/mime-types": "^3.0.1",
99
- "@types/node": "^22",
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/openai": "3.0.0",
109
- "@ai-sdk/provider": "3.0.0",
110
- "@ai-sdk/provider-utils": "4.0.0",
111
- "@ai-sdk/anthropic": "3.0.0",
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.23.2",
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": {
@@ -1,39 +1,47 @@
1
- import type { Tool, ToolCallOptions } from "@ai-sdk/provider-utils";
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
- const targeted = toolDefinitions.filter((def) => def.from === this.constructor.name);
22
- return targeted.reduce<Record<string, Tool>>((acc, def) => {
23
- acc[def.name] = tool({
24
- description: def.description,
25
- inputSchema: def.schema.input,
26
- outputSchema: def.schema.output,
27
- needsApproval: def.needsApproval ?? false,
28
- toModelOutput: def.toModelOutput,
29
- providerOptions: def.providerOptions,
30
- execute: (input: unknown, options: ToolCallOptions) => {
31
- const bound = def.method.bind(this);
32
- return bound.length >= 2 ? bound(input, options) : bound(input);
33
- },
34
- });
35
- return acc;
36
- }, {}) as ToolSet;
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(
@@ -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 (zodType instanceof z.ZodDate && new Date(raw).toString() !== "Invalid Date") {
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?: "postgresql";
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") {
@@ -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 type { BaseModelClass } from "../database/base-model";
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 (_target: DecoratorTarget, _propertyKey: string, descriptor: PropertyDescriptor) => {
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
- const existingContext = DB.transactionStorage.getStore();
272
+ this.logger.debug("transactional: {model}.{method}", {
273
+ model: modelName,
274
+ method: methodName,
275
+ });
212
276
 
213
- // 이미 AsyncLocalStorage 컨텍스트 안에 있는지 확인
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 (_target: DecoratorTarget, _propertyKey: string, descriptor: PropertyDescriptor) => {
327
+ return (target: DecoratorTarget, propertyKey: string, descriptor: PropertyDescriptor) => {
262
328
  const originalMethod = descriptor.value;
263
- const modelName = _target.constructor.name.match(/(.+)Class$/)?.[1];
329
+ const modelName = target.constructor.name.match(/(.+)Class$/)?.[1];
264
330
  assert(
265
331
  modelName,
266
- `modelName is required on @upload decorator on ${_target.constructor.name}.${_propertyKey}`,
332
+ `modelName is required on @upload decorator on ${target.constructor.name}.${propertyKey}`,
267
333
  );
268
- const methodName = _propertyKey;
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
@@ -2,4 +2,5 @@ export * from "./caster";
2
2
  export * from "./code-converters";
3
3
  export * from "./context";
4
4
  export * from "./decorators";
5
+ export * from "./secret";
5
6
  export * from "./sonamu";
@@ -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
+ }