sonamu 0.7.53 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/config.d.ts +9 -1
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +1 -1
- package/dist/api/sonamu.d.ts +21 -1
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +159 -65
- package/dist/auth/plugins/entity-definitions/anonymous.d.ts +10 -0
- package/dist/auth/plugins/entity-definitions/anonymous.d.ts.map +1 -0
- package/dist/auth/plugins/entity-definitions/anonymous.js +23 -0
- package/dist/auth/plugins/entity-definitions/api-key.d.ts +9 -0
- package/dist/auth/plugins/entity-definitions/api-key.d.ts.map +1 -0
- package/dist/auth/plugins/entity-definitions/api-key.js +199 -0
- package/dist/auth/plugins/entity-definitions/index.d.ts +6 -0
- package/dist/auth/plugins/entity-definitions/index.d.ts.map +1 -1
- package/dist/auth/plugins/entity-definitions/index.js +20 -2
- package/dist/auth/plugins/entity-definitions/jwt.d.ts +9 -0
- package/dist/auth/plugins/entity-definitions/jwt.d.ts.map +1 -0
- package/dist/auth/plugins/entity-definitions/jwt.js +67 -0
- package/dist/auth/plugins/entity-definitions/organization.d.ts +9 -0
- package/dist/auth/plugins/entity-definitions/organization.d.ts.map +1 -0
- package/dist/auth/plugins/entity-definitions/organization.js +424 -0
- package/dist/auth/plugins/entity-definitions/passkey.d.ts +10 -0
- package/dist/auth/plugins/entity-definitions/passkey.d.ts.map +1 -0
- package/dist/auth/plugins/entity-definitions/passkey.js +129 -0
- package/dist/auth/plugins/entity-definitions/sso.d.ts +10 -0
- package/dist/auth/plugins/entity-definitions/sso.d.ts.map +1 -0
- package/dist/auth/plugins/entity-definitions/sso.js +110 -0
- package/dist/auth/plugins/entity-definitions/types.d.ts +1 -1
- package/dist/auth/plugins/entity-definitions/types.d.ts.map +1 -1
- package/dist/auth/plugins/entity-definitions/types.js +1 -1
- package/dist/auth/plugins/wrappers/admin.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/admin.js +2 -4
- package/dist/auth/plugins/wrappers/anonymous.d.ts +18 -0
- package/dist/auth/plugins/wrappers/anonymous.d.ts.map +1 -0
- package/dist/auth/plugins/wrappers/anonymous.js +26 -0
- package/dist/auth/plugins/wrappers/api-key.d.ts +18 -0
- package/dist/auth/plugins/wrappers/api-key.d.ts.map +1 -0
- package/dist/auth/plugins/wrappers/api-key.js +38 -0
- package/dist/auth/plugins/wrappers/index.d.ts +6 -0
- package/dist/auth/plugins/wrappers/index.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/index.js +7 -1
- package/dist/auth/plugins/wrappers/jwt.d.ts +18 -0
- package/dist/auth/plugins/wrappers/jwt.d.ts.map +1 -0
- package/dist/auth/plugins/wrappers/jwt.js +30 -0
- package/dist/auth/plugins/wrappers/organization.d.ts +18 -0
- package/dist/auth/plugins/wrappers/organization.d.ts.map +1 -0
- package/dist/auth/plugins/wrappers/organization.js +67 -0
- package/dist/auth/plugins/wrappers/passkey.d.ts +18 -0
- package/dist/auth/plugins/wrappers/passkey.d.ts.map +1 -0
- package/dist/auth/plugins/wrappers/passkey.js +32 -0
- package/dist/auth/plugins/wrappers/phone-number.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/phone-number.js +2 -4
- package/dist/auth/plugins/wrappers/sso.d.ts +853 -0
- package/dist/auth/plugins/wrappers/sso.d.ts.map +1 -0
- package/dist/auth/plugins/wrappers/sso.js +36 -0
- package/dist/auth/plugins/wrappers/two-factor.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/two-factor.js +2 -4
- package/dist/auth/plugins/wrappers/username.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/username.js +2 -4
- package/dist/bin/build-config.d.ts +2 -2
- package/dist/bin/build-config.js +6 -7
- package/dist/bin/cli.js +417 -32
- package/dist/bin/fixture.d.ts +27 -0
- package/dist/bin/fixture.d.ts.map +1 -0
- package/dist/bin/fixture.js +245 -0
- package/dist/cache/decorator.d.ts +4 -3
- package/dist/cache/decorator.d.ts.map +1 -1
- package/dist/cache/decorator.js +5 -4
- package/dist/cone/cone-generator.d.ts +33 -0
- package/dist/cone/cone-generator.d.ts.map +1 -0
- package/dist/cone/cone-generator.js +286 -0
- package/dist/database/_batch_update.d.ts.map +1 -1
- package/dist/database/_batch_update.js +16 -2
- package/dist/database/puri-subset.test-d.js +1 -1
- package/dist/database/puri-subset.types.d.ts +1 -1
- package/dist/database/puri-subset.types.d.ts.map +1 -1
- package/dist/database/puri-subset.types.js +1 -1
- package/dist/database/puri.d.ts +4 -0
- package/dist/database/puri.d.ts.map +1 -1
- package/dist/database/puri.js +20 -2
- package/dist/database/upsert-builder.d.ts.map +1 -1
- package/dist/database/upsert-builder.js +19 -3
- package/dist/dict/en.d.ts +15 -0
- package/dist/dict/en.d.ts.map +1 -1
- package/dist/dict/en.js +2 -1
- package/dist/dict/ko.d.ts +15 -0
- package/dist/dict/ko.d.ts.map +1 -1
- package/dist/dict/ko.js +2 -1
- package/dist/dict/rc-keys.d.ts +28 -0
- package/dist/dict/rc-keys.d.ts.map +1 -1
- package/dist/dict/rc-keys.js +31 -1
- package/dist/dict/sd.d.ts.map +1 -1
- package/dist/dict/sd.js +20 -4
- package/dist/entity/entity-manager.d.ts +298 -2
- package/dist/entity/entity-manager.d.ts.map +1 -1
- package/dist/entity/entity-manager.js +4 -1
- package/dist/entity/entity-template-cone.d.ts +14 -0
- package/dist/entity/entity-template-cone.d.ts.map +1 -0
- package/dist/entity/entity-template-cone.js +222 -0
- package/dist/entity/entity.d.ts +47 -2
- package/dist/entity/entity.d.ts.map +1 -1
- package/dist/entity/entity.js +161 -14
- package/dist/ssr/renderer.js +3 -3
- package/dist/syncer/api-parser.js +12 -1
- package/dist/syncer/checksum.d.ts +0 -14
- package/dist/syncer/checksum.d.ts.map +1 -1
- package/dist/syncer/checksum.js +1 -23
- package/dist/syncer/syncer-actions.d.ts.map +1 -1
- package/dist/syncer/syncer-actions.js +8 -2
- package/dist/syncer/syncer.d.ts +1 -1
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/syncer/syncer.js +17 -10
- package/dist/tasks/workflow-manager.d.ts +13 -1
- package/dist/tasks/workflow-manager.d.ts.map +1 -1
- package/dist/tasks/workflow-manager.js +18 -1
- package/dist/template/entity-converter.js +4 -4
- package/dist/template/helpers.d.ts +10 -0
- package/dist/template/helpers.d.ts.map +1 -1
- package/dist/template/helpers.js +48 -1
- package/dist/template/implementations/entry-server.template.d.ts +1 -1
- package/dist/template/implementations/entry-server.template.js +7 -2
- package/dist/template/implementations/generated.template.d.ts.map +1 -1
- package/dist/template/implementations/generated.template.js +5 -1
- package/dist/template/implementations/generated_http.template.d.ts +1 -0
- package/dist/template/implementations/generated_http.template.d.ts.map +1 -1
- package/dist/template/implementations/generated_http.template.js +6 -2
- package/dist/template/implementations/generated_sso.template.d.ts.map +1 -1
- package/dist/template/implementations/generated_sso.template.js +29 -8
- package/dist/template/implementations/queries.template.d.ts.map +1 -1
- package/dist/template/implementations/queries.template.js +9 -1
- package/dist/template/implementations/sd.template.d.ts +1 -1
- package/dist/template/implementations/sd.template.d.ts.map +1 -1
- package/dist/template/implementations/sd.template.js +28 -4
- package/dist/template/implementations/services.template.d.ts.map +1 -1
- package/dist/template/implementations/services.template.js +12 -12
- package/dist/template/implementations/view_form.template.d.ts +11 -7
- package/dist/template/implementations/view_form.template.d.ts.map +1 -1
- package/dist/template/implementations/view_form.template.js +97 -87
- package/dist/template/implementations/view_list.template.d.ts +3 -3
- package/dist/template/implementations/view_list.template.d.ts.map +1 -1
- package/dist/template/implementations/view_list.template.js +115 -109
- package/dist/template/implementations/view_search_input.template.d.ts.map +1 -1
- package/dist/template/implementations/view_search_input.template.js +18 -14
- package/dist/template/zod-converter.d.ts.map +1 -1
- package/dist/template/zod-converter.js +95 -7
- package/dist/testing/_relation-graph.js +1 -1
- package/dist/testing/data-explorer.d.ts +61 -0
- package/dist/testing/data-explorer.d.ts.map +1 -0
- package/dist/testing/data-explorer.js +274 -0
- package/dist/testing/faker-mappings.d.ts +20 -0
- package/dist/testing/faker-mappings.d.ts.map +1 -0
- package/dist/testing/faker-mappings.js +421 -0
- package/dist/testing/fixture-generator.d.ts +161 -0
- package/dist/testing/fixture-generator.d.ts.map +1 -0
- package/dist/testing/fixture-generator.js +954 -0
- package/dist/testing/fixture-manager.d.ts +6 -1
- package/dist/testing/fixture-manager.d.ts.map +1 -1
- package/dist/testing/fixture-manager.js +72 -4
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +4 -1
- package/dist/types/types.d.ts +1520 -26
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +136 -22
- package/dist/ui/ai-client.d.ts.map +1 -1
- package/dist/ui/ai-client.js +9 -4
- package/dist/ui/api.d.ts.map +1 -1
- package/dist/ui/api.js +303 -24
- package/dist/ui-web/assets/index-CsUr-_pV.js +254 -0
- package/dist/ui-web/assets/index-T42zzs1K.css +1 -0
- package/dist/ui-web/index.html +2 -2
- package/dist/utils/fs-utils.d.ts +2 -1
- package/dist/utils/fs-utils.d.ts.map +1 -1
- package/dist/utils/fs-utils.js +14 -3
- package/package.json +19 -11
- package/src/api/config.ts +12 -1
- package/src/api/sonamu.ts +179 -65
- package/src/auth/plugins/entity-definitions/anonymous.ts +17 -0
- package/src/auth/plugins/entity-definitions/api-key.ts +93 -0
- package/src/auth/plugins/entity-definitions/index.ts +18 -0
- package/src/auth/plugins/entity-definitions/jwt.ts +35 -0
- package/src/auth/plugins/entity-definitions/organization.ts +215 -0
- package/src/auth/plugins/entity-definitions/passkey.ts +64 -0
- package/src/auth/plugins/entity-definitions/sso.ts +62 -0
- package/src/auth/plugins/entity-definitions/types.ts +11 -1
- package/src/auth/plugins/wrappers/admin.ts +1 -3
- package/src/auth/plugins/wrappers/anonymous.ts +30 -0
- package/src/auth/plugins/wrappers/api-key.ts +42 -0
- package/src/auth/plugins/wrappers/index.ts +6 -0
- package/src/auth/plugins/wrappers/jwt.ts +34 -0
- package/src/auth/plugins/wrappers/organization.ts +73 -0
- package/src/auth/plugins/wrappers/passkey.ts +36 -0
- package/src/auth/plugins/wrappers/phone-number.ts +1 -3
- package/src/auth/plugins/wrappers/sso.ts +40 -0
- package/src/auth/plugins/wrappers/two-factor.ts +1 -3
- package/src/auth/plugins/wrappers/username.ts +1 -3
- package/src/bin/build-config.ts +6 -6
- package/src/bin/cli.ts +452 -31
- package/src/bin/fixture.ts +302 -0
- package/src/cache/decorator.ts +4 -3
- package/src/cone/cone-generator.ts +363 -0
- package/src/database/_batch_update.ts +11 -0
- package/src/database/puri-subset.test-d.ts +13 -13
- package/src/database/puri-subset.types.ts +1 -1
- package/src/database/puri.ts +43 -1
- package/src/database/upsert-builder.ts +16 -2
- package/src/dict/en.ts +1 -0
- package/src/dict/ko.ts +1 -0
- package/src/dict/rc-keys.ts +32 -0
- package/src/dict/sd.ts +23 -3
- package/src/entity/entity-manager.ts +4 -0
- package/src/entity/entity-template-cone.ts +298 -0
- package/src/entity/entity.ts +189 -13
- package/src/shared/app.shared.ts.txt +5 -0
- package/src/shared/web.shared.ts.txt +9 -5
- package/src/skills/project/README.md +21 -0
- package/src/skills/project/architecture.md +373 -0
- package/src/skills/project/business-logic.md +270 -0
- package/src/skills/project/requirements.md +160 -0
- package/src/skills/sonamu/SKILL.md +168 -3
- package/src/skills/sonamu/api.md +102 -0
- package/src/skills/sonamu/database.md +220 -1
- package/src/skills/sonamu/entity-relations.md +89 -1
- package/src/skills/sonamu/fixture-cli.md +501 -0
- package/src/skills/sonamu/frontend.md +214 -0
- package/src/skills/sonamu/i18n.md +95 -0
- package/src/skills/sonamu/model.md +153 -0
- package/src/skills/sonamu/project-init.md +178 -8
- package/src/skills/sonamu/scaffolding.md +112 -0
- package/src/skills/sonamu/subset.md +9 -3
- package/src/skills/sonamu/testing.md +287 -2
- package/src/skills/sonamu/workflow.md +70 -5
- package/src/ssr/renderer.ts +2 -2
- package/src/syncer/api-parser.ts +12 -0
- package/src/syncer/checksum.ts +0 -38
- package/src/syncer/syncer-actions.ts +7 -1
- package/src/syncer/syncer.ts +16 -5
- package/src/tasks/workflow-manager.ts +29 -8
- package/src/template/entity-converter.ts +3 -3
- package/src/template/helpers.ts +49 -0
- package/src/template/implementations/entry-server.template.ts +1 -1
- package/src/template/implementations/generated.template.ts +4 -0
- package/src/template/implementations/generated_http.template.ts +1 -0
- package/src/template/implementations/generated_sso.template.ts +40 -11
- package/src/template/implementations/queries.template.ts +8 -0
- package/src/template/implementations/sd.template.ts +22 -3
- package/src/template/implementations/services.template.ts +11 -10
- package/src/template/implementations/view_form.template.ts +111 -101
- package/src/template/implementations/view_list.template.ts +120 -119
- package/src/template/implementations/view_search_input.template.ts +17 -13
- package/src/template/zod-converter.ts +103 -6
- package/src/testing/_relation-graph.ts +1 -1
- package/src/testing/data-explorer.ts +427 -0
- package/src/testing/faker-mappings.ts +434 -0
- package/src/testing/fixture-generator.ts +1166 -0
- package/src/testing/fixture-manager.ts +91 -6
- package/src/testing/index.ts +3 -0
- package/src/types/types.ts +222 -26
- package/src/ui/ai-client.ts +9 -1
- package/src/ui/api.ts +429 -23
- package/src/utils/fs-utils.ts +14 -1
- package/dist/template/implementations/view_enums_select.template.d.ts +0 -17
- package/dist/template/implementations/view_enums_select.template.d.ts.map +0 -1
- package/dist/template/implementations/view_enums_select.template.js +0 -62
- package/dist/template/implementations/view_id_async_select.template.d.ts +0 -17
- package/dist/template/implementations/view_id_async_select.template.d.ts.map +0 -1
- package/dist/template/implementations/view_id_async_select.template.js +0 -125
- package/dist/ui-web/assets/index-Bd_2AkLb.css +0 -1
- package/dist/ui-web/assets/index-BpSbhQWo.js +0 -225
- package/src/template/implementations/view_enums_select.template.ts +0 -65
- package/src/template/implementations/view_id_async_select.template.ts +0 -139
|
@@ -2,8 +2,12 @@ import inflection from "inflection";
|
|
|
2
2
|
import { flat } from "radashi";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { EntityManager, type EntityNamesRecord } from "../../entity/entity-manager";
|
|
5
|
-
import type { RenderingNode,
|
|
6
|
-
import {
|
|
5
|
+
import type { RenderingNode, TemplateOptions } from "../../types/types";
|
|
6
|
+
import {
|
|
7
|
+
getEnumInfoFromColName,
|
|
8
|
+
getRelationNameFromColumnName,
|
|
9
|
+
getRelationPropFromColName,
|
|
10
|
+
} from "../helpers";
|
|
7
11
|
import type { RenderedTemplate } from "../template";
|
|
8
12
|
import { Template } from "../template";
|
|
9
13
|
|
|
@@ -52,8 +56,10 @@ export class Template__view_list extends Template {
|
|
|
52
56
|
case "string-plain":
|
|
53
57
|
case "string-date":
|
|
54
58
|
case "number-id":
|
|
59
|
+
case "string-id":
|
|
55
60
|
return `<>{${colName}}</>`;
|
|
56
|
-
case "number-fk_id":
|
|
61
|
+
case "number-fk_id":
|
|
62
|
+
case "string-fk_id": {
|
|
57
63
|
try {
|
|
58
64
|
const baseName = col.name.includes(".")
|
|
59
65
|
? (col.name.split(".").pop() ?? col.name).replace("_id", "")
|
|
@@ -169,7 +175,7 @@ export class Template__view_list extends Template {
|
|
|
169
175
|
return "";
|
|
170
176
|
}
|
|
171
177
|
}
|
|
172
|
-
} else if (col.renderType === "number-fk_id") {
|
|
178
|
+
} else if (col.renderType === "number-fk_id" || col.renderType === "string-fk_id") {
|
|
173
179
|
try {
|
|
174
180
|
const relProp = getRelationPropFromColName(entityId, col.name.replace("_id", ""));
|
|
175
181
|
const targetNames = EntityManager.getNamesFromId(relProp.with);
|
|
@@ -202,7 +208,7 @@ export class Template__view_list extends Template {
|
|
|
202
208
|
}
|
|
203
209
|
}
|
|
204
210
|
return `<${componentId} {...register('${col.name}')} ${isClearable ? "clearable" : ""} />`;
|
|
205
|
-
} else if (col.renderType === "number-fk_id") {
|
|
211
|
+
} else if (col.renderType === "number-fk_id" || col.renderType === "string-fk_id") {
|
|
206
212
|
try {
|
|
207
213
|
const relProp = getRelationPropFromColName(entityId, col.name.replace("_id", ""));
|
|
208
214
|
componentId = `${relProp.with}IdAsyncSelect`;
|
|
@@ -262,17 +268,31 @@ export class Template__view_list extends Template {
|
|
|
262
268
|
|
|
263
269
|
.sort((a, b) => (a.name === "id" ? -1 : b.name === "id" ? 1 : 0))
|
|
264
270
|
.map((col) => {
|
|
265
|
-
const propCandidate = entity.props.find((p) => p.name === col.name);
|
|
266
271
|
const rendered = this.renderColumn(entityId, col, names);
|
|
272
|
+
|
|
273
|
+
// 라벨 생성: common 필드(created_at)는 SD("common.{field}"), entity 필드는 SD("entity.{Entity}.{field}")
|
|
274
|
+
let label: string;
|
|
275
|
+
if (col.name === "id") {
|
|
276
|
+
label = '"ID"';
|
|
277
|
+
} else if (["created_at"].includes(col.name)) {
|
|
278
|
+
// camelCase로 변환 (created_at -> createdAt)
|
|
279
|
+
const camelName = col.name.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
|
|
280
|
+
label = `SD("common.${camelName}")`;
|
|
281
|
+
} else {
|
|
282
|
+
const labelName = getRelationNameFromColumnName(entityId, col.name);
|
|
283
|
+
label = `SD("entity.${names.capital}.${labelName}")`;
|
|
284
|
+
}
|
|
285
|
+
|
|
267
286
|
return {
|
|
268
287
|
name: col.name,
|
|
269
|
-
label
|
|
288
|
+
label,
|
|
270
289
|
tc: `(row) => ${rendered}`,
|
|
271
290
|
fit:
|
|
291
|
+
col.name === "id" ||
|
|
272
292
|
col.renderType === "number-id" ||
|
|
273
293
|
col.renderType === "datetime" ||
|
|
274
294
|
col.renderType === "string-datetime",
|
|
275
|
-
align: col.renderType === "number-id" ? "center" : undefined,
|
|
295
|
+
align: col.name === "id" || col.renderType === "number-id" ? "center" : undefined,
|
|
276
296
|
};
|
|
277
297
|
});
|
|
278
298
|
|
|
@@ -282,7 +302,7 @@ export class Template__view_list extends Template {
|
|
|
282
302
|
(col) =>
|
|
283
303
|
col.name !== "id" &&
|
|
284
304
|
col.name !== "queryMode" &&
|
|
285
|
-
|
|
305
|
+
["enums", "number-id", "number-fk_id", "string-fk_id"].includes(col.renderType),
|
|
286
306
|
)
|
|
287
307
|
// orderBy가 가장 뒤로 오게 순서 조정
|
|
288
308
|
.sort((a) => {
|
|
@@ -291,50 +311,6 @@ export class Template__view_list extends Template {
|
|
|
291
311
|
|
|
292
312
|
// 필터 컬럼을 프리 템플릿으로 설정
|
|
293
313
|
const preTemplates: RenderedTemplate["preTemplates"] = [];
|
|
294
|
-
for (const col of filterColumns) {
|
|
295
|
-
let key: TemplateKey;
|
|
296
|
-
let targetEntityId = entityId;
|
|
297
|
-
let enumId: string | undefined;
|
|
298
|
-
|
|
299
|
-
if (col.renderType === "enums") {
|
|
300
|
-
if (col.name === "search") {
|
|
301
|
-
key = "view_enums_select";
|
|
302
|
-
enumId = `${names.capital}SearchField`;
|
|
303
|
-
targetEntityId = names.capital;
|
|
304
|
-
} else {
|
|
305
|
-
key = "view_enums_select";
|
|
306
|
-
// config.enumId 우선 사용
|
|
307
|
-
if (col.config && "enumId" in col.config) {
|
|
308
|
-
enumId = (col.config as { enumId: string }).enumId;
|
|
309
|
-
targetEntityId = entityId;
|
|
310
|
-
} else {
|
|
311
|
-
try {
|
|
312
|
-
const { targetEntityNames, id } = getEnumInfoFromColName(entityId, col.name);
|
|
313
|
-
targetEntityId = targetEntityNames.capital;
|
|
314
|
-
enumId = id;
|
|
315
|
-
} catch {
|
|
316
|
-
continue;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
} else {
|
|
321
|
-
key = "view_id_async_select";
|
|
322
|
-
try {
|
|
323
|
-
const relProp = getRelationPropFromColName(entityId, col.name.replace("_id", ""));
|
|
324
|
-
targetEntityId = relProp.with;
|
|
325
|
-
} catch {
|
|
326
|
-
continue;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
preTemplates.push({
|
|
331
|
-
key,
|
|
332
|
-
options: {
|
|
333
|
-
entityId: targetEntityId,
|
|
334
|
-
enumId,
|
|
335
|
-
},
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
314
|
|
|
339
315
|
// 컬럼에서 사용하는 enum들 수집
|
|
340
316
|
const columnEnums: string[] = [];
|
|
@@ -347,14 +323,6 @@ export class Template__view_list extends Template {
|
|
|
347
323
|
}
|
|
348
324
|
});
|
|
349
325
|
|
|
350
|
-
// SearchInput
|
|
351
|
-
preTemplates?.push({
|
|
352
|
-
key: "view_search_input",
|
|
353
|
-
options: {
|
|
354
|
-
entityId,
|
|
355
|
-
},
|
|
356
|
-
});
|
|
357
|
-
|
|
358
326
|
// 디폴트 파라미터
|
|
359
327
|
// const def = this.getDefault(filterColumns);
|
|
360
328
|
|
|
@@ -368,13 +336,14 @@ import { Card, CardContent, CardHeader } from "@sonamu-kit/react-components/comp
|
|
|
368
336
|
import { Badge } from "@sonamu-kit/react-components/components";
|
|
369
337
|
import { Button } from "@sonamu-kit/react-components/components";
|
|
370
338
|
import { Pagination, Table, TableBody, TableCell, type TableCol, TableHead, TableHeader, TableRow } from "@sonamu-kit/react-components/components";
|
|
371
|
-
import {
|
|
339
|
+
import { EnumSelect } from "@sonamu-kit/react-components/components";
|
|
372
340
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@sonamu-kit/react-components/components";
|
|
373
341
|
import { Input } from "@sonamu-kit/react-components/components";
|
|
374
342
|
import { Checkbox } from "@sonamu-kit/react-components/components";
|
|
343
|
+
import { SonamuFilterModal, SonamuFilterPopover, extractFieldMetaFromSchema, type Rule } from "@sonamu-kit/react-components/components";
|
|
375
344
|
|
|
376
345
|
import { useListParams, numF, dateF, datetimeF } from "@sonamu-kit/react-components/lib";
|
|
377
|
-
import { ${names.capital}SubsetA } from "@/services/sonamu.generated";
|
|
346
|
+
import { ${names.capital}SubsetA, ${names.capital}BaseSchema } from "@/services/sonamu.generated";
|
|
378
347
|
import { ${names.capital}Service } from "@/services/services.generated";
|
|
379
348
|
import { ${names.capital}ListParams } from "@/services/${names.fs}/${names.fs}.types";
|
|
380
349
|
import { ${(() => {
|
|
@@ -411,37 +380,30 @@ import { ${(() => {
|
|
|
411
380
|
|
|
412
381
|
return [...baseEnums, ...enumImports].join(", ");
|
|
413
382
|
})()} } from "@/services/sonamu.generated";
|
|
383
|
+
import { IdAsyncSelect } from "@sonamu-kit/react-components/components";
|
|
414
384
|
${(() => {
|
|
415
|
-
// FK 필드의
|
|
416
|
-
const fkColumns = filterColumns.filter(
|
|
417
|
-
|
|
385
|
+
// FK 필드의 AsyncIdConfig import
|
|
386
|
+
const fkColumns = filterColumns.filter(
|
|
387
|
+
(col) => col.renderType === "number-fk_id" || col.renderType === "string-fk_id",
|
|
388
|
+
);
|
|
389
|
+
const configNames = fkColumns
|
|
418
390
|
.map((col) => {
|
|
419
391
|
try {
|
|
420
392
|
const relProp = getRelationPropFromColName(entityId, col.name.replace("_id", ""));
|
|
421
|
-
|
|
422
|
-
return `import { ${relProp.with}IdAsyncSelect } from "@/components/${targetNames.fs}/${relProp.with}IdAsyncSelect";`;
|
|
393
|
+
return `${relProp.with}AsyncIdConfig`;
|
|
423
394
|
} catch {
|
|
424
395
|
return "";
|
|
425
396
|
}
|
|
426
397
|
})
|
|
427
|
-
.filter(Boolean)
|
|
428
|
-
|
|
398
|
+
.filter(Boolean);
|
|
399
|
+
return configNames.length > 0
|
|
400
|
+
? `import { ${configNames.join(", ")} } from "@/services/services.generated";`
|
|
401
|
+
: "";
|
|
429
402
|
})()}
|
|
430
|
-
${
|
|
431
|
-
filterColumns.some((col) => col.name === "search")
|
|
432
|
-
? `
|
|
433
|
-
import { ${names.capital}SearchFieldSelect } from "@/components/${names.fs}/${names.capital}SearchFieldSelect";`
|
|
434
|
-
: ""
|
|
435
|
-
}
|
|
436
|
-
${
|
|
437
|
-
filterColumns.some((col) => col.name === "orderBy")
|
|
438
|
-
? `
|
|
439
|
-
import { ${names.capital}OrderBySelect } from "@/components/${names.fs}/${names.capital}OrderBySelect";`
|
|
440
|
-
: ""
|
|
441
|
-
}
|
|
442
403
|
|
|
443
404
|
import EditIcon from "~icons/lucide/square-pen";
|
|
444
405
|
import TrashIcon from "~icons/lucide/trash-2";
|
|
406
|
+
import FilterIcon from "~icons/mdi/filter-variant";
|
|
445
407
|
import ListIcon from "~icons/mdi/format-list-bulleted";
|
|
446
408
|
import SearchIcon from "~icons/mdi/magnify";
|
|
447
409
|
import { SD } from "@/i18n/sd.generated";
|
|
@@ -455,9 +417,11 @@ function ${names.capital}List({}: ${names.capital}ListProps) {
|
|
|
455
417
|
const [selectedItems, setSelectedItems] = useState<Set<${idTsType}>>(new Set());
|
|
456
418
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
457
419
|
const [itemToDelete, setItemToDelete] = useState<{ id: ${idTsType}; name?: string } | null>(null);
|
|
420
|
+
const [filterModalOpen, setFilterModalOpen] = useState(false);
|
|
421
|
+
const [appliedRules, setAppliedRules] = useState<Rule[]>([]);
|
|
458
422
|
|
|
459
423
|
// 리스트 필터
|
|
460
|
-
const { listParams, register } = useListParams(${names.capital}ListParams, {
|
|
424
|
+
const { listParams, register, setListParams } = useListParams(${names.capital}ListParams, {
|
|
461
425
|
num: 10,
|
|
462
426
|
page: 1,
|
|
463
427
|
keyword: "",${
|
|
@@ -471,6 +435,7 @@ function ${names.capital}List({}: ${names.capital}ListProps) {
|
|
|
471
435
|
orderBy: ${names.capital}OrderBy.options[0],`
|
|
472
436
|
: ""
|
|
473
437
|
}
|
|
438
|
+
sonamuFilter: {},
|
|
474
439
|
});
|
|
475
440
|
|
|
476
441
|
// 리스트 쿼리
|
|
@@ -480,7 +445,7 @@ function ${names.capital}List({}: ${names.capital}ListProps) {
|
|
|
480
445
|
// 현재 경로와 타이틀
|
|
481
446
|
const PAGE = {
|
|
482
447
|
route: "/admin/${names.fsPlural}",
|
|
483
|
-
title: "
|
|
448
|
+
title: SD("entity.list")(SD("entity.${names.capital}")),
|
|
484
449
|
};
|
|
485
450
|
|
|
486
451
|
// 컬럼 정의
|
|
@@ -489,7 +454,7 @@ function ${names.capital}List({}: ${names.capital}ListProps) {
|
|
|
489
454
|
${columns
|
|
490
455
|
.map(
|
|
491
456
|
(col) => ` {
|
|
492
|
-
label:
|
|
457
|
+
label: ${col.label},
|
|
493
458
|
tc: ${col.tc},${
|
|
494
459
|
col.fit
|
|
495
460
|
? `
|
|
@@ -505,7 +470,7 @@ ${columns
|
|
|
505
470
|
)
|
|
506
471
|
.join(",\n")},
|
|
507
472
|
{
|
|
508
|
-
label: "
|
|
473
|
+
label: SD("common.manage"),
|
|
509
474
|
fit: true,
|
|
510
475
|
align: "center",
|
|
511
476
|
tc: (row) => (
|
|
@@ -583,10 +548,12 @@ ${columns
|
|
|
583
548
|
<div className="flex items-center gap-3 flex-wrap">
|
|
584
549
|
${
|
|
585
550
|
filterColumns.some((col) => col.name === "search")
|
|
586
|
-
? `
|
|
551
|
+
? ` <EnumSelect
|
|
552
|
+
enum={${names.capital}SearchField}
|
|
553
|
+
labels={${names.capital}SearchFieldLabel}
|
|
587
554
|
{...register("search")}
|
|
588
|
-
placeholder="
|
|
589
|
-
className="w-
|
|
555
|
+
placeholder={SD("common.searchType")}
|
|
556
|
+
className="w-50 h-8 bg-white border-gray-300 text-xs"
|
|
590
557
|
/>`
|
|
591
558
|
: ""
|
|
592
559
|
}
|
|
@@ -594,7 +561,7 @@ ${
|
|
|
594
561
|
<div className="relative flex-1 max-w-xs">
|
|
595
562
|
<Input
|
|
596
563
|
{...register("keyword")}
|
|
597
|
-
placeholder="
|
|
564
|
+
placeholder={SD("common.search")}
|
|
598
565
|
className="h-8 pr-8 text-xs bg-white border-gray-300"
|
|
599
566
|
/>
|
|
600
567
|
<Button
|
|
@@ -605,13 +572,35 @@ ${
|
|
|
605
572
|
/>
|
|
606
573
|
</div>
|
|
607
574
|
|
|
608
|
-
<div className="ml-auto">
|
|
575
|
+
<div className="ml-auto flex items-center gap-2">
|
|
609
576
|
<Button
|
|
610
577
|
className="h-8 px-4 bg-primary hover:bg-primary/90 text-white"
|
|
611
578
|
onClick={() => navigate({ to: \`\${PAGE.route}/form\` })}
|
|
612
579
|
>
|
|
613
|
-
<span className="text-xs">
|
|
580
|
+
<span className="text-xs">{SD("common.create")}</span>
|
|
614
581
|
</Button>
|
|
582
|
+
<SonamuFilterPopover
|
|
583
|
+
rules={appliedRules}
|
|
584
|
+
fieldMeta={extractFieldMetaFromSchema(
|
|
585
|
+
${names.capital}BaseSchema,
|
|
586
|
+
SD as (key: string) => string,
|
|
587
|
+
)}
|
|
588
|
+
>
|
|
589
|
+
<Button
|
|
590
|
+
variant="outline"
|
|
591
|
+
size="sm"
|
|
592
|
+
icon={<FilterIcon />}
|
|
593
|
+
onClick={() => setFilterModalOpen(true)}
|
|
594
|
+
className="h-8"
|
|
595
|
+
>
|
|
596
|
+
<span className="text-xs">{SD("rc.sonamuFilter.title")}</span>
|
|
597
|
+
{appliedRules.length > 0 && (
|
|
598
|
+
<Badge variant="secondary" className="ml-1">
|
|
599
|
+
{appliedRules.length}
|
|
600
|
+
</Badge>
|
|
601
|
+
)}
|
|
602
|
+
</Button>
|
|
603
|
+
</SonamuFilterPopover>
|
|
615
604
|
</div>
|
|
616
605
|
</div>
|
|
617
606
|
|
|
@@ -626,32 +615,30 @@ ${filterColumns
|
|
|
626
615
|
col.config && "enumId" in col.config
|
|
627
616
|
? (col.config as { enumId: string }).enumId
|
|
628
617
|
: getEnumInfoFromColName(entityId, col.name).id;
|
|
629
|
-
return ` <
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
))}
|
|
639
|
-
</SelectContent>
|
|
640
|
-
</Select>`;
|
|
618
|
+
return ` <EnumSelect
|
|
619
|
+
key={\`${col.name}-\${listParams.${col.name}}\`}
|
|
620
|
+
enum={${enumId}}
|
|
621
|
+
labels={${enumId}Label}
|
|
622
|
+
{...register("${col.name}")}
|
|
623
|
+
placeholder="${col.label}"
|
|
624
|
+
clearable
|
|
625
|
+
className="w-50 h-8 bg-white border-gray-300 text-xs"
|
|
626
|
+
/>`;
|
|
641
627
|
} catch {
|
|
642
628
|
return "";
|
|
643
629
|
}
|
|
644
630
|
}
|
|
645
|
-
// FK 필드 (
|
|
646
|
-
if (col.
|
|
631
|
+
// FK 필드 (IdAsyncSelect)
|
|
632
|
+
if (col.renderType === "number-fk_id" || col.renderType === "string-fk_id") {
|
|
647
633
|
try {
|
|
648
634
|
const relProp = getRelationPropFromColName(entityId, col.name.replace("_id", ""));
|
|
649
|
-
return `
|
|
635
|
+
return ` <IdAsyncSelect
|
|
636
|
+
config={${relProp.with}AsyncIdConfig}
|
|
650
637
|
subset="A"
|
|
651
638
|
{...register("${col.name}")}
|
|
652
639
|
placeholder="${col.label ?? relProp.with}"
|
|
653
640
|
clearable
|
|
654
|
-
className="w-
|
|
641
|
+
className="w-50 h-8 text-xs"
|
|
655
642
|
/>`;
|
|
656
643
|
} catch {
|
|
657
644
|
return "";
|
|
@@ -663,15 +650,17 @@ ${filterColumns
|
|
|
663
650
|
.join("\n")}
|
|
664
651
|
${
|
|
665
652
|
filterColumns.some((col) => col.name === "orderBy")
|
|
666
|
-
? `
|
|
653
|
+
? ` <EnumSelect
|
|
654
|
+
enum={${names.capital}OrderBy}
|
|
655
|
+
labels={${names.capital}OrderByLabel}
|
|
667
656
|
{...register("orderBy")}
|
|
668
|
-
placeholder="
|
|
669
|
-
textPrefix="
|
|
670
|
-
className="w-
|
|
657
|
+
placeholder={SD("common.sort")}
|
|
658
|
+
textPrefix={\`\${SD("common.sort")}: \`}
|
|
659
|
+
className="w-50 h-8 bg-white border-gray-300 text-xs"
|
|
671
660
|
/>`
|
|
672
661
|
: ""
|
|
673
662
|
}
|
|
674
|
-
<span className="text-xs text-muted-foreground">{total ?? 0}
|
|
663
|
+
<span className="text-xs text-muted-foreground">{SD("common.results")(total ?? 0)}</span>
|
|
675
664
|
</div>
|
|
676
665
|
</div>
|
|
677
666
|
</CardHeader>
|
|
@@ -730,17 +719,29 @@ ${
|
|
|
730
719
|
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
|
731
720
|
<AlertDialogContent>
|
|
732
721
|
<AlertDialogHeader>
|
|
733
|
-
<AlertDialogTitle>
|
|
734
|
-
<AlertDialogDescription>
|
|
735
|
-
This action cannot be undone. This will permanently delete this item.
|
|
736
|
-
</AlertDialogDescription>
|
|
722
|
+
<AlertDialogTitle>{SD("delete.confirm.title")}</AlertDialogTitle>
|
|
723
|
+
<AlertDialogDescription>{SD("delete.confirm.description")}</AlertDialogDescription>
|
|
737
724
|
</AlertDialogHeader>
|
|
738
725
|
<AlertDialogFooter>
|
|
739
|
-
<AlertDialogCancel>
|
|
740
|
-
<AlertDialogAction onClick={handleConfirmDelete}>
|
|
726
|
+
<AlertDialogCancel>{SD("common.cancel")}</AlertDialogCancel>
|
|
727
|
+
<AlertDialogAction onClick={handleConfirmDelete}>
|
|
728
|
+
{SD("common.delete")}
|
|
729
|
+
</AlertDialogAction>
|
|
741
730
|
</AlertDialogFooter>
|
|
742
731
|
</AlertDialogContent>
|
|
743
732
|
</AlertDialog>
|
|
733
|
+
|
|
734
|
+
{/* Sonamu Filter Modal */}
|
|
735
|
+
<SonamuFilterModal
|
|
736
|
+
baseSchema={${names.capital}BaseSchema}
|
|
737
|
+
open={filterModalOpen}
|
|
738
|
+
onOpenChange={setFilterModalOpen}
|
|
739
|
+
initialRules={appliedRules}
|
|
740
|
+
onApply={(filters, rules) => {
|
|
741
|
+
setListParams({ ...listParams, sonamuFilter: filters, page: 1 });
|
|
742
|
+
setAppliedRules(rules);
|
|
743
|
+
}}
|
|
744
|
+
/>
|
|
744
745
|
</div>
|
|
745
746
|
);
|
|
746
747
|
}
|
|
@@ -24,37 +24,32 @@ export class Template__view_search_input extends Template {
|
|
|
24
24
|
return {
|
|
25
25
|
...this.getTargetAndPath(names),
|
|
26
26
|
body: `
|
|
27
|
-
import { Button, Input } from "@sonamu-kit/react-components/components";
|
|
27
|
+
import { Button, EnumSelect, Input } from "@sonamu-kit/react-components/components";
|
|
28
|
+
import { useSonamuBaseContext } from "@sonamu-kit/react-components/contexts";
|
|
28
29
|
import type React from "react";
|
|
29
30
|
import { useState } from "react";
|
|
30
|
-
import { ${names.capital}
|
|
31
|
+
import { ${names.capital}SearchField, ${names.capital}SearchFieldLabel } from "@/services/sonamu.generated";
|
|
31
32
|
import SearchIcon from "~icons/lucide/search";
|
|
32
33
|
import { SD } from "@/i18n/sd.generated";
|
|
33
34
|
export type ${names.capital}SearchInputProps = {
|
|
34
35
|
input: {
|
|
35
36
|
value?: string;
|
|
36
|
-
|
|
37
|
+
onValueChange?: (value: string | null | undefined) => void;
|
|
37
38
|
};
|
|
38
39
|
dropdown: {
|
|
39
40
|
value?: string;
|
|
40
|
-
|
|
41
|
+
onValueChange?: (value: string | null | undefined) => void;
|
|
41
42
|
};
|
|
42
43
|
};
|
|
43
44
|
|
|
44
45
|
export function ${names.capital}SearchInput({
|
|
45
|
-
input: { value: inputValue,
|
|
46
|
+
input: { value: inputValue, onValueChange: inputOnValueChange },
|
|
46
47
|
dropdown: dropdownProps,
|
|
47
48
|
}: ${names.capital}SearchInputProps) {
|
|
48
49
|
const [keyword, setKeyword] = useState<string>(inputValue ?? "");
|
|
49
50
|
|
|
50
51
|
const handleSearch = () => {
|
|
51
|
-
|
|
52
|
-
const syntheticEvent = {
|
|
53
|
-
target: { value: keyword },
|
|
54
|
-
currentTarget: { value: keyword },
|
|
55
|
-
} as React.ChangeEvent<HTMLInputElement>;
|
|
56
|
-
inputOnChange(syntheticEvent);
|
|
57
|
-
}
|
|
52
|
+
inputOnValueChange?.(keyword || undefined);
|
|
58
53
|
};
|
|
59
54
|
|
|
60
55
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
@@ -65,7 +60,16 @@ export function ${names.capital}SearchInput({
|
|
|
65
60
|
|
|
66
61
|
return (
|
|
67
62
|
<div className="flex items-center gap-1">
|
|
68
|
-
|
|
63
|
+
<EnumSelect
|
|
64
|
+
enum={${names.capital}SearchField}
|
|
65
|
+
labels={${names.capital}SearchFieldLabel}
|
|
66
|
+
value={dropdownProps.value}
|
|
67
|
+
onValueChange={(value) => {
|
|
68
|
+
dropdownProps.onValueChange?.(value as string | null | undefined);
|
|
69
|
+
}}
|
|
70
|
+
placeholder={SD("common.search")}
|
|
71
|
+
className="w-[150px] h-8"
|
|
72
|
+
/>
|
|
69
73
|
<div className="relative flex items-center">
|
|
70
74
|
<Input
|
|
71
75
|
type="text"
|
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
type RenderingNode,
|
|
58
58
|
SonamuFileArraySchema,
|
|
59
59
|
SonamuFileSchema,
|
|
60
|
+
type ZodStringFormat,
|
|
60
61
|
} from "../types/types";
|
|
61
62
|
import { createImportUrl } from "../utils/esm-utils";
|
|
62
63
|
import { runtimePath } from "../utils/path-utils";
|
|
@@ -87,6 +88,72 @@ export const BUILT_IN_TYPES = {
|
|
|
87
88
|
},
|
|
88
89
|
} as const;
|
|
89
90
|
|
|
91
|
+
/**
|
|
92
|
+
* zodFormat을 Zod 4 코드 문자열로 변환합니다.
|
|
93
|
+
* Zod 4에서는 z.email(), z.uuid() 등 독립적인 함수 형태를 사용합니다.
|
|
94
|
+
*/
|
|
95
|
+
function zodFormatToCode(format: ZodStringFormat): string {
|
|
96
|
+
// ISO 포맷은 z.iso.xxx() 형태
|
|
97
|
+
const isoFormats: Record<string, string> = {
|
|
98
|
+
isoDate: "z.iso.date()",
|
|
99
|
+
isoTime: "z.iso.time()",
|
|
100
|
+
isoDatetime: "z.iso.datetime()",
|
|
101
|
+
isoDuration: "z.iso.duration()",
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// hash 포맷은 z.hash("algorithm") 형태
|
|
105
|
+
const hashFormats: Record<string, string> = {
|
|
106
|
+
hashMd5: 'z.hash("md5")',
|
|
107
|
+
hashSha1: 'z.hash("sha1")',
|
|
108
|
+
hashSha256: 'z.hash("sha256")',
|
|
109
|
+
hashSha384: 'z.hash("sha384")',
|
|
110
|
+
hashSha512: 'z.hash("sha512")',
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (format in isoFormats) {
|
|
114
|
+
return isoFormats[format];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (format in hashFormats) {
|
|
118
|
+
return hashFormats[format];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 기본 포맷은 z.xxx() 형태 (Zod 4)
|
|
122
|
+
return `z.${format}()`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* zodFormat을 Zod 4 타입으로 변환합니다.
|
|
127
|
+
* Zod 4에서는 z.email(), z.uuid() 등 독립적인 함수 형태를 사용합니다.
|
|
128
|
+
*/
|
|
129
|
+
function zodFormatToType(format: ZodStringFormat): z.ZodType {
|
|
130
|
+
// ISO 포맷은 z.iso.xxx() 형태
|
|
131
|
+
switch (format) {
|
|
132
|
+
case "isoDate":
|
|
133
|
+
return z.iso.date();
|
|
134
|
+
case "isoTime":
|
|
135
|
+
return z.iso.time();
|
|
136
|
+
case "isoDatetime":
|
|
137
|
+
return z.iso.datetime();
|
|
138
|
+
case "isoDuration":
|
|
139
|
+
return z.iso.duration();
|
|
140
|
+
// hash 포맷은 z.hash("algorithm") 형태
|
|
141
|
+
case "hashMd5":
|
|
142
|
+
return z.hash("md5");
|
|
143
|
+
case "hashSha1":
|
|
144
|
+
return z.hash("sha1");
|
|
145
|
+
case "hashSha256":
|
|
146
|
+
return z.hash("sha256");
|
|
147
|
+
case "hashSha384":
|
|
148
|
+
return z.hash("sha384");
|
|
149
|
+
case "hashSha512":
|
|
150
|
+
return z.hash("sha512");
|
|
151
|
+
// 기본 포맷은 z.xxx() 형태 (Zod 4)
|
|
152
|
+
default:
|
|
153
|
+
return (z as unknown as Record<string, () => z.ZodType>)[format]();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
90
157
|
/**
|
|
91
158
|
* Zod 타입 ID로부터 동적으로 Zod 스키마를 로드합니다.
|
|
92
159
|
* 내장 타입(BUILT_IN_TYPE_IDS)은 바로 반환하고,
|
|
@@ -136,17 +203,29 @@ export async function propToZodType(prop: EntityProp): Promise<z.ZodTypeAny> {
|
|
|
136
203
|
} else if (isEnumArrayProp(prop)) {
|
|
137
204
|
zodType = (await getZodTypeById(prop.id)).array();
|
|
138
205
|
} else if (isStringSingleProp(prop)) {
|
|
139
|
-
if (prop.
|
|
206
|
+
if (prop.zodFormat) {
|
|
207
|
+
zodType = zodFormatToType(prop.zodFormat);
|
|
208
|
+
if (prop.length && "max" in zodType) {
|
|
209
|
+
zodType = (zodType as z.ZodString).max(prop.length);
|
|
210
|
+
}
|
|
211
|
+
} else if (prop.length) {
|
|
140
212
|
zodType = z.string().max(prop.length);
|
|
141
213
|
} else {
|
|
142
214
|
zodType = z.string();
|
|
143
215
|
}
|
|
144
216
|
} else if (isStringArrayProp(prop)) {
|
|
145
|
-
|
|
146
|
-
|
|
217
|
+
let elementType: z.ZodType;
|
|
218
|
+
if (prop.zodFormat) {
|
|
219
|
+
elementType = zodFormatToType(prop.zodFormat);
|
|
220
|
+
if (prop.length && "max" in elementType) {
|
|
221
|
+
elementType = (elementType as z.ZodString).max(prop.length);
|
|
222
|
+
}
|
|
223
|
+
} else if (prop.length) {
|
|
224
|
+
elementType = z.string().max(prop.length);
|
|
147
225
|
} else {
|
|
148
|
-
|
|
226
|
+
elementType = z.string();
|
|
149
227
|
}
|
|
228
|
+
zodType = elementType.array();
|
|
150
229
|
} else if (isNumberSingleProp(prop)) {
|
|
151
230
|
zodType = z.number();
|
|
152
231
|
} else if (isNumberArrayProp(prop)) {
|
|
@@ -220,13 +299,27 @@ export function propToZodTypeDef(prop: EntityProp, injectImportKeys: string[]):
|
|
|
220
299
|
stmt = `${prop.name}: ${prop.id}.array()`;
|
|
221
300
|
injectImportKeys.push(prop.id);
|
|
222
301
|
} else if (isStringSingleProp(prop)) {
|
|
223
|
-
if (prop.
|
|
302
|
+
if (prop.zodFormat) {
|
|
303
|
+
const base = zodFormatToCode(prop.zodFormat);
|
|
304
|
+
if (prop.length) {
|
|
305
|
+
stmt = `${prop.name}: ${base}.max(${prop.length})`;
|
|
306
|
+
} else {
|
|
307
|
+
stmt = `${prop.name}: ${base}`;
|
|
308
|
+
}
|
|
309
|
+
} else if (prop.length) {
|
|
224
310
|
stmt = `${prop.name}: z.string().max(${prop.length})`;
|
|
225
311
|
} else {
|
|
226
312
|
stmt = `${prop.name}: z.string()`;
|
|
227
313
|
}
|
|
228
314
|
} else if (isStringArrayProp(prop)) {
|
|
229
|
-
if (prop.
|
|
315
|
+
if (prop.zodFormat) {
|
|
316
|
+
const base = zodFormatToCode(prop.zodFormat);
|
|
317
|
+
if (prop.length) {
|
|
318
|
+
stmt = `${prop.name}: ${base}.max(${prop.length}).array()`;
|
|
319
|
+
} else {
|
|
320
|
+
stmt = `${prop.name}: ${base}.array()`;
|
|
321
|
+
}
|
|
322
|
+
} else if (prop.length) {
|
|
230
323
|
stmt = `${prop.name}: z.string().max(${prop.length}).array()`;
|
|
231
324
|
} else {
|
|
232
325
|
stmt = `${prop.name}: z.string().array()`;
|
|
@@ -653,6 +746,10 @@ function resolveRenderType(key: string, zodType: z.ZodTypeAny): RenderingNode["r
|
|
|
653
746
|
return "string-datetime";
|
|
654
747
|
} else if (key.endsWith("date")) {
|
|
655
748
|
return "string-date";
|
|
749
|
+
} else if (key === "id") {
|
|
750
|
+
return "string-id";
|
|
751
|
+
} else if (key.endsWith("_id")) {
|
|
752
|
+
return "string-fk_id";
|
|
656
753
|
} else {
|
|
657
754
|
return "string-plain";
|
|
658
755
|
}
|
|
@@ -44,7 +44,7 @@ export class RelationGraph {
|
|
|
44
44
|
}
|
|
45
45
|
} else if (isManyToManyRelationProp(prop)) {
|
|
46
46
|
// ManyToMany 관계의 경우 양방향 의존성 추가
|
|
47
|
-
const relatedIds = column.value as unknown as number[];
|
|
47
|
+
const relatedIds = column.value as unknown as (number | string)[];
|
|
48
48
|
for (const relatedId of relatedIds) {
|
|
49
49
|
const relatedFixtureId = `${prop.with}#${relatedId}`;
|
|
50
50
|
if (this.graph.has(relatedFixtureId)) {
|