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.
Files changed (84) hide show
  1. package/dist/api/config.d.ts +1 -2
  2. package/dist/api/config.d.ts.map +1 -1
  3. package/dist/api/config.js +1 -1
  4. package/dist/api/sonamu.d.ts.map +1 -1
  5. package/dist/api/sonamu.js +10 -1
  6. package/dist/auth/audit-log/builders.d.ts +216 -0
  7. package/dist/auth/audit-log/builders.d.ts.map +1 -0
  8. package/dist/auth/audit-log/builders.js +307 -0
  9. package/dist/auth/audit-log/events.d.ts +143 -0
  10. package/dist/auth/audit-log/events.d.ts.map +1 -0
  11. package/dist/auth/audit-log/events.js +74 -0
  12. package/dist/auth/audit-log/plugin.d.ts +11 -0
  13. package/dist/auth/audit-log/plugin.d.ts.map +1 -0
  14. package/dist/auth/audit-log/plugin.js +427 -0
  15. package/dist/auth/audit-log-ingestor.d.ts +9 -0
  16. package/dist/auth/audit-log-ingestor.d.ts.map +1 -0
  17. package/dist/auth/audit-log-ingestor.js +194 -0
  18. package/dist/auth/index.d.ts +3 -0
  19. package/dist/auth/index.d.ts.map +1 -1
  20. package/dist/auth/index.js +4 -2
  21. package/dist/auth/plugins/entity-definitions/admin.d.ts +1 -1
  22. package/dist/auth/plugins/entity-definitions/admin.js +4 -4
  23. package/dist/auth/plugins/entity-definitions/audit-log.d.ts +12 -0
  24. package/dist/auth/plugins/entity-definitions/audit-log.d.ts.map +1 -0
  25. package/dist/auth/plugins/entity-definitions/audit-log.js +291 -0
  26. package/dist/auth/plugins/entity-definitions/index.d.ts +1 -0
  27. package/dist/auth/plugins/entity-definitions/index.d.ts.map +1 -1
  28. package/dist/auth/plugins/entity-definitions/index.js +5 -3
  29. package/dist/auth/plugins/entity-definitions/types.d.ts +1 -1
  30. package/dist/auth/plugins/entity-definitions/types.d.ts.map +1 -1
  31. package/dist/bin/fixture.d.ts.map +1 -1
  32. package/dist/bin/fixture.js +111 -1
  33. package/dist/database/_batch_update.d.ts +1 -1
  34. package/dist/database/_batch_update.js +2 -2
  35. package/dist/entity/entity-manager.d.ts.map +1 -1
  36. package/dist/entity/entity-manager.js +14 -4
  37. package/dist/index.js +4 -2
  38. package/dist/storage/buffered-file.d.ts +1 -1
  39. package/dist/storage/buffered-file.js +2 -2
  40. package/dist/syncer/syncer.d.ts.map +1 -1
  41. package/dist/syncer/syncer.js +2 -9
  42. package/dist/template/implementations/entry-server.template.js +3 -2
  43. package/dist/template/implementations/generated.template.d.ts.map +1 -1
  44. package/dist/template/implementations/generated.template.js +2 -1
  45. package/dist/template/implementations/generated_sso.template.d.ts.map +1 -1
  46. package/dist/template/implementations/generated_sso.template.js +2 -1
  47. package/dist/template/implementations/queries.template.d.ts.map +1 -1
  48. package/dist/template/implementations/queries.template.js +3 -1
  49. package/dist/template/implementations/sd.template.js +3 -2
  50. package/dist/template/implementations/services.template.d.ts.map +1 -1
  51. package/dist/template/implementations/services.template.js +44 -7
  52. package/dist/template/zod-converter.d.ts.map +1 -1
  53. package/dist/template/zod-converter.js +2 -2
  54. package/dist/ui-web/assets/{index-CfgbCoOJ.js → index-C5KUjXm0.js} +48 -45
  55. package/dist/ui-web/index.html +1 -1
  56. package/dist/utils/fs-utils.d.ts.map +1 -1
  57. package/dist/utils/fs-utils.js +4 -4
  58. package/package.json +3 -3
  59. package/src/api/config.ts +1 -2
  60. package/src/api/sonamu.ts +14 -0
  61. package/src/auth/audit-log/builders.ts +791 -0
  62. package/src/auth/audit-log/events.ts +149 -0
  63. package/src/auth/audit-log/plugin.ts +913 -0
  64. package/src/auth/audit-log-ingestor.ts +233 -0
  65. package/src/auth/index.ts +3 -0
  66. package/src/auth/plugins/entity-definitions/admin.ts +3 -3
  67. package/src/auth/plugins/entity-definitions/audit-log.ts +171 -0
  68. package/src/auth/plugins/entity-definitions/index.ts +3 -0
  69. package/src/auth/plugins/entity-definitions/types.ts +2 -1
  70. package/src/bin/fixture.ts +143 -0
  71. package/src/database/_batch_update.ts +1 -1
  72. package/src/entity/entity-manager.ts +10 -3
  73. package/src/shared/app.shared.ts.txt +2 -3
  74. package/src/shared/web.shared.ts.txt +2 -2
  75. package/src/storage/buffered-file.ts +1 -1
  76. package/src/syncer/syncer.ts +1 -11
  77. package/src/template/implementations/entry-server.template.ts +1 -1
  78. package/src/template/implementations/generated.template.ts +1 -0
  79. package/src/template/implementations/generated_sso.template.ts +1 -0
  80. package/src/template/implementations/queries.template.ts +10 -1
  81. package/src/template/implementations/sd.template.ts +1 -1
  82. package/src/template/implementations/services.template.ts +62 -6
  83. package/src/template/zod-converter.ts +2 -1
  84. 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 "../i18n/sd.generated";
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> {
@@ -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
  }
@@ -130,6 +130,7 @@ export class Template__generated extends Template {
130
130
  " * @generated",
131
131
  " * 직접 수정하지 마세요.",
132
132
  " */",
133
+ "",
133
134
  "/* oxlint-disable */",
134
135
  "",
135
136
  `import { z } from 'zod';`,
@@ -152,6 +152,7 @@ export class Template__generated_sso extends Template {
152
152
  " * @generated",
153
153
  " * 직접 수정하지 마세요.",
154
154
  " */",
155
+ "",
155
156
  `import { ${sonamuImports} } from "sonamu";`,
156
157
  ];
157
158
  if (this.hasAuthConfig()) {
@@ -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
- : ["/**", " * @generated", " * 직접 수정하지 마세요.", " */", "/* oxlint-disable */", ""],
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 { AxiosProgressEvent } from 'axios';`,
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 { AsyncIdConfig } from '@sonamu-kit/react-components/components';`,
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":
@@ -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
- // 여러 형식만 매칭합니다. 줄짜리(/** @generated */) 매칭하지 않으므로 수동으로 축약하지 마세요.
76
- const generatedBlockRegex = /\/\*\*\r?\n \* @generated\r?\n[\s\S]*?\*\/\r?\n/;
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