sonamu 0.7.15 → 0.7.17
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/providers/rtzr/error.d.ts +1 -1
- package/dist/ai/providers/rtzr/error.d.ts.map +1 -1
- package/dist/api/config.d.ts +1 -0
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +1 -1
- package/dist/api/decorators.d.ts +1 -1
- package/dist/api/decorators.d.ts.map +1 -1
- package/dist/api/decorators.js +1 -1
- package/dist/api/sonamu.d.ts +3 -1
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +51 -40
- package/dist/database/base-model.d.ts +16 -6
- package/dist/database/base-model.d.ts.map +1 -1
- package/dist/database/base-model.js +44 -3
- package/dist/database/base-model.types.d.ts +29 -48
- package/dist/database/base-model.types.d.ts.map +1 -1
- package/dist/database/base-model.types.js +12 -2
- package/dist/database/puri.d.ts +2 -1
- package/dist/database/puri.d.ts.map +1 -1
- package/dist/database/puri.js +2 -1
- package/dist/database/puri.types.d.ts +3 -3
- package/dist/database/puri.types.d.ts.map +1 -1
- package/dist/database/puri.types.js +1 -1
- package/dist/entity/entity-manager.d.ts +8 -4
- package/dist/entity/entity-manager.d.ts.map +1 -1
- package/dist/entity/entity.d.ts +10 -1
- package/dist/entity/entity.d.ts.map +1 -1
- package/dist/entity/entity.js +84 -39
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/syncer/checksum.d.ts +8 -3
- package/dist/syncer/checksum.d.ts.map +1 -1
- package/dist/syncer/checksum.js +17 -9
- package/dist/syncer/code-generator.js +7 -2
- package/dist/syncer/syncer.d.ts +6 -6
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/syncer/syncer.js +27 -13
- package/dist/tasks/workflow-manager.d.ts +3 -3
- package/dist/tasks/workflow-manager.d.ts.map +1 -1
- package/dist/tasks/workflow-manager.js +15 -11
- package/dist/template/implementations/generated.template.d.ts.map +1 -1
- package/dist/template/implementations/generated.template.js +8 -6
- package/dist/template/implementations/model.template.js +5 -5
- package/dist/template/implementations/services.template.d.ts +17 -0
- package/dist/template/implementations/services.template.d.ts.map +1 -0
- package/dist/template/implementations/services.template.js +159 -0
- package/dist/template/implementations/view_form.template.js +2 -2
- package/dist/template/implementations/view_id_async_select.template.js +2 -2
- package/dist/template/implementations/view_list.template.js +5 -5
- package/dist/types/types.d.ts +43 -25
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +29 -17
- package/dist/ui/ai-api.d.ts +2 -0
- package/dist/ui/ai-api.d.ts.map +1 -1
- package/dist/ui/ai-api.js +43 -49
- package/dist/ui/ai-client.d.ts +10 -0
- package/dist/ui/ai-client.d.ts.map +1 -1
- package/dist/ui/ai-client.js +457 -437
- package/dist/ui/api.d.ts.map +1 -1
- package/dist/ui/api.js +14 -3
- package/dist/ui-web/assets/{index-J9MCfjCd.js → index-DzqUrTB-.js} +56 -59
- package/dist/ui-web/index.html +1 -1
- package/package.json +12 -8
- package/src/api/config.ts +3 -0
- package/src/api/decorators.ts +6 -1
- package/src/api/sonamu.ts +71 -52
- package/src/database/base-model.ts +66 -11
- package/src/database/base-model.types.ts +79 -76
- package/src/database/puri.ts +5 -1
- package/src/database/puri.types.ts +3 -6
- package/src/entity/entity.ts +83 -34
- package/src/index.ts +1 -0
- package/src/shared/app.shared.ts.txt +1 -1
- package/src/shared/web.shared.ts.txt +0 -43
- package/src/syncer/checksum.ts +31 -9
- package/src/syncer/code-generator.ts +8 -1
- package/src/syncer/syncer.ts +38 -26
- package/src/tasks/workflow-manager.ts +16 -12
- package/src/template/implementations/generated.template.ts +17 -3
- package/src/template/implementations/model.template.ts +4 -4
- package/src/template/implementations/services.template.ts +226 -0
- package/src/template/implementations/view_form.template.ts +1 -1
- package/src/template/implementations/view_id_async_select.template.ts +1 -1
- package/src/template/implementations/view_list.template.ts +4 -4
- package/src/types/types.ts +33 -16
- package/src/ui/ai-api.ts +61 -60
- package/src/ui/ai-client.ts +535 -499
- package/src/ui/api.ts +14 -2
- package/src/ui/entity.instructions.md +536 -0
- package/dist/template/implementations/service.template.d.ts +0 -29
- package/dist/template/implementations/service.template.d.ts.map +0 -1
- package/dist/template/implementations/service.template.js +0 -202
- package/dist/ui-web/assets/provider-utils_false-BKJD46kk.js +0 -1
- package/dist/ui-web/assets/provider-utils_false-Bu5lmX18.js +0 -1
- package/src/template/implementations/service.template.ts +0 -328
package/src/entity/entity.ts
CHANGED
|
@@ -14,12 +14,15 @@ import {
|
|
|
14
14
|
isBelongsToOneRelationProp,
|
|
15
15
|
isEnumProp,
|
|
16
16
|
isHasManyRelationProp,
|
|
17
|
+
isInternalSubsetField,
|
|
17
18
|
isManyToManyRelationProp,
|
|
18
19
|
isOneToOneRelationProp,
|
|
19
20
|
isRelationProp,
|
|
21
|
+
isVirtualCodeProp,
|
|
20
22
|
isVirtualProp,
|
|
23
|
+
normalizeSubsetField,
|
|
21
24
|
type RelationProp,
|
|
22
|
-
type
|
|
25
|
+
type SubsetField,
|
|
23
26
|
type SubsetQuery,
|
|
24
27
|
} from "../types/types";
|
|
25
28
|
import { importMembers } from "../utils/esm-utils";
|
|
@@ -50,6 +53,9 @@ export class Entity {
|
|
|
50
53
|
subsets: {
|
|
51
54
|
[key: string]: string[];
|
|
52
55
|
};
|
|
56
|
+
subsetsInternal: {
|
|
57
|
+
[key: string]: string[];
|
|
58
|
+
};
|
|
53
59
|
types: {
|
|
54
60
|
[name: string]: z.ZodTypeAny;
|
|
55
61
|
} = {};
|
|
@@ -98,8 +104,13 @@ export class Entity {
|
|
|
98
104
|
// indexes
|
|
99
105
|
this.indexes = indexes ?? [];
|
|
100
106
|
|
|
101
|
-
// subsets
|
|
102
|
-
this.subsets =
|
|
107
|
+
// subsets: SubsetField[]를 파싱하여 subsets(일반)와 subsetsInternal(internal)로 분리
|
|
108
|
+
this.subsets = {};
|
|
109
|
+
this.subsetsInternal = {};
|
|
110
|
+
for (const [key, fields] of Object.entries(subsets ?? {})) {
|
|
111
|
+
this.subsets[key] = fields.filter((f) => !isInternalSubsetField(f)).map(normalizeSubsetField);
|
|
112
|
+
this.subsetsInternal[key] = fields.filter(isInternalSubsetField).map(normalizeSubsetField);
|
|
113
|
+
}
|
|
103
114
|
|
|
104
115
|
// enums
|
|
105
116
|
this.enumLabels = enums ?? {};
|
|
@@ -117,11 +128,18 @@ export class Entity {
|
|
|
117
128
|
};
|
|
118
129
|
}
|
|
119
130
|
|
|
131
|
+
/**
|
|
132
|
+
* 쿼리용 서브셋 필드를 반환합니다 (subsets + subsetsInternal 합침)
|
|
133
|
+
*/
|
|
134
|
+
getSubsetFieldsForQuery(subsetKey: string): string[] {
|
|
135
|
+
return [...(this.subsets[subsetKey] ?? []), ...(this.subsetsInternal[subsetKey] ?? [])];
|
|
136
|
+
}
|
|
137
|
+
|
|
120
138
|
/**
|
|
121
139
|
* 주어진 이름(subsetKey)의 subset을 실제로 가져오는 Puri 코드 구현체 string을 반환합니다.
|
|
122
140
|
*/
|
|
123
141
|
getPuriSubsetQuery(subsetKey: string): string {
|
|
124
|
-
const subset = this.
|
|
142
|
+
const subset = this.getSubsetFieldsForQuery(subsetKey);
|
|
125
143
|
const subsetQuery = this.resolveSubsetQuery("", subset);
|
|
126
144
|
|
|
127
145
|
const lines: string[] = [];
|
|
@@ -148,7 +166,6 @@ export class Entity {
|
|
|
148
166
|
|
|
149
167
|
// select - 입체적 구조로 생성
|
|
150
168
|
const selectObj = this.buildNestedSelectObject(subsetQuery.select);
|
|
151
|
-
|
|
152
169
|
lines.push(`.select(${this.stringifyNestedSelectObject(selectObj)});`);
|
|
153
170
|
|
|
154
171
|
return lines.join("\n");
|
|
@@ -263,7 +280,7 @@ export class Entity {
|
|
|
263
280
|
}
|
|
264
281
|
|
|
265
282
|
getPuriLoaderQuery(subsetKey: string): string {
|
|
266
|
-
const subset = this.
|
|
283
|
+
const subset = this.getSubsetFieldsForQuery(subsetKey);
|
|
267
284
|
const { loaders } = this.resolveSubsetQuery("", subset);
|
|
268
285
|
|
|
269
286
|
const lines: string[] = [`[`];
|
|
@@ -370,7 +387,7 @@ export class Entity {
|
|
|
370
387
|
subset SELECT/JOIN/LOADER 결과 리턴
|
|
371
388
|
*/
|
|
372
389
|
getSubsetQuery(subsetKey: string): SubsetQuery {
|
|
373
|
-
const subset = this.
|
|
390
|
+
const subset = this.getSubsetFieldsForQuery(subsetKey);
|
|
374
391
|
|
|
375
392
|
const result: SubsetQuery = this.resolveSubsetQuery("", subset);
|
|
376
393
|
return result;
|
|
@@ -404,12 +421,16 @@ export class Entity {
|
|
|
404
421
|
// 현재 테이블 필드셋은 select, virtual에 추가하고 리턴
|
|
405
422
|
if (groupKey === "") {
|
|
406
423
|
const realFields = fields.filter((field) => !isVirtualProp(this.propsDict[field]));
|
|
407
|
-
|
|
424
|
+
// virtualType: "code" (또는 undefined)인 virtual prop만 r.virtual에 추가
|
|
425
|
+
// virtualType: "query"인 경우 사용자가 appendSelect로 직접 추가하므로 제외
|
|
426
|
+
const virtualCodeFields = fields.filter((field) =>
|
|
427
|
+
isVirtualCodeProp(this.propsDict[field]),
|
|
428
|
+
);
|
|
408
429
|
|
|
409
430
|
if (prefix === "") {
|
|
410
431
|
// 현재 테이블인 경우
|
|
411
432
|
r.select = r.select.concat(realFields.map((field) => `${this.table}.${field}`));
|
|
412
|
-
r.virtual = r.virtual.concat(
|
|
433
|
+
r.virtual = r.virtual.concat(virtualCodeFields);
|
|
413
434
|
} else {
|
|
414
435
|
// 넘어온 테이블인 경우
|
|
415
436
|
r.select = r.select.concat(
|
|
@@ -605,25 +626,12 @@ export class Entity {
|
|
|
605
626
|
},
|
|
606
627
|
);
|
|
607
628
|
|
|
608
|
-
return Object.keys(groups).flatMap((key) => {
|
|
629
|
+
return Object.keys(groups).flatMap<EntityPropNode, EntityPropNode[]>((key) => {
|
|
609
630
|
const group = groups[key];
|
|
610
631
|
|
|
611
632
|
// 일반 prop 처리
|
|
612
633
|
if (key === "") {
|
|
613
634
|
return group.map((propName) => {
|
|
614
|
-
// FIXME: 이거 나중에 없애야함
|
|
615
|
-
if (propName === "말도안되는프롭명__이거왜타입처리가꼬여서이러지?") {
|
|
616
|
-
return {
|
|
617
|
-
nodeType: "plain" as const,
|
|
618
|
-
prop: {
|
|
619
|
-
type: "string",
|
|
620
|
-
name: "uuid",
|
|
621
|
-
length: 128,
|
|
622
|
-
} as StringProp,
|
|
623
|
-
children: [],
|
|
624
|
-
} as EntityPropNode;
|
|
625
|
-
}
|
|
626
|
-
|
|
627
635
|
const prop = entity.props.find((p) => p.name === propName);
|
|
628
636
|
if (prop === undefined) {
|
|
629
637
|
console.log({ propName, groups });
|
|
@@ -632,7 +640,6 @@ export class Entity {
|
|
|
632
640
|
return {
|
|
633
641
|
nodeType: "plain" as const,
|
|
634
642
|
prop,
|
|
635
|
-
children: [],
|
|
636
643
|
};
|
|
637
644
|
});
|
|
638
645
|
}
|
|
@@ -656,7 +663,6 @@ export class Entity {
|
|
|
656
663
|
name: `${key}_id`,
|
|
657
664
|
nullable: prop.nullable,
|
|
658
665
|
},
|
|
659
|
-
children: [],
|
|
660
666
|
};
|
|
661
667
|
}
|
|
662
668
|
}
|
|
@@ -671,9 +677,9 @@ export class Entity {
|
|
|
671
677
|
: ("array" as const);
|
|
672
678
|
|
|
673
679
|
return {
|
|
680
|
+
nodeType,
|
|
674
681
|
prop,
|
|
675
682
|
children,
|
|
676
|
-
nodeType,
|
|
677
683
|
};
|
|
678
684
|
});
|
|
679
685
|
}
|
|
@@ -793,6 +799,17 @@ export class Entity {
|
|
|
793
799
|
}
|
|
794
800
|
|
|
795
801
|
toJson(): EntityJson {
|
|
802
|
+
// subsets와 subsetsInternal을 SubsetField[] 형태로 복원
|
|
803
|
+
const subsets: { [key: string]: SubsetField[] } = {};
|
|
804
|
+
for (const key of Object.keys(this.subsets)) {
|
|
805
|
+
const normalFields: SubsetField[] = this.subsets[key];
|
|
806
|
+
const internalFields: SubsetField[] = (this.subsetsInternal[key] ?? []).map((field) => ({
|
|
807
|
+
field,
|
|
808
|
+
internal: true,
|
|
809
|
+
}));
|
|
810
|
+
subsets[key] = [...normalFields, ...internalFields];
|
|
811
|
+
}
|
|
812
|
+
|
|
796
813
|
return {
|
|
797
814
|
id: this.id,
|
|
798
815
|
parentId: this.parentId,
|
|
@@ -800,7 +817,7 @@ export class Entity {
|
|
|
800
817
|
title: this.title,
|
|
801
818
|
props: this.props,
|
|
802
819
|
indexes: this.indexes,
|
|
803
|
-
subsets
|
|
820
|
+
subsets,
|
|
804
821
|
enums: this.enumLabels,
|
|
805
822
|
};
|
|
806
823
|
}
|
|
@@ -810,7 +827,12 @@ export class Entity {
|
|
|
810
827
|
const subsetRows = this.getSubsetRows();
|
|
811
828
|
this.subsets = Object.fromEntries(
|
|
812
829
|
Object.entries(this.subsets).map(([subsetKey]) => {
|
|
813
|
-
return [subsetKey, this.subsetRowsToSubsetFields(subsetRows, subsetKey)];
|
|
830
|
+
return [subsetKey, this.subsetRowsToSubsetFields(subsetRows, subsetKey, false)];
|
|
831
|
+
}),
|
|
832
|
+
);
|
|
833
|
+
this.subsetsInternal = Object.fromEntries(
|
|
834
|
+
Object.entries(this.subsetsInternal).map(([subsetKey]) => {
|
|
835
|
+
return [subsetKey, this.subsetRowsToSubsetFields(subsetRows, subsetKey, true)];
|
|
814
836
|
}),
|
|
815
837
|
);
|
|
816
838
|
|
|
@@ -828,6 +850,7 @@ export class Entity {
|
|
|
828
850
|
|
|
829
851
|
getSubsetRows(
|
|
830
852
|
_subsets?: { [key: string]: string[] },
|
|
853
|
+
_subsetsInternal?: { [key: string]: string[] },
|
|
831
854
|
prefixes: string[] = [],
|
|
832
855
|
): EntitySubsetRow[] {
|
|
833
856
|
if (prefixes.length > 10) {
|
|
@@ -835,16 +858,23 @@ export class Entity {
|
|
|
835
858
|
}
|
|
836
859
|
|
|
837
860
|
const subsets = _subsets ?? this.subsets;
|
|
861
|
+
const subsetsInternal = _subsetsInternal ?? this.subsetsInternal;
|
|
838
862
|
const subsetKeys = Object.keys(subsets);
|
|
839
863
|
const allFields = unique(subsetKeys.flatMap((key) => subsets[key]));
|
|
864
|
+
// internal 필드도 allFields에 포함 (relation 탐색용)
|
|
865
|
+
const allInternalFields = unique(subsetKeys.flatMap((key) => subsetsInternal[key] ?? []));
|
|
866
|
+
const combinedFields = unique([...allFields, ...allInternalFields]);
|
|
840
867
|
|
|
841
868
|
return this.props.map((prop) => {
|
|
842
869
|
if (
|
|
843
870
|
prop.type === "relation" &&
|
|
844
|
-
|
|
871
|
+
combinedFields.find((f) => f.startsWith(`${[...prefixes, prop.name].join(".")}.`))
|
|
845
872
|
) {
|
|
846
873
|
const relEntity = EntityManager.get(prop.with);
|
|
847
|
-
const children = relEntity.getSubsetRows(subsets,
|
|
874
|
+
const children = relEntity.getSubsetRows(subsets, subsetsInternal, [
|
|
875
|
+
...prefixes,
|
|
876
|
+
`${prop.name}`,
|
|
877
|
+
]);
|
|
848
878
|
|
|
849
879
|
return {
|
|
850
880
|
field: prop.name,
|
|
@@ -857,9 +887,15 @@ export class Entity {
|
|
|
857
887
|
return [subsetKey, children.every((child) => child.has[subsetKey] === true)];
|
|
858
888
|
}),
|
|
859
889
|
),
|
|
890
|
+
isInternal: Object.fromEntries(
|
|
891
|
+
subsetKeys.map((subsetKey) => {
|
|
892
|
+
return [subsetKey, children.every((child) => child.isInternal[subsetKey] === true)];
|
|
893
|
+
}),
|
|
894
|
+
),
|
|
860
895
|
};
|
|
861
896
|
}
|
|
862
897
|
|
|
898
|
+
const field = [...prefixes, prop.name].join(".");
|
|
863
899
|
return {
|
|
864
900
|
field: prop.name,
|
|
865
901
|
children: [],
|
|
@@ -869,22 +905,35 @@ export class Entity {
|
|
|
869
905
|
subsetKeys.map((subsetKey) => {
|
|
870
906
|
const subsetFields = subsets[subsetKey];
|
|
871
907
|
const has = subsetFields.some((f) => {
|
|
872
|
-
const field = [...prefixes, prop.name].join(".");
|
|
873
908
|
return f === field || f.startsWith(`${field}.`);
|
|
874
909
|
});
|
|
875
910
|
return [subsetKey, has];
|
|
876
911
|
}),
|
|
877
912
|
),
|
|
913
|
+
isInternal: Object.fromEntries(
|
|
914
|
+
subsetKeys.map((subsetKey) => {
|
|
915
|
+
const internalFields = subsetsInternal[subsetKey] ?? [];
|
|
916
|
+
const isInternal = internalFields.some((f) => {
|
|
917
|
+
return f === field || f.startsWith(`${field}.`);
|
|
918
|
+
});
|
|
919
|
+
return [subsetKey, isInternal];
|
|
920
|
+
}),
|
|
921
|
+
),
|
|
878
922
|
};
|
|
879
923
|
});
|
|
880
924
|
}
|
|
881
925
|
|
|
882
|
-
subsetRowsToSubsetFields(
|
|
926
|
+
subsetRowsToSubsetFields(
|
|
927
|
+
subsetRows: EntitySubsetRow[],
|
|
928
|
+
subsetKey: string,
|
|
929
|
+
internal: boolean = false,
|
|
930
|
+
): string[] {
|
|
931
|
+
const hasKey = internal ? "isInternal" : "has";
|
|
883
932
|
return subsetRows
|
|
884
933
|
.map((subsetRow) => {
|
|
885
934
|
if (subsetRow.children.length > 0) {
|
|
886
|
-
return this.subsetRowsToSubsetFields(subsetRow.children, subsetKey);
|
|
887
|
-
} else if (subsetRow
|
|
935
|
+
return this.subsetRowsToSubsetFields(subsetRow.children, subsetKey, internal);
|
|
936
|
+
} else if (subsetRow[hasKey][subsetKey]) {
|
|
888
937
|
return subsetRow.prefixes.concat(subsetRow.field).join(".");
|
|
889
938
|
} else {
|
|
890
939
|
return null;
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export type * from "./api/context";
|
|
|
4
4
|
export * from "./api/decorators";
|
|
5
5
|
export * from "./api/sonamu";
|
|
6
6
|
export * from "./database/base-model";
|
|
7
|
+
export * from "./database/base-model.types";
|
|
7
8
|
export * from "./database/db";
|
|
8
9
|
export * from "./database/puri";
|
|
9
10
|
export * from "./database/puri.types";
|
|
@@ -322,7 +322,7 @@ export function useSSEStream<T extends Record<string, any>>(
|
|
|
322
322
|
|
|
323
323
|
// URL에 파라미터 추가 - 절대 URL로 변환
|
|
324
324
|
const queryString = qs.stringify(params);
|
|
325
|
-
const baseUrl = url.startsWith("http") ? url :
|
|
325
|
+
const baseUrl = url.startsWith("http") ? url : `$[[baseUrl]]${url}`;
|
|
326
326
|
const fullUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl;
|
|
327
327
|
|
|
328
328
|
const eventSource = new EventSource(fullUrl);
|
|
@@ -108,49 +108,6 @@ export const SonamuSemanticParams = z
|
|
|
108
108
|
.partial();
|
|
109
109
|
export type SonamuSemanticParams = z.infer<typeof SonamuSemanticParams>;
|
|
110
110
|
|
|
111
|
-
/*
|
|
112
|
-
SWR
|
|
113
|
-
*/
|
|
114
|
-
export type SwrOptions = {
|
|
115
|
-
conditional?: () => boolean;
|
|
116
|
-
};
|
|
117
|
-
export type SWRError = {
|
|
118
|
-
name: string;
|
|
119
|
-
message: string;
|
|
120
|
-
statusCode: number;
|
|
121
|
-
};
|
|
122
|
-
export async function swrFetcher(args: [string, object]): Promise<any> {
|
|
123
|
-
try {
|
|
124
|
-
const [url, params] = args;
|
|
125
|
-
const res = await axios.get(`${url}?${qs.stringify(params)}`);
|
|
126
|
-
return res.data;
|
|
127
|
-
} catch (e: any) {
|
|
128
|
-
const error: any = new Error(e.response.data.message ?? e.response.message ?? "Unknown");
|
|
129
|
-
error.statusCode = e.response?.data.statusCode ?? e.response.status;
|
|
130
|
-
throw error;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
export async function swrPostFetcher(args: [string, object]): Promise<any> {
|
|
134
|
-
try {
|
|
135
|
-
const [url, params] = args;
|
|
136
|
-
const res = await axios.post(url, params);
|
|
137
|
-
return res.data;
|
|
138
|
-
} catch (e: any) {
|
|
139
|
-
const error: any = new Error(e.response.data.message ?? e.response.message ?? "Unknown");
|
|
140
|
-
error.statusCode = e.response?.data.statusCode ?? e.response.status;
|
|
141
|
-
throw error;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
export function handleConditional(
|
|
145
|
-
args: [string, object],
|
|
146
|
-
conditional?: () => boolean,
|
|
147
|
-
): [string, object] | null {
|
|
148
|
-
if (conditional) {
|
|
149
|
-
return conditional() ? args : null;
|
|
150
|
-
}
|
|
151
|
-
return args;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
111
|
/*
|
|
155
112
|
Utils
|
|
156
113
|
*/
|
package/src/syncer/checksum.ts
CHANGED
|
@@ -48,22 +48,38 @@ export async function renewChecksums(): Promise<void> {
|
|
|
48
48
|
await saveChecksums(calculatedChecksums);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
export type FileOrData =
|
|
52
|
+
| {
|
|
53
|
+
path: PathLike;
|
|
54
|
+
}
|
|
55
|
+
| {
|
|
56
|
+
data: string;
|
|
57
|
+
};
|
|
58
|
+
|
|
51
59
|
/**
|
|
52
60
|
* 두 파일의 내용이 같은지 체크섬으로 비교합니다.
|
|
53
61
|
* 만약 파일이 둘 중 하나라도 없다면 비교 불가로 false 반환합니다.
|
|
54
|
-
* @param one 파일 경로
|
|
55
|
-
* @param two 파일 경로
|
|
62
|
+
* @param one 파일 경로 혹은 데이터
|
|
63
|
+
* @param two 파일 경로 혹은 데이터
|
|
56
64
|
* @returns boolean
|
|
57
65
|
*/
|
|
58
|
-
export async function areFilesSame(
|
|
59
|
-
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
66
|
+
export async function areFilesSame(...files: FileOrData[]): Promise<boolean> {
|
|
67
|
+
const checksums: string[] = [];
|
|
62
68
|
|
|
63
|
-
const
|
|
64
|
-
|
|
69
|
+
for (const file of files) {
|
|
70
|
+
if ("path" in file && !(await exists(file.path))) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
checksums.push(
|
|
75
|
+
"path" in file ? await getChecksumOfFile(file.path) : getChecksumOfData(file.data),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
65
78
|
|
|
66
|
-
return
|
|
79
|
+
return checksums.every(
|
|
80
|
+
// 다음 체크섬과 비교, 만약 마지막 체크섬일 때는 첫 번째 체크섬과 비교
|
|
81
|
+
(checksum, index) => checksum === checksums[index === checksums.length - 1 ? 0 : index + 1],
|
|
82
|
+
);
|
|
67
83
|
}
|
|
68
84
|
|
|
69
85
|
async function getCurrentChecksums(): Promise<PathAndChecksum[]> {
|
|
@@ -125,6 +141,12 @@ async function saveChecksums(checksums: PathAndChecksum[]): Promise<void> {
|
|
|
125
141
|
console.log("checksum saved", checksumFilePath);
|
|
126
142
|
}
|
|
127
143
|
|
|
144
|
+
function getChecksumOfData(data: string): string {
|
|
145
|
+
const hash = crypto.createHash("sha1");
|
|
146
|
+
hash.update(data);
|
|
147
|
+
return hash.digest("hex");
|
|
148
|
+
}
|
|
149
|
+
|
|
128
150
|
async function getChecksumOfFile(filePath: PathLike): Promise<string> {
|
|
129
151
|
return new Promise<string>((resolve, reject) => {
|
|
130
152
|
const hash = crypto.createHash("sha1");
|
|
@@ -115,7 +115,14 @@ async function resolveRenderedTemplate(
|
|
|
115
115
|
const importDefs = importKeys
|
|
116
116
|
.reduce(
|
|
117
117
|
(r, importKey) => {
|
|
118
|
-
|
|
118
|
+
let modulePath = importKey;
|
|
119
|
+
try {
|
|
120
|
+
modulePath = EntityManager.getModulePath(importKey);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`[resolveRenderedTemplate:${key}] ${importKey} 모듈 경로 찾기 실패: ${error}`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
119
126
|
let importPath = modulePath;
|
|
120
127
|
if (modulePath.includes("/") || modulePath.includes(".")) {
|
|
121
128
|
importPath = wrapIf(path.relative(path.dirname(filePath), modulePath), (p) => [
|
package/src/syncer/syncer.ts
CHANGED
|
@@ -161,6 +161,13 @@ export class Syncer {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
async copySharedToTargets(targets: string[]): Promise<void> {
|
|
164
|
+
// 특정 변수 치환을 위해서 사용합니다.
|
|
165
|
+
const convertMap = {
|
|
166
|
+
baseUrl:
|
|
167
|
+
Sonamu.config.server.baseUrl ??
|
|
168
|
+
`http://${Sonamu.config.server.listen?.host ?? "localhost"}:${Sonamu.config.server.listen?.port ?? 3000}`,
|
|
169
|
+
};
|
|
170
|
+
|
|
164
171
|
for (const target of targets) {
|
|
165
172
|
// 지금 가져가려는 이 파일은 Sonamu 코드베이스의 일부입니다.
|
|
166
173
|
// 그런데 dist 속 빌드된 소스 코드 파일이 필요한 것이 아니고, src에만 있는 텍스트 파일이 필요합니다.
|
|
@@ -178,6 +185,12 @@ export class Syncer {
|
|
|
178
185
|
);
|
|
179
186
|
}
|
|
180
187
|
|
|
188
|
+
const fullText = await readFile(srcPath, "utf-8");
|
|
189
|
+
const convertedText = Object.entries(convertMap).reduce(
|
|
190
|
+
(acc, [key, value]) => acc.replace(`$[[${key}]]`, value),
|
|
191
|
+
fullText,
|
|
192
|
+
);
|
|
193
|
+
|
|
181
194
|
// 이건 프로젝트에 .ts 소스 코드 파일을 생성하는 것이므로 src의 .ts 경로로 갑니다.
|
|
182
195
|
const destPath = path.join(Sonamu.appRootPath, target, "src/services/sonamu.shared.ts");
|
|
183
196
|
|
|
@@ -187,12 +200,11 @@ export class Syncer {
|
|
|
187
200
|
console.warn(`Created directory '${path.dirname(destPath)}' because it did not exist.`);
|
|
188
201
|
}
|
|
189
202
|
|
|
190
|
-
if (await areFilesSame(
|
|
203
|
+
if (await areFilesSame({ data: convertedText }, { path: destPath })) {
|
|
191
204
|
continue;
|
|
192
205
|
}
|
|
193
206
|
|
|
194
|
-
await writeFile(destPath,
|
|
195
|
-
|
|
207
|
+
await writeFile(destPath, convertedText);
|
|
196
208
|
!isTest() &&
|
|
197
209
|
console.log(
|
|
198
210
|
chalk.bold("Copied: ") + chalk.blue(path.relative(Sonamu.appRootPath, destPath)),
|
|
@@ -375,22 +387,7 @@ export class Syncer {
|
|
|
375
387
|
}
|
|
376
388
|
|
|
377
389
|
/**
|
|
378
|
-
*
|
|
379
|
-
* @returns 생성된 파일 경로 배열.
|
|
380
|
-
*/
|
|
381
|
-
async actionGenerateSchemas(): Promise<AbsolutePath[]> {
|
|
382
|
-
return (
|
|
383
|
-
await Promise.all([
|
|
384
|
-
generateTemplate("generated_sso", {}, { overwrite: true }),
|
|
385
|
-
generateTemplate("generated", {}, { overwrite: true }),
|
|
386
|
-
])
|
|
387
|
-
)
|
|
388
|
-
.flat()
|
|
389
|
-
.flat();
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
/**
|
|
393
|
-
* *.service.ts를 생성합니다.
|
|
390
|
+
* services.generated.ts를 생성합니다.
|
|
394
391
|
* @param paramsArray
|
|
395
392
|
* @returns 생성된 파일 경로 배열.
|
|
396
393
|
*/
|
|
@@ -400,14 +397,29 @@ export class Syncer {
|
|
|
400
397
|
}[],
|
|
401
398
|
): Promise<string[]> {
|
|
402
399
|
Naite.t("actionGenerateServices", paramsArray);
|
|
400
|
+
|
|
401
|
+
// services.generated.ts 통합 파일 생성
|
|
402
|
+
const servicesFile = await generateTemplate(
|
|
403
|
+
"services",
|
|
404
|
+
{},
|
|
405
|
+
{
|
|
406
|
+
overwrite: true,
|
|
407
|
+
},
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
return [...servicesFile];
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* sonamu.generated.ts와 sonamu.generated.sso.ts를 생성합니다.
|
|
415
|
+
* @returns 생성된 파일 경로 배열.
|
|
416
|
+
*/
|
|
417
|
+
async actionGenerateSchemas(): Promise<AbsolutePath[]> {
|
|
403
418
|
return (
|
|
404
|
-
await Promise.all(
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
}),
|
|
409
|
-
),
|
|
410
|
-
)
|
|
419
|
+
await Promise.all([
|
|
420
|
+
generateTemplate("generated_sso", {}, { overwrite: true }),
|
|
421
|
+
generateTemplate("generated", {}, { overwrite: true }),
|
|
422
|
+
])
|
|
411
423
|
)
|
|
412
424
|
.flat()
|
|
413
425
|
.flat();
|
|
@@ -77,10 +77,14 @@ export class WorkflowManager {
|
|
|
77
77
|
}
|
|
78
78
|
>;
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
// BackendPostgres에서 처리하는 것들이 있어서 Knex 커넥션이 아니라 설정값을 넣어줘야함.
|
|
81
|
+
constructor(dbConf: Knex.Config, runMigrations: boolean = true) {
|
|
82
|
+
const backend = new BackendPostgres(dbConf, { runMigrations });
|
|
83
|
+
|
|
81
84
|
this.#backend = backend;
|
|
82
85
|
this.#ow = new OpenWorkflow({ backend });
|
|
83
86
|
this.#worker = null;
|
|
87
|
+
|
|
84
88
|
this.#workflowsMap = new Map();
|
|
85
89
|
this.#scheduledTasks = new Map();
|
|
86
90
|
}
|
|
@@ -283,9 +287,18 @@ export class WorkflowManager {
|
|
|
283
287
|
}
|
|
284
288
|
|
|
285
289
|
// Worker를 설정 후 시작
|
|
286
|
-
|
|
290
|
+
setupWorker(options: WorkflowOptions) {
|
|
287
291
|
this.#worker = this.#ow.newWorker(options);
|
|
288
|
-
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Worker를 초기화
|
|
295
|
+
async startWorker() {
|
|
296
|
+
if (!this.#worker) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
await this.#backend.initialize();
|
|
301
|
+
await this.#worker?.start();
|
|
289
302
|
}
|
|
290
303
|
|
|
291
304
|
// Worker를 중지
|
|
@@ -318,13 +331,4 @@ export class WorkflowManager {
|
|
|
318
331
|
[Symbol.asyncDispose]() {
|
|
319
332
|
return this.destroy();
|
|
320
333
|
}
|
|
321
|
-
|
|
322
|
-
// BackendPostgres에서 처리하는 것들이 있어서 Knex 커넥션이 아니라 설정값을 넣어줘야함.
|
|
323
|
-
static async create(
|
|
324
|
-
dbConf: Knex.Config,
|
|
325
|
-
runMigrations: boolean = true,
|
|
326
|
-
): Promise<WorkflowManager> {
|
|
327
|
-
const backend = await BackendPostgres.connect(dbConf, { runMigrations });
|
|
328
|
-
return new WorkflowManager(backend);
|
|
329
|
-
}
|
|
330
334
|
}
|
|
@@ -4,7 +4,12 @@ import { Sonamu } from "../../api";
|
|
|
4
4
|
import type { Entity } from "../../entity/entity";
|
|
5
5
|
import { EntityManager } from "../../entity/entity-manager";
|
|
6
6
|
import { Naite } from "../../naite/naite";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
type EntityIndex,
|
|
9
|
+
type EntityPropNode,
|
|
10
|
+
isVirtualCodeProp,
|
|
11
|
+
isVirtualQueryProp,
|
|
12
|
+
} from "../../types/types";
|
|
8
13
|
import { nonNullable } from "../../utils/utils";
|
|
9
14
|
import { Template } from "../template";
|
|
10
15
|
import { propNodeToZodTypeDef, zodTypeToZodCode } from "../zod-converter";
|
|
@@ -171,9 +176,14 @@ export class Template__generated extends Template {
|
|
|
171
176
|
// TODO: GIN/GiST 인덱스 생성된 컬럼 추출
|
|
172
177
|
const fulltextColumns: EntityIndex["columns"][] = [];
|
|
173
178
|
|
|
174
|
-
// virtual props
|
|
179
|
+
// virtual props (virtualType: "code" 또는 undefined인 것만 포함)
|
|
175
180
|
const virtualProps = entity.props
|
|
176
|
-
.filter((prop) =>
|
|
181
|
+
.filter((prop) => isVirtualCodeProp(prop))
|
|
182
|
+
.map((prop) => prop.name);
|
|
183
|
+
|
|
184
|
+
// query virtual props (virtualType: "query"인 것만 포함)
|
|
185
|
+
const virtualQueryProps = entity.props
|
|
186
|
+
.filter((prop) => isVirtualQueryProp(prop))
|
|
177
187
|
.map((prop) => prop.name);
|
|
178
188
|
|
|
179
189
|
/**
|
|
@@ -209,6 +219,7 @@ export class Template__generated extends Template {
|
|
|
209
219
|
const hasMetadata =
|
|
210
220
|
fulltextColumns.length > 0 ||
|
|
211
221
|
virtualProps.length > 0 ||
|
|
222
|
+
virtualQueryProps.length > 0 ||
|
|
212
223
|
hasDefaultColumns.length > 0 ||
|
|
213
224
|
generatedColumns.length > 0 ||
|
|
214
225
|
hasVectorColumns.length > 0;
|
|
@@ -226,6 +237,9 @@ export class Template__generated extends Template {
|
|
|
226
237
|
(virtualProps.length > 0
|
|
227
238
|
? `readonly __virtual__: readonly [${virtualProps.map((prop) => `"${prop}"`).join(", ")}],`
|
|
228
239
|
: "") +
|
|
240
|
+
(virtualQueryProps.length > 0
|
|
241
|
+
? `readonly __virtual_query__: readonly [${virtualQueryProps.map((prop) => `"${prop}"`).join(", ")}],`
|
|
242
|
+
: "") +
|
|
229
243
|
(hasDefaultColumns.length > 0
|
|
230
244
|
? `readonly __hasDefault__: readonly [${hasDefaultColumns
|
|
231
245
|
.map((col) => `"${col}"`)
|
|
@@ -63,7 +63,7 @@ class ${entityId}ModelClass extends BaseModelClass<
|
|
|
63
63
|
> {
|
|
64
64
|
modelName = "${entityId}";
|
|
65
65
|
|
|
66
|
-
@api({ httpMethod: "GET", clients: ["axios", "
|
|
66
|
+
@api({ httpMethod: "GET", clients: ["axios", "tanstack-query"], resourceName: "${entityId}" })
|
|
67
67
|
async findById<T extends ${entityId}SubsetKey>(
|
|
68
68
|
subset: T,
|
|
69
69
|
id: number
|
|
@@ -93,7 +93,7 @@ class ${entityId}ModelClass extends BaseModelClass<
|
|
|
93
93
|
return rows[0] ?? null;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
@api({ httpMethod: "GET", clients: ["axios", "
|
|
96
|
+
@api({ httpMethod: "GET", clients: ["axios", "tanstack-query"], resourceName: "${names.capitalPlural}" })
|
|
97
97
|
async findMany<T extends ${entityId}SubsetKey, LP extends ${entityId}ListParams>(
|
|
98
98
|
subset: T,
|
|
99
99
|
rawParams?: LP,
|
|
@@ -156,7 +156,7 @@ class ${entityId}ModelClass extends BaseModelClass<
|
|
|
156
156
|
});
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
@api({ httpMethod: "POST" })
|
|
159
|
+
@api({ httpMethod: "POST", clients: ["axios", "tanstack-mutation"] })
|
|
160
160
|
async save(
|
|
161
161
|
spa: ${entityId}SaveParams[]
|
|
162
162
|
): Promise<number[]> {
|
|
@@ -175,7 +175,7 @@ class ${entityId}ModelClass extends BaseModelClass<
|
|
|
175
175
|
});
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
@api({ httpMethod: "POST", guards: [ "admin" ] })
|
|
178
|
+
@api({ httpMethod: "POST", clients: ["axios", "tanstack-mutation"], guards: [ "admin" ] })
|
|
179
179
|
async del(ids: number[]): Promise<number> {
|
|
180
180
|
const wdb = this.getPuri("w");
|
|
181
181
|
|