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
@@ -20,44 +20,60 @@ export class Template__view_search_input extends Template {
20
20
  return {
21
21
  ...this.getTargetAndPath(names),
22
22
  body: `
23
- import React from "react";
23
+ import { Button, Input } from "@sonamu-kit/react-components/components";
24
+ import type React from "react";
24
25
  import { useState } from "react";
25
- import { DropdownProps, Input, InputProps } from "semantic-ui-react";
26
- import { ${names.capital}SearchFieldDropdown } from "@/components/${names.fs}/${names.capital}SearchFieldDropdown";
26
+ import { ${names.capital}SearchFieldSelect } from "@/components/${names.fs}/${names.capital}SearchFieldSelect";
27
+ import SearchIcon from "~icons/lucide/search";
28
+
29
+ export type ${names.capital}SearchInputProps = {
30
+ input: {
31
+ value?: string;
32
+ onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
33
+ };
34
+ dropdown: {
35
+ value?: string;
36
+ onChange?: (e: React.ChangeEvent<HTMLSelectElement>) => void;
37
+ };
38
+ };
27
39
 
28
40
  export function ${names.capital}SearchInput({
29
- input: { value: inputValue, onChange: inputOnChange, ...inputProps },
41
+ input: { value: inputValue, onChange: inputOnChange },
30
42
  dropdown: dropdownProps,
31
- }: {
32
- input: InputProps;
33
- dropdown: DropdownProps;
34
- }) {
35
- const [keyword, setKeyword] = useState<string>(inputValue ?? '');
43
+ }: ${names.capital}SearchInputProps) {
44
+ const [keyword, setKeyword] = useState<string>(inputValue ?? "");
45
+
46
+ const handleSearch = () => {
47
+ if (inputOnChange) {
48
+ const syntheticEvent = {
49
+ target: { value: keyword },
50
+ currentTarget: { value: keyword },
51
+ } as React.ChangeEvent<HTMLInputElement>;
52
+ inputOnChange(syntheticEvent);
53
+ }
54
+ };
36
55
 
37
- const handleKeyDown = (e: { key: string }) => {
38
- if (inputOnChange && e.key === 'Enter') {
39
- inputOnChange(e as any, {
40
- value: keyword,
41
- });
56
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
57
+ if (e.key === "Enter") {
58
+ handleSearch();
42
59
  }
43
60
  };
44
61
 
45
62
  return (
46
- <Input
47
- size="small"
48
- placeholder="검색..."
49
- style={{ margin: 0 }}
50
- label={<${names.capital}SearchFieldDropdown {...dropdownProps} />}
51
- labelPosition="left"
52
- action={{
53
- icon: 'search',
54
- onClick: () => handleKeyDown({ key: 'Enter' }),
55
- }}
56
- {...inputProps}
57
- value={keyword}
58
- onChange={(e, { value }) => setKeyword(value)}
59
- onKeyDown={handleKeyDown}
60
- />
63
+ <div className="flex items-center gap-1">
64
+ <${names.capital}SearchFieldSelect {...dropdownProps} />
65
+ <div className="relative flex items-center">
66
+ <Input
67
+ type="text"
68
+ placeholder="검색..."
69
+ className="h-8 w-[200px] pr-8"
70
+ value={keyword}
71
+ onChange={(e) => setKeyword(e.target.value)}
72
+ onKeyDown={handleKeyDown}
73
+ />
74
+ <Button type="button" variant="ghost" onClick={handleSearch} icon={<SearchIcon />} />
75
+ </div>
76
+ </div>
61
77
  );
62
78
  }
63
79
  `.trim(),
@@ -513,7 +513,10 @@ export function zodTypeToRenderingNode(
513
513
  };
514
514
  } else if (zodType instanceof z.ZodArray) {
515
515
  const innerType = (zodType as z.ZodArray<z.ZodTypeAny>).def.element;
516
- if (innerType instanceof z.ZodString && baseKey.includes("images")) {
516
+ if (
517
+ innerType instanceof z.ZodString &&
518
+ (baseKey.includes("images") || baseKey.includes("image"))
519
+ ) {
517
520
  return {
518
521
  ...def,
519
522
  renderType: "array-images",
@@ -0,0 +1,176 @@
1
+ import {
2
+ afterAll,
3
+ afterEach,
4
+ beforeAll,
5
+ beforeEach,
6
+ type TestFunction,
7
+ type TestOptions,
8
+ type VitestUtils,
9
+ test as vitestTest,
10
+ } from "vitest";
11
+ import type { AuthContext, Context } from "../api/context";
12
+ import { Sonamu } from "../api/sonamu";
13
+ import { DB } from "../database/db";
14
+ import { Naite } from "../naite/naite";
15
+ import { NaiteReporter } from "../naite/naite-reporter";
16
+
17
+ export function bootstrap(vi: VitestUtils) {
18
+ beforeAll(async () => {
19
+ await Sonamu.initForTesting();
20
+ });
21
+ beforeEach(async () => {
22
+ await DB.createTestTransaction();
23
+ });
24
+ afterEach(async ({ task }) => {
25
+ vi.useRealTimers();
26
+ await DB.clearTestTransaction();
27
+
28
+ await NaiteReporter.reportTestResult({
29
+ suiteName: task.suite?.name ?? "(no suite)",
30
+ suiteFilePath: task.file?.filepath,
31
+ testName: task.name,
32
+ testFilePath: task.file?.filepath ?? "",
33
+ testLine: task.location?.line ?? 0,
34
+ status: task.result?.state ?? "pass",
35
+ duration: task.result?.duration ?? 0,
36
+ error: task.result?.errors?.[0]
37
+ ? {
38
+ message: task.result.errors[0].message,
39
+ stack: task.result.errors[0].stack,
40
+ }
41
+ : undefined,
42
+ traces: task.meta?.traces ?? [],
43
+ });
44
+ });
45
+ afterAll(() => {});
46
+ }
47
+
48
+ function getMockContext(): Context {
49
+ return {
50
+ ip: "127.0.0.1",
51
+ session: {},
52
+ user: null,
53
+ passport: {
54
+ login: async () => {},
55
+ logout: () => {},
56
+ },
57
+ naiteStore: Naite.createStore(),
58
+ } as unknown as Context;
59
+ }
60
+
61
+ export async function runWithContext(context: Context | null, fn: () => Promise<void>) {
62
+ // Sonamu.asyncLocalStorage.run으로 context 설정
63
+ await Sonamu.asyncLocalStorage.run({ context: context ?? getMockContext() }, fn);
64
+ }
65
+
66
+ export async function runWithMockContext(fn: () => Promise<void>) {
67
+ await runWithContext(getMockContext(), fn);
68
+ }
69
+
70
+ declare module "vitest" {
71
+ interface TaskMeta {
72
+ traces: {
73
+ key: string;
74
+ // biome-ignore lint/suspicious/noExplicitAny: expect와 호응하도록 any를 허용함.
75
+ value: any;
76
+ filePath: string;
77
+ lineNumber: number;
78
+ at: string;
79
+ }[];
80
+ }
81
+ }
82
+
83
+ export const test = Object.assign(
84
+ async (title: string, fn: TestFunction<object>, options?: TestOptions) => {
85
+ return vitestTest(title, options, async (context) => {
86
+ await runWithMockContext(async () => {
87
+ try {
88
+ await fn(context);
89
+ context.task.meta.traces = Naite.getAllTraces();
90
+ } catch (e: unknown) {
91
+ context.task.meta.traces = Naite.getAllTraces();
92
+ throw e;
93
+ }
94
+ });
95
+ });
96
+ },
97
+ {
98
+ skip: async (title: string, fn: TestFunction<object>, options?: TestOptions) =>
99
+ vitestTest.skip(title, options, fn),
100
+ only: async (title: string, fn: TestFunction<object>, options?: TestOptions) => {
101
+ return vitestTest.only(title, options, async (context) => {
102
+ await runWithMockContext(async () => {
103
+ try {
104
+ await fn(context);
105
+ context.task.meta.traces = Naite.getAllTraces();
106
+ } catch (e: unknown) {
107
+ context.task.meta.traces = Naite.getAllTraces();
108
+ throw e;
109
+ }
110
+ });
111
+ });
112
+ },
113
+ todo: (title: string) => vitestTest.todo(title),
114
+ each: vitestTest.each.bind(vitestTest) as typeof vitestTest.each,
115
+ },
116
+ );
117
+
118
+ export const testAs = Object.assign(
119
+ async <User extends AuthContext["user"]>(
120
+ user: User,
121
+ title: string,
122
+ fn: TestFunction<object>,
123
+ options?: TestOptions,
124
+ ) => {
125
+ return vitestTest(title, options, async (context) => {
126
+ await runWithContext(
127
+ {
128
+ ...getMockContext(),
129
+ user,
130
+ },
131
+ async () => {
132
+ try {
133
+ await fn(context);
134
+ context.task.meta.traces = Naite.getAllTraces();
135
+ } catch (e: unknown) {
136
+ context.task.meta.traces = Naite.getAllTraces();
137
+ throw e;
138
+ }
139
+ },
140
+ );
141
+ });
142
+ },
143
+ {
144
+ skip: async <User extends AuthContext["user"]>(
145
+ _user: User,
146
+ title: string,
147
+ fn: TestFunction<object>,
148
+ options?: TestOptions,
149
+ ) => vitestTest.skip(title, options, fn),
150
+ only: async <User extends AuthContext["user"]>(
151
+ user: User,
152
+ title: string,
153
+ fn: TestFunction<object>,
154
+ options?: TestOptions,
155
+ ) => {
156
+ return vitestTest.only(title, options, async (context) => {
157
+ await runWithContext(
158
+ {
159
+ ...getMockContext(),
160
+ user,
161
+ },
162
+ async () => {
163
+ try {
164
+ await fn(context);
165
+ context.task.meta.traces = Naite.getAllTraces();
166
+ } catch (e: unknown) {
167
+ context.task.meta.traces = Naite.getAllTraces();
168
+ throw e;
169
+ }
170
+ },
171
+ );
172
+ });
173
+ },
174
+ todo: (title: string) => vitestTest.todo(title),
175
+ },
176
+ );
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Fixture Loader Factory
3
+ *
4
+ * 테스트에서 사용할 fixture를 로드하는 함수를 생성
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * // fixture.ts
9
+ * import { createFixtureLoader } from "sonamu/test";
10
+ *
11
+ * export const loadFixtures = createFixtureLoader({
12
+ * company01: async () => CompanyModel.findById("A", 1),
13
+ * user01: async () => UserModel.findById("A", 1),
14
+ * });
15
+ *
16
+ * // test.ts
17
+ * const { company01, user01 } = await loadFixtures(["company01", "user01"]);
18
+ * ```
19
+ */
20
+ export function createFixtureLoader<T extends Record<string, () => Promise<unknown>>>(loaders: T) {
21
+ return async function loadFixtures<K extends keyof T>(
22
+ names: K[],
23
+ ): Promise<{ [P in K]: Awaited<ReturnType<T[P]>> }> {
24
+ return Object.fromEntries(
25
+ await Promise.all(names.map(async (name) => [name, await loaders[name]()])),
26
+ );
27
+ };
28
+ }
@@ -3,12 +3,13 @@ import chalk from "chalk";
3
3
  import { execSync } from "child_process";
4
4
  import { readFileSync, writeFileSync } from "fs";
5
5
  import inflection from "inflection";
6
- import knex, { type Knex } from "knex";
6
+ import type { Knex } from "knex";
7
7
  import { unique } from "radashi";
8
8
  import { inspect } from "util";
9
9
  import { Sonamu } from "../api";
10
10
  import { BaseModel } from "../database/base-model";
11
11
  import type { SonamuDBConfig } from "../database/db";
12
+ import { createKnexInstance } from "../database/knex";
12
13
  import { type UBRef, UpsertBuilder } from "../database/upsert-builder";
13
14
  import type { Entity } from "../entity/entity";
14
15
  import { EntityManager } from "../entity/entity-manager";
@@ -86,8 +87,8 @@ export class FixtureManagerClass {
86
87
  }
87
88
  }
88
89
 
89
- this.tdb = knex(Sonamu.dbConfig.test);
90
- this.fdb = knex(Sonamu.dbConfig.fixture);
90
+ this.tdb = createKnexInstance(Sonamu.dbConfig.test);
91
+ this.fdb = createKnexInstance(Sonamu.dbConfig.fixture);
91
92
  }
92
93
 
93
94
  async getChecksum(db: Knex, tableName: string) {
@@ -254,8 +255,8 @@ export class FixtureManagerClass {
254
255
  searchOptions: FixtureSearchOptions,
255
256
  duplicateCheck?: DuplicateCheckOptions,
256
257
  ) {
257
- const sourceDB = knex(Sonamu.dbConfig[sourceDBName]);
258
- const targetDB = knex(Sonamu.dbConfig[targetDBName]);
258
+ const sourceDB = createKnexInstance(Sonamu.dbConfig[sourceDBName]);
259
+ const targetDB = createKnexInstance(Sonamu.dbConfig[targetDBName]);
259
260
 
260
261
  const { entityId, field, value, searchType } = searchOptions;
261
262
 
@@ -442,7 +443,7 @@ export class FixtureManagerClass {
442
443
  this.uuidToFixtureId = new Map();
443
444
  this.skippedFixtures = new Map();
444
445
 
445
- const db = knex(Sonamu.dbConfig[dbName]);
446
+ const db = createKnexInstance(Sonamu.dbConfig[dbName]);
446
447
  const results: FixtureImportResult[] = [];
447
448
 
448
449
  try {
@@ -0,0 +1,3 @@
1
+ export * from "./bootstrap";
2
+ export * from "./fixture-loader";
3
+ export * from "./naite-vitest-reporter";
@@ -0,0 +1,18 @@
1
+ import { NaiteReporter } from "../naite/naite-reporter";
2
+
3
+ /**
4
+ * Naite Vitest Reporter
5
+ *
6
+ * 테스트 런 시작/종료를 정확히 감지하여 NaiteReporter에 알립니다.
7
+ * - onTestRunStart: 테스트 런 시작 시 (첫 실행 및 watch 재실행 포함)
8
+ * - onTestRunEnd: 각 런 종료 시
9
+ */
10
+ export const NaiteVitestReporter = {
11
+ async onTestRunStart() {
12
+ await NaiteReporter.startTestRun();
13
+ },
14
+
15
+ async onTestRunEnd() {
16
+ await NaiteReporter.endTestRun();
17
+ },
18
+ };
@@ -1268,14 +1268,12 @@ export const TemplateOptions = z.object({
1268
1268
  entityId: z.string(),
1269
1269
  enumId: z.string(),
1270
1270
  }),
1271
- view_enums_dropdown: z.object({
1272
- entityId: z.string(),
1273
- enumId: z.string(),
1274
- }),
1275
1271
  view_enums_buttonset: z.object({
1276
1272
  entityId: z.string(),
1277
1273
  enumId: z.string(),
1278
1274
  }),
1275
+ queries: z.object({}),
1276
+ entry_server: z.object({}),
1279
1277
  });
1280
1278
  export type TemplateOptions = z.infer<typeof TemplateOptions>;
1281
1279
 
@@ -1296,8 +1294,9 @@ export const TemplateKey = z.enum([
1296
1294
  "view_id_all_select",
1297
1295
  "view_id_async_select",
1298
1296
  "view_enums_select",
1299
- "view_enums_dropdown",
1300
1297
  "view_enums_buttonset",
1298
+ "queries",
1299
+ "entry_server",
1301
1300
  ]);
1302
1301
  export type TemplateKey = z.infer<typeof TemplateKey>;
1303
1302
 
@@ -1,13 +1,5 @@
1
1
  /** biome-ignore-all lint/suspicious/noExplicitAny: AI SDK의 타입이 명확하지 않아 any를 허용함 */
2
- import { anthropic } from "@ai-sdk/anthropic";
3
- import {
4
- type LanguageModel,
5
- type ModelMessage,
6
- type StreamTextResult,
7
- stepCountIs,
8
- streamText,
9
- tool,
10
- } from "ai";
2
+ import type { LanguageModel, ModelMessage, StreamTextResult } from "ai";
11
3
  import assert from "assert";
12
4
  import fs from "fs";
13
5
  import path from "path";
@@ -29,16 +21,35 @@ type ValidationError = {
29
21
  };
30
22
 
31
23
  class AIClient {
32
- private model = anthropic("claude-sonnet-4-5");
24
+ private model: LanguageModel | null = null;
25
+ private aiSdk:
26
+ | (typeof import("ai") & {
27
+ anthropic?: typeof import("@ai-sdk/anthropic").anthropic;
28
+ })
29
+ | null = null;
33
30
 
34
31
  async init() {
35
- console.log("AI client initialized with AI SDK");
32
+ try {
33
+ const { anthropic } = await import("@ai-sdk/anthropic");
34
+ const aiModule = await import("ai");
35
+ this.aiSdk = { ...aiModule, anthropic };
36
+ this.model = anthropic("claude-sonnet-4-5");
37
+ } catch (error) {
38
+ console.warn(
39
+ "AI SDK packages not installed. Install @ai-sdk/anthropic and ai to use AI features.",
40
+ );
41
+ throw error;
42
+ }
36
43
  }
37
44
 
38
45
  handleFixture(
39
46
  messages: ModelMessage[],
40
47
  fixtureRecords: FixtureRecord[],
41
48
  ): StreamTextResult<any, any> {
49
+ if (!this.aiSdk || !this.model) {
50
+ throw new Error("AI SDK not initialized. Call init() first.");
51
+ }
52
+
42
53
  // 현재 fixtureRecords에서 사용된 엔티티들의 구조 정보 수집
43
54
  const usedEntityIds = [...new Set(fixtureRecords.map((r) => r.entityId))];
44
55
  const entityStructures = usedEntityIds.map((entityId) => {
@@ -82,6 +93,8 @@ class AIClient {
82
93
  createFixtures({ fixtures: [{ entityId: "User", id: -1, columns: { name: "홍길동", email: "hong@example.com" } }] })
83
94
  `;
84
95
 
96
+ const { streamText, tool } = this.aiSdk;
97
+
85
98
  return streamText({
86
99
  model: this.model,
87
100
  system: systemMessage,
@@ -102,10 +115,14 @@ class AIClient {
102
115
  }),
103
116
  execute: async ({
104
117
  updates,
118
+ }: {
119
+ updates: Array<{ fixtureId: string; updates: Record<string, unknown> }>;
105
120
  }): Promise<{ success: boolean; updatedRecords: FixtureRecord[] }> => {
106
121
  // fixtureRecords를 복사하고 업데이트 적용
107
122
  const updatedRecords: FixtureRecord[] = fixtureRecords.map((record) => {
108
- const update = updates.find((u) => u.fixtureId === record.fixtureId);
123
+ const update = updates.find(
124
+ (u: { fixtureId: string }) => u.fixtureId === record.fixtureId,
125
+ );
109
126
  if (update) {
110
127
  // columns의 value를 업데이트
111
128
  for (const [columnName, newValue] of Object.entries(update.updates)) {
@@ -137,44 +154,48 @@ class AIClient {
137
154
  }),
138
155
  execute: async ({
139
156
  fixtures,
157
+ }: {
158
+ fixtures: Array<{ entityId: string; id: number; columns: Record<string, unknown> }>;
140
159
  }): Promise<{ success: boolean; updatedRecords: FixtureRecord[] }> => {
141
- const newRecords: FixtureRecord[] = fixtures.map((fixture) => {
142
- const entity = EntityManager.get(fixture.entityId);
143
-
144
- // 엔티티 props를 기반으로 columns 구성
145
- const columns: FixtureRecord["columns"] = {};
146
- for (const prop of entity.props) {
147
- if (prop.type === "virtual") continue;
148
-
149
- let value = fixture.columns[prop.name] ?? null;
150
-
151
- if (prop.name === "created_at") {
152
- // 현재 시간으로 설정
153
- value = new Date().toISOString();
154
- } else if (
155
- prop.type === "relation" &&
156
- (prop.relationType === "HasMany" || prop.relationType === "ManyToMany")
157
- ) {
158
- // 배열로 변환
159
- value = Array.isArray(value) ? value : [value].filter(nonNullable);
160
+ const newRecords: FixtureRecord[] = fixtures.map(
161
+ (fixture: { entityId: string; id: number; columns: Record<string, unknown> }) => {
162
+ const entity = EntityManager.get(fixture.entityId);
163
+
164
+ // 엔티티 props를 기반으로 columns 구성
165
+ const columns: FixtureRecord["columns"] = {};
166
+ for (const prop of entity.props) {
167
+ if (prop.type === "virtual") continue;
168
+
169
+ let value = fixture.columns[prop.name] ?? null;
170
+
171
+ if (prop.name === "created_at") {
172
+ // 현재 시간으로 설정
173
+ value = new Date().toISOString();
174
+ } else if (
175
+ prop.type === "relation" &&
176
+ (prop.relationType === "HasMany" || prop.relationType === "ManyToMany")
177
+ ) {
178
+ // 배열로 변환
179
+ value = Array.isArray(value) ? value : [value].filter(nonNullable);
180
+ }
181
+
182
+ columns[prop.name] = {
183
+ prop,
184
+ value: value as FixtureRecord["columns"][string]["value"],
185
+ };
160
186
  }
161
187
 
162
- columns[prop.name] = {
163
- prop,
164
- value: value as FixtureRecord["columns"][string]["value"],
188
+ return {
189
+ fixtureId: `${fixture.entityId}#${fixture.id}`,
190
+ entityId: fixture.entityId,
191
+ id: fixture.id,
192
+ columns,
193
+ fetchedRecords: [],
194
+ belongsRecords: [],
195
+ override: false,
165
196
  };
166
- }
167
-
168
- return {
169
- fixtureId: `${fixture.entityId}#${fixture.id}`,
170
- entityId: fixture.entityId,
171
- id: fixture.id,
172
- columns,
173
- fetchedRecords: [],
174
- belongsRecords: [],
175
- override: false,
176
- };
177
- });
197
+ },
198
+ );
178
199
 
179
200
  // 새 레코드들의 relation 컬럼을 확인하여 기존 레코드들의 역방향 relation 업데이트
180
201
  for (const newRecord of newRecords) {
@@ -232,6 +253,10 @@ class AIClient {
232
253
  }
233
254
 
234
255
  handleEntity(messages: ModelMessage[]): StreamTextResult<any, any> {
256
+ if (!this.aiSdk || !this.model) {
257
+ throw new Error("AI SDK not initialized. Call init() first.");
258
+ }
259
+
235
260
  // entity.instructions.md 파일 읽기 (dist/ui 또는 src/ui에서 실행되므로 패키지 루트 기준으로 접근)
236
261
  const instructionsPath = path.join(
237
262
  import.meta.dirname,
@@ -326,6 +351,8 @@ updateEntity({ entityId: "Project", updates: { props: [{ name: "priority", type:
326
351
  | "onUpdate가 필수" | 해당 relation prop에 onUpdate, onDelete 추가 ("CASCADE") |
327
352
  `;
328
353
 
354
+ const { streamText, tool, stepCountIs } = this.aiSdk;
355
+
329
356
  return streamText({
330
357
  model: this.model as unknown as LanguageModel,
331
358
  system: systemMessage,
@@ -337,7 +364,7 @@ updateEntity({ entityId: "Project", updates: { props: [{ name: "priority", type:
337
364
  "새로운 Entity를 생성합니다. 사용자가 새로운 엔티티나 테이블 생성을 요청할 때 사용하세요.",
338
365
  inputSchema: TemplateOptions.shape.entity,
339
366
  execute: async (
340
- entity,
367
+ entity: z.infer<typeof TemplateOptions.shape.entity>,
341
368
  ): Promise<{
342
369
  success: boolean;
343
370
  entityId: string;
@@ -388,6 +415,10 @@ updateEntity({ entityId: "Project", updates: { props: [{ name: "priority", type:
388
415
  entityId,
389
416
  updates,
390
417
  mode = "merge",
418
+ }: {
419
+ entityId: string;
420
+ updates: Partial<z.infer<typeof TemplateOptions.shape.entity>>;
421
+ mode?: "merge" | "replace";
391
422
  }): Promise<{
392
423
  success: boolean;
393
424
  entityId: string;
@@ -432,10 +463,11 @@ updateEntity({ entityId: "Project", updates: { props: [{ name: "priority", type:
432
463
  const normalizedSubsetsInternal: { [key: string]: string[] } = {};
433
464
 
434
465
  for (const [key, fields] of Object.entries(updates.subsets)) {
435
- normalizedSubsets[key] = fields
436
- .filter((f) => !isInternalSubsetField(f))
466
+ const fieldArray = fields as string[];
467
+ normalizedSubsets[key] = fieldArray
468
+ .filter((f: string) => !isInternalSubsetField(f))
437
469
  .map(normalizeSubsetField);
438
- normalizedSubsetsInternal[key] = fields
470
+ normalizedSubsetsInternal[key] = fieldArray
439
471
  .filter(isInternalSubsetField)
440
472
  .map(normalizeSubsetField);
441
473
  }
@@ -1,17 +0,0 @@
1
- import { type EntityNamesRecord } from "../../entity/entity-manager";
2
- import type { TemplateOptions } from "../../types/types";
3
- import { Template } from "../template";
4
- export declare class Template__view_enums_dropdown extends Template {
5
- constructor();
6
- getTargetAndPath(names: EntityNamesRecord, enumId: string): {
7
- target: string;
8
- path: string;
9
- };
10
- render({ entityId, enumId }: TemplateOptions["view_enums_dropdown"]): {
11
- body: string;
12
- importKeys: never[];
13
- target: string;
14
- path: string;
15
- };
16
- }
17
- //# sourceMappingURL=view_enums_dropdown.template.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"view_enums_dropdown.template.d.ts","sourceRoot":"","sources":["../../../src/template/implementations/view_enums_dropdown.template.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACpF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,qBAAa,6BAA8B,SAAQ,QAAQ;;IAKzD,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM;;;;IAOzD,MAAM,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,eAAe,CAAC,qBAAqB,CAAC;;;;;;CAmCpE"}