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
|
@@ -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
|
|
23
|
+
import { Button, Input } from "@sonamu-kit/react-components/components";
|
|
24
|
+
import type React from "react";
|
|
24
25
|
import { useState } from "react";
|
|
25
|
-
import {
|
|
26
|
-
import
|
|
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
|
|
41
|
+
input: { value: inputValue, onChange: inputOnChange },
|
|
30
42
|
dropdown: dropdownProps,
|
|
31
|
-
}: {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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:
|
|
38
|
-
if (
|
|
39
|
-
|
|
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
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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 (
|
|
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
|
|
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 =
|
|
90
|
-
this.fdb =
|
|
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 =
|
|
258
|
-
const targetDB =
|
|
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 =
|
|
446
|
+
const db = createKnexInstance(Sonamu.dbConfig[dbName]);
|
|
446
447
|
const results: FixtureImportResult[] = [];
|
|
447
448
|
|
|
448
449
|
try {
|
|
@@ -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
|
+
};
|
package/src/types/types.ts
CHANGED
|
@@ -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
|
|
package/src/ui/ai-client.ts
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
/** biome-ignore-all lint/suspicious/noExplicitAny: AI SDK의 타입이 명확하지 않아 any를 허용함 */
|
|
2
|
-
import {
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
436
|
-
|
|
466
|
+
const fieldArray = fields as string[];
|
|
467
|
+
normalizedSubsets[key] = fieldArray
|
|
468
|
+
.filter((f: string) => !isInternalSubsetField(f))
|
|
437
469
|
.map(normalizeSubsetField);
|
|
438
|
-
normalizedSubsetsInternal[key] =
|
|
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"}
|