sonamu 0.9.2 → 0.9.4
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 +1 -2
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +1 -1
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +10 -1
- package/dist/auth/audit-log/builders.d.ts +216 -0
- package/dist/auth/audit-log/builders.d.ts.map +1 -0
- package/dist/auth/audit-log/builders.js +307 -0
- package/dist/auth/audit-log/events.d.ts +143 -0
- package/dist/auth/audit-log/events.d.ts.map +1 -0
- package/dist/auth/audit-log/events.js +74 -0
- package/dist/auth/audit-log/plugin.d.ts +11 -0
- package/dist/auth/audit-log/plugin.d.ts.map +1 -0
- package/dist/auth/audit-log/plugin.js +427 -0
- package/dist/auth/audit-log-ingestor.d.ts +9 -0
- package/dist/auth/audit-log-ingestor.d.ts.map +1 -0
- package/dist/auth/audit-log-ingestor.js +194 -0
- package/dist/auth/index.d.ts +3 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +4 -2
- package/dist/auth/plugins/entity-definitions/admin.d.ts +1 -1
- package/dist/auth/plugins/entity-definitions/admin.js +4 -4
- package/dist/auth/plugins/entity-definitions/audit-log.d.ts +12 -0
- package/dist/auth/plugins/entity-definitions/audit-log.d.ts.map +1 -0
- package/dist/auth/plugins/entity-definitions/audit-log.js +291 -0
- package/dist/auth/plugins/entity-definitions/index.d.ts +1 -0
- package/dist/auth/plugins/entity-definitions/index.d.ts.map +1 -1
- package/dist/auth/plugins/entity-definitions/index.js +5 -3
- 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/bin/fixture.d.ts.map +1 -1
- package/dist/bin/fixture.js +111 -1
- package/dist/database/_batch_update.d.ts +1 -1
- package/dist/database/_batch_update.js +2 -2
- package/dist/entity/entity-manager.d.ts.map +1 -1
- package/dist/entity/entity-manager.js +14 -4
- package/dist/index.js +4 -2
- package/dist/storage/buffered-file.d.ts +1 -1
- package/dist/storage/buffered-file.js +2 -2
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/syncer/syncer.js +2 -9
- package/dist/template/implementations/entry-server.template.js +3 -2
- package/dist/template/implementations/generated.template.d.ts.map +1 -1
- package/dist/template/implementations/generated.template.js +2 -1
- package/dist/template/implementations/generated_sso.template.d.ts.map +1 -1
- package/dist/template/implementations/generated_sso.template.js +2 -1
- package/dist/template/implementations/queries.template.d.ts.map +1 -1
- package/dist/template/implementations/queries.template.js +3 -1
- package/dist/template/implementations/sd.template.js +3 -2
- package/dist/template/implementations/services.template.d.ts.map +1 -1
- package/dist/template/implementations/services.template.js +44 -7
- package/dist/template/zod-converter.d.ts.map +1 -1
- package/dist/template/zod-converter.js +2 -2
- package/dist/ui-web/assets/{index-CfgbCoOJ.js → index-C5KUjXm0.js} +48 -45
- package/dist/ui-web/index.html +1 -1
- package/dist/utils/fs-utils.d.ts.map +1 -1
- package/dist/utils/fs-utils.js +4 -4
- package/package.json +3 -3
- package/src/api/config.ts +1 -2
- package/src/api/sonamu.ts +14 -0
- package/src/auth/audit-log/builders.ts +791 -0
- package/src/auth/audit-log/events.ts +149 -0
- package/src/auth/audit-log/plugin.ts +913 -0
- package/src/auth/audit-log-ingestor.ts +233 -0
- package/src/auth/index.ts +3 -0
- package/src/auth/plugins/entity-definitions/admin.ts +3 -3
- package/src/auth/plugins/entity-definitions/audit-log.ts +171 -0
- package/src/auth/plugins/entity-definitions/index.ts +3 -0
- package/src/auth/plugins/entity-definitions/types.ts +2 -1
- package/src/bin/fixture.ts +143 -0
- package/src/database/_batch_update.ts +1 -1
- package/src/entity/entity-manager.ts +10 -3
- package/src/shared/app.shared.ts.txt +2 -3
- package/src/shared/web.shared.ts.txt +2 -2
- package/src/storage/buffered-file.ts +1 -1
- package/src/syncer/syncer.ts +1 -11
- package/src/template/implementations/entry-server.template.ts +1 -1
- package/src/template/implementations/generated.template.ts +1 -0
- package/src/template/implementations/generated_sso.template.ts +1 -0
- package/src/template/implementations/queries.template.ts +10 -1
- package/src/template/implementations/sd.template.ts +1 -1
- package/src/template/implementations/services.template.ts +62 -6
- package/src/template/zod-converter.ts +2 -1
- package/src/utils/fs-utils.ts +6 -4
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* 최초 1회 생성되며, 이후에는 덮어쓰지 않습니다.
|
|
4
4
|
* 필요시 직접 수정할 수 있습니다.
|
|
5
5
|
*/
|
|
6
|
+
|
|
6
7
|
/* oxlint-disable react-hooks/exhaustive-deps */ // shared
|
|
7
|
-
/* oxlint-disable @typescript-eslint/no-explicit-any */ // shared
|
|
8
8
|
|
|
9
9
|
/*
|
|
10
10
|
fetch
|
|
@@ -15,7 +15,7 @@ import { EventSource } from "eventsource";
|
|
|
15
15
|
import qs from "qs";
|
|
16
16
|
import { useEffect, useRef, useState } from "react";
|
|
17
17
|
import { type core, z } from "zod";
|
|
18
|
-
import { getCurrentLocale } from "
|
|
18
|
+
import { getCurrentLocale } from "@/i18n/sd.generated";
|
|
19
19
|
|
|
20
20
|
// ISO 8601 및 타임존 포맷의 날짜 문자열을 Date 객체로 변환하는 reviver
|
|
21
21
|
export function dateReviver(_key: string, value: any): any {
|
|
@@ -35,8 +35,8 @@ export class BufferedFile extends BaseFile {
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* 파일을 디스크에 저장
|
|
38
|
+
* @param diskName 디스크 이름 ("fs" 또는 "s3")
|
|
38
39
|
* @param key 저장 경로 (예: 'uploads/avatar.png')
|
|
39
|
-
* @param diskName 디스크 이름 (기본: default disk)
|
|
40
40
|
* @returns 저장된 파일의 URL
|
|
41
41
|
*/
|
|
42
42
|
async saveToDisk(diskName: DriverKey, key: string): Promise<string> {
|
package/src/syncer/syncer.ts
CHANGED
|
@@ -5,7 +5,6 @@ import path from "path";
|
|
|
5
5
|
|
|
6
6
|
import { hot } from "@sonamu-kit/hmr-hook";
|
|
7
7
|
import chalk from "chalk";
|
|
8
|
-
import inflection from "inflection";
|
|
9
8
|
import { minimatch } from "minimatch";
|
|
10
9
|
import { group, unique } from "radashi";
|
|
11
10
|
import { type z } from "zod";
|
|
@@ -452,22 +451,13 @@ export class Syncer {
|
|
|
452
451
|
const params: {
|
|
453
452
|
namesRecord: EntityNamesRecord;
|
|
454
453
|
}[] = mergedGroup.map((modelPath) => {
|
|
455
|
-
if (modelPath.endsWith(".model.ts")) {
|
|
454
|
+
if (modelPath.endsWith(".model.ts") || modelPath.endsWith(".frame.ts")) {
|
|
456
455
|
const entityId = EntityManager.getEntityIdFromPath(modelPath);
|
|
457
456
|
assert(entityId);
|
|
458
457
|
return {
|
|
459
458
|
namesRecord: EntityManager.getNamesFromId(entityId),
|
|
460
459
|
};
|
|
461
460
|
}
|
|
462
|
-
if (modelPath.endsWith(".frame.ts")) {
|
|
463
|
-
const [, frameName] = modelPath.match(/.+\/(.+)\.frame\.ts$/) ?? [];
|
|
464
|
-
assert(frameName);
|
|
465
|
-
// frameName을 PascalCase로 변환 (dashboard -> Dashboard)
|
|
466
|
-
const frameId = inflection.camelize(frameName);
|
|
467
|
-
return {
|
|
468
|
-
namesRecord: EntityManager.getNamesFromId(frameId),
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
461
|
throw new Error("not reachable");
|
|
472
462
|
});
|
|
473
463
|
|
|
@@ -75,7 +75,7 @@ export async function render(url: string, preloadedData: PreloadedData[] = []) {
|
|
|
75
75
|
...this.getTargetAndPath(),
|
|
76
76
|
body,
|
|
77
77
|
importKeys: [],
|
|
78
|
-
customHeaders: ["/**", " * @generated", " * 직접 수정하지 마세요.", " */"],
|
|
78
|
+
customHeaders: ["/**", " * @generated", " * 직접 수정하지 마세요.", " */", ""],
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
81
|
}
|
|
@@ -106,6 +106,7 @@ ${functions.join("\n\n")}
|
|
|
106
106
|
" * @generated",
|
|
107
107
|
" * 직접 수정하지 마세요.",
|
|
108
108
|
" */",
|
|
109
|
+
"",
|
|
109
110
|
"/* oxlint-disable */",
|
|
110
111
|
"",
|
|
111
112
|
`import type { SSRQuery } from 'sonamu/ssr';`,
|
|
@@ -116,7 +117,15 @@ ${functions.join("\n\n")}
|
|
|
116
117
|
`}`,
|
|
117
118
|
"",
|
|
118
119
|
]
|
|
119
|
-
: [
|
|
120
|
+
: [
|
|
121
|
+
"/**",
|
|
122
|
+
" * @generated",
|
|
123
|
+
" * 직접 수정하지 마세요.",
|
|
124
|
+
" */",
|
|
125
|
+
"",
|
|
126
|
+
"/* oxlint-disable */",
|
|
127
|
+
"",
|
|
128
|
+
],
|
|
120
129
|
};
|
|
121
130
|
}
|
|
122
131
|
}
|
|
@@ -250,7 +250,7 @@ SD.enumLabels = (enumName: string): Record<string, LocalizedString> => {
|
|
|
250
250
|
...this.getTargetAndPath(undefined, target),
|
|
251
251
|
body,
|
|
252
252
|
importKeys: [],
|
|
253
|
-
customHeaders: ["/**", " * @generated", " * 직접 수정하지 마세요.", " */"],
|
|
253
|
+
customHeaders: ["/**", " * @generated", " * 직접 수정하지 마세요.", " */", ""],
|
|
254
254
|
};
|
|
255
255
|
}
|
|
256
256
|
|
|
@@ -209,18 +209,64 @@ export const ${methodName}QueryOptions = ${typeParamsDef}(${paramsDef}) => query
|
|
|
209
209
|
`.trim(),
|
|
210
210
|
);
|
|
211
211
|
|
|
212
|
-
// useQuery hook
|
|
212
|
+
// useQuery hook (useRefreshable로 래핑해 refresh/isRefreshing을 기본 제공)
|
|
213
213
|
functions.push(
|
|
214
214
|
`
|
|
215
215
|
export const use${inflection.camelize(hookName)} = ${typeParamsDef}(${paramsDef}${
|
|
216
216
|
paramsDef ? ", " : ""
|
|
217
217
|
}options?: { enabled?: boolean }) =>
|
|
218
|
-
useQuery({
|
|
218
|
+
useRefreshable(useQuery({
|
|
219
219
|
...${methodName}QueryOptions(${paramNames}),
|
|
220
220
|
...options
|
|
221
|
-
});
|
|
221
|
+
}));
|
|
222
222
|
`.trim(),
|
|
223
223
|
);
|
|
224
|
+
|
|
225
|
+
// infiniteQueryOptions + useInfiniteQuery (AsyncIdConfig.useListInfinite 대상 조건)
|
|
226
|
+
// 조건: resourceName이 복수형이고 methodName === "findMany"
|
|
227
|
+
const resourceName = api.options.resourceName;
|
|
228
|
+
const isInfiniteTarget =
|
|
229
|
+
!!resourceName &&
|
|
230
|
+
inflection.pluralize(resourceName) === resourceName &&
|
|
231
|
+
api.methodName === "findMany";
|
|
232
|
+
|
|
233
|
+
if (isInfiniteTarget) {
|
|
234
|
+
const infiniteMethodName = `${methodName}Infinite`;
|
|
235
|
+
const infiniteHookName = `use${inflection.camelize(hookName)}Infinite`;
|
|
236
|
+
|
|
237
|
+
functions.push(
|
|
238
|
+
`
|
|
239
|
+
export const ${infiniteMethodName}QueryOptions = ${typeParamsDef}(${paramsDef}) => infiniteQueryOptions({
|
|
240
|
+
queryKey: ['${modelName}', '${methodName}', 'infinite'${paramNames ? `, ${paramNames}` : ""}],
|
|
241
|
+
queryFn: ({ pageParam }) => ${methodName}(${
|
|
242
|
+
paramNames ? paramNames.replace(/\brawParams\b/, "{ ...rawParams, page: pageParam }") : ""
|
|
243
|
+
}),
|
|
244
|
+
initialPageParam: 1 as number,
|
|
245
|
+
getNextPageParam: (lastPage, allPages) => {
|
|
246
|
+
const total = (lastPage as { total?: number })?.total ?? 0;
|
|
247
|
+
const loaded = allPages.reduce(
|
|
248
|
+
(sum, p) => sum + ((p as { rows?: unknown[] })?.rows?.length ?? 0),
|
|
249
|
+
0,
|
|
250
|
+
);
|
|
251
|
+
return loaded < total ? allPages.length + 1 : undefined;
|
|
252
|
+
},
|
|
253
|
+
select: dedupeAndFlatten,
|
|
254
|
+
});
|
|
255
|
+
`.trim(),
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
functions.push(
|
|
259
|
+
`
|
|
260
|
+
export const ${infiniteHookName} = ${typeParamsDef}(${paramsDef}${
|
|
261
|
+
paramsDef ? ", " : ""
|
|
262
|
+
}options?: { enabled?: boolean }) =>
|
|
263
|
+
useRefreshable(useInfiniteQuery({
|
|
264
|
+
...${infiniteMethodName}QueryOptions(${paramNames}),
|
|
265
|
+
...options
|
|
266
|
+
}));
|
|
267
|
+
`.trim(),
|
|
268
|
+
);
|
|
269
|
+
}
|
|
224
270
|
}
|
|
225
271
|
|
|
226
272
|
// 3. useMutation (tanstack-mutation)
|
|
@@ -315,6 +361,7 @@ ${functions.join("\n\n")}
|
|
|
315
361
|
// resourceName에서 hook 이름 생성 (기존 로직과 동일)
|
|
316
362
|
const hookName = inflection.camelize(assertDefined(listApi.options.resourceName), true);
|
|
317
363
|
const useHookName = `use${inflection.camelize(hookName)}`;
|
|
364
|
+
const useHookInfiniteName = `${useHookName}Infinite`;
|
|
318
365
|
|
|
319
366
|
// ListParams 타입명 구성
|
|
320
367
|
const listParamsType = `${names.capital}ListParams`;
|
|
@@ -325,6 +372,7 @@ ${functions.join("\n\n")}
|
|
|
325
372
|
export const ${names.capital}AsyncIdConfig: AsyncIdConfig<${names.capital}SubsetKey, ${names.capital}SubsetMapping, ${listParamsType}> = {
|
|
326
373
|
placeholderKey: "entity.${names.capital}",
|
|
327
374
|
useList: ${names.capital}Service.${useHookName},
|
|
375
|
+
useListInfinite: ${names.capital}Service.${useHookInfiniteName},
|
|
328
376
|
};
|
|
329
377
|
`.trim(),
|
|
330
378
|
);
|
|
@@ -337,6 +385,11 @@ export const ${names.capital}AsyncIdConfig: AsyncIdConfig<${names.capital}Subset
|
|
|
337
385
|
.map((typeKey) => `type ${typeKey}`);
|
|
338
386
|
|
|
339
387
|
// sonamu.shared에서 import할 항목들을 동적으로 구성
|
|
388
|
+
// body 문자열을 기준으로 infinite 훅 생성 여부 판단 (findMany 복수형 분기에서만 참조됨)
|
|
389
|
+
const bodyForImportCheck = namespaces.join("\n\n");
|
|
390
|
+
const needsDedupeAndFlatten = bodyForImportCheck.includes("dedupeAndFlatten");
|
|
391
|
+
const needsUseRefreshable = bodyForImportCheck.includes("useRefreshable");
|
|
392
|
+
|
|
340
393
|
const sonamuSharedImports = [
|
|
341
394
|
"type ListResult",
|
|
342
395
|
"type FilterQuery",
|
|
@@ -346,6 +399,8 @@ export const ${names.capital}AsyncIdConfig: AsyncIdConfig<${names.capital}Subset
|
|
|
346
399
|
"type SSEStreamOptions",
|
|
347
400
|
"useSSEStream",
|
|
348
401
|
"toFormData",
|
|
402
|
+
...(needsDedupeAndFlatten ? ["dedupeAndFlatten"] : []),
|
|
403
|
+
...(needsUseRefreshable ? ["useRefreshable"] : []),
|
|
349
404
|
].join(", ");
|
|
350
405
|
|
|
351
406
|
// body 구성: namespaces + asyncIdConfigs
|
|
@@ -364,13 +419,14 @@ export const ${names.capital}AsyncIdConfig: AsyncIdConfig<${names.capital}Subset
|
|
|
364
419
|
" * @generated",
|
|
365
420
|
" * 직접 수정하지 마세요.",
|
|
366
421
|
" */",
|
|
422
|
+
"",
|
|
367
423
|
"/* oxlint-disable */",
|
|
368
424
|
"",
|
|
369
|
-
`import { queryOptions, useQuery, useMutation, type UseMutationOptions } from '@tanstack/react-query';`,
|
|
370
|
-
`import type
|
|
425
|
+
`import { queryOptions, useQuery, useInfiniteQuery, infiniteQueryOptions, useMutation, type UseMutationOptions } from '@tanstack/react-query';`,
|
|
426
|
+
`import { type AxiosProgressEvent } from 'axios';`,
|
|
371
427
|
`import qs from 'qs';`,
|
|
372
428
|
`import { ${sonamuSharedImports} } from './sonamu.shared';`,
|
|
373
|
-
`import type
|
|
429
|
+
`import { type AsyncIdConfig } from '@sonamu-kit/react-components/components';`,
|
|
374
430
|
],
|
|
375
431
|
};
|
|
376
432
|
}
|
|
@@ -442,13 +442,14 @@ export function zodTypeToTsTypeDef(zt: z.ZodType): string {
|
|
|
442
442
|
case "number":
|
|
443
443
|
case "boolean":
|
|
444
444
|
case "bigint":
|
|
445
|
-
case "date":
|
|
446
445
|
case "null":
|
|
447
446
|
case "undefined":
|
|
448
447
|
case "any":
|
|
449
448
|
case "unknown":
|
|
450
449
|
case "never":
|
|
451
450
|
return zt.def.type;
|
|
451
|
+
case "date":
|
|
452
|
+
return "Date";
|
|
452
453
|
case "nullable":
|
|
453
454
|
return `${zodTypeToTsTypeDef((zt as AnyZodNullable).def.innerType)} | null`;
|
|
454
455
|
case "default":
|
package/src/utils/fs-utils.ts
CHANGED
|
@@ -71,13 +71,15 @@ export async function copyFileWithReplaceCoreToShared(
|
|
|
71
71
|
})();
|
|
72
72
|
|
|
73
73
|
// syncHeader가 제공된 경우 @generated 블록을 교체하거나 최상단에 추가합니다.
|
|
74
|
+
// `*/` 직후에는 항상 빈 줄 한 개를 보장하여 oxfmt가 주석을 import의 leading comment로 오인해 정렬 시 끌려가는 문제를 방지합니다.
|
|
74
75
|
if (syncHeader) {
|
|
75
|
-
//
|
|
76
|
-
|
|
76
|
+
// 기존 @generated 블록 + 뒤이어 오는 빈 줄들(있으면 포함)을 한꺼번에 매칭하여 교체 시 빈 줄이 누적되지 않게 합니다.
|
|
77
|
+
// 한 줄짜리(/** @generated */)는 매칭하지 않으므로 수동으로 축약하지 마세요.
|
|
78
|
+
const generatedBlockRegex = /\/\*\*\r?\n \* @generated\r?\n[\s\S]*?\*\/\r?\n(\r?\n)*/;
|
|
77
79
|
if (generatedBlockRegex.test(newFileContent)) {
|
|
78
|
-
newFileContent = newFileContent.replace(generatedBlockRegex, `${syncHeader}\n`);
|
|
80
|
+
newFileContent = newFileContent.replace(generatedBlockRegex, `${syncHeader}\n\n`);
|
|
79
81
|
} else {
|
|
80
|
-
newFileContent = `${syncHeader}\n${newFileContent}`;
|
|
82
|
+
newFileContent = `${syncHeader}\n\n${newFileContent}`;
|
|
81
83
|
}
|
|
82
84
|
}
|
|
83
85
|
|