sonamu 0.3.1 → 0.4.1

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 (83) hide show
  1. package/.pnp.cjs +11 -0
  2. package/dist/base-model-BzMJ2E_I.d.mts +43 -0
  3. package/dist/base-model-CWRKUX49.d.ts +43 -0
  4. package/dist/bin/cli.js +118 -89
  5. package/dist/bin/cli.js.map +1 -1
  6. package/dist/bin/cli.mjs +74 -45
  7. package/dist/bin/cli.mjs.map +1 -1
  8. package/dist/chunk-FLPD24HS.mjs +231 -0
  9. package/dist/chunk-FLPD24HS.mjs.map +1 -0
  10. package/dist/chunk-I2MMJRJN.mjs +1550 -0
  11. package/dist/chunk-I2MMJRJN.mjs.map +1 -0
  12. package/dist/{chunk-MPXE4IHO.mjs → chunk-PP2PSSAG.mjs} +5284 -5617
  13. package/dist/chunk-PP2PSSAG.mjs.map +1 -0
  14. package/dist/chunk-QK5XXJUX.mjs +280 -0
  15. package/dist/chunk-QK5XXJUX.mjs.map +1 -0
  16. package/dist/chunk-U636LQJJ.js +231 -0
  17. package/dist/chunk-U636LQJJ.js.map +1 -0
  18. package/dist/chunk-W7KDVJLQ.js +280 -0
  19. package/dist/chunk-W7KDVJLQ.js.map +1 -0
  20. package/dist/{chunk-YXILRRDT.js → chunk-XT6LHCX5.js} +5252 -5585
  21. package/dist/chunk-XT6LHCX5.js.map +1 -0
  22. package/dist/chunk-Z2P7XTXE.js +1550 -0
  23. package/dist/chunk-Z2P7XTXE.js.map +1 -0
  24. package/dist/database/drivers/knex/base-model.d.mts +16 -0
  25. package/dist/database/drivers/knex/base-model.d.ts +16 -0
  26. package/dist/database/drivers/knex/base-model.js +55 -0
  27. package/dist/database/drivers/knex/base-model.js.map +1 -0
  28. package/dist/database/drivers/knex/base-model.mjs +56 -0
  29. package/dist/database/drivers/knex/base-model.mjs.map +1 -0
  30. package/dist/database/drivers/kysely/base-model.d.mts +22 -0
  31. package/dist/database/drivers/kysely/base-model.d.ts +22 -0
  32. package/dist/database/drivers/kysely/base-model.js +64 -0
  33. package/dist/database/drivers/kysely/base-model.js.map +1 -0
  34. package/dist/database/drivers/kysely/base-model.mjs +65 -0
  35. package/dist/database/drivers/kysely/base-model.mjs.map +1 -0
  36. package/dist/index.d.mts +220 -926
  37. package/dist/index.d.ts +220 -926
  38. package/dist/index.js +13 -26
  39. package/dist/index.js.map +1 -1
  40. package/dist/index.mjs +18 -31
  41. package/dist/index.mjs.map +1 -1
  42. package/dist/model-CAH_4oQh.d.mts +1042 -0
  43. package/dist/model-CAH_4oQh.d.ts +1042 -0
  44. package/import-to-require.js +27 -0
  45. package/package.json +23 -2
  46. package/src/api/caster.ts +6 -0
  47. package/src/api/code-converters.ts +3 -1
  48. package/src/api/sonamu.ts +41 -22
  49. package/src/bin/cli.ts +78 -46
  50. package/src/database/_batch_update.ts +16 -11
  51. package/src/database/base-model.abstract.ts +97 -0
  52. package/src/database/base-model.ts +214 -280
  53. package/src/database/code-generator.ts +72 -0
  54. package/src/database/db.abstract.ts +75 -0
  55. package/src/database/db.ts +21 -82
  56. package/src/database/drivers/knex/base-model.ts +55 -0
  57. package/src/database/drivers/knex/client.ts +209 -0
  58. package/src/database/drivers/knex/db.ts +227 -0
  59. package/src/database/drivers/knex/generator.ts +659 -0
  60. package/src/database/drivers/kysely/base-model.ts +89 -0
  61. package/src/database/drivers/kysely/client.ts +309 -0
  62. package/src/database/drivers/kysely/db.ts +238 -0
  63. package/src/database/drivers/kysely/generator.ts +714 -0
  64. package/src/database/types.ts +117 -0
  65. package/src/database/upsert-builder.ts +31 -18
  66. package/src/entity/entity-utils.ts +1 -1
  67. package/src/entity/migrator.ts +98 -693
  68. package/src/index.ts +1 -1
  69. package/src/syncer/syncer.ts +69 -27
  70. package/src/templates/generated_http.template.ts +14 -0
  71. package/src/templates/kysely_types.template.ts +205 -0
  72. package/src/templates/model.template.ts +2 -139
  73. package/src/templates/service.template.ts +3 -1
  74. package/src/testing/_relation-graph.ts +111 -0
  75. package/src/testing/fixture-manager.ts +216 -332
  76. package/src/types/types.ts +56 -6
  77. package/src/utils/utils.ts +56 -4
  78. package/src/utils/zod-error.ts +189 -0
  79. package/tsconfig.json +2 -2
  80. package/tsup.config.js +11 -10
  81. package/dist/chunk-MPXE4IHO.mjs.map +0 -1
  82. package/dist/chunk-YXILRRDT.js.map +0 -1
  83. /package/src/database/{knex-plugins → drivers/knex/plugins}/knex-on-duplicate-update.ts +0 -0
@@ -1,4 +1,3 @@
1
- import { Knex } from "knex";
2
1
  import { z } from "zod";
3
2
 
4
3
  /*
@@ -90,7 +89,7 @@ export type DateProp = CommonProp & {
90
89
  type: "date";
91
90
  };
92
91
  export type DateTimeProp = CommonProp & {
93
- type: "dateTime";
92
+ type: "datetime";
94
93
  };
95
94
  export type TimeProp = CommonProp & {
96
95
  type: "time";
@@ -279,7 +278,7 @@ export function isDateProp(p: any): p is DateProp {
279
278
  return p?.type === "date";
280
279
  }
281
280
  export function isDateTimeProp(p: any): p is DateTimeProp {
282
- return p?.type === "dateTime";
281
+ return p?.type === "datetime";
283
282
  }
284
283
  export function isTimeProp(p: any): p is TimeProp {
285
284
  return p?.type === "time";
@@ -327,6 +326,40 @@ export function isCustomJoinClause(p: any): p is { custom: string } {
327
326
  }
328
327
 
329
328
  /* 서브셋 */
329
+ // type SubsetLoader = {
330
+ // as: string;
331
+ // table: string;
332
+ // manyJoin: {
333
+ // fromTable: string;
334
+ // fromCol: string;
335
+ // idField: string;
336
+ // toTable: string;
337
+ // toCol: string;
338
+ // through?: {
339
+ // table: string;
340
+ // fromCol: string;
341
+ // toCol: string;
342
+ // };
343
+ // };
344
+ // oneJoins: ({
345
+ // as: string;
346
+ // join: "inner" | "outer";
347
+ // table: string;
348
+ // } & JoinClause)[];
349
+ // select: (string | Knex.Raw)[];
350
+ // loaders?: SubsetLoader[];
351
+ // };
352
+ // export type SubsetQuery = {
353
+ // select: (string | Knex.Raw)[];
354
+ // virtual: string[];
355
+ // joins: ({
356
+ // as: string;
357
+ // join: "inner" | "outer";
358
+ // table: string;
359
+ // } & JoinClause)[];
360
+ // loaders: SubsetLoader[];
361
+ // };
362
+
330
363
  type SubsetLoader = {
331
364
  as: string;
332
365
  table: string;
@@ -347,11 +380,12 @@ type SubsetLoader = {
347
380
  join: "inner" | "outer";
348
381
  table: string;
349
382
  } & JoinClause)[];
350
- select: (string | Knex.Raw)[];
383
+ select: string[];
351
384
  loaders?: SubsetLoader[];
352
385
  };
386
+
353
387
  export type SubsetQuery = {
354
- select: (string | Knex.Raw)[];
388
+ select: string[];
355
389
  virtual: string[];
356
390
  joins: ({
357
391
  as: string;
@@ -376,6 +410,9 @@ export type KnexError = {
376
410
  export function isKnexError(e: any): e is KnexError {
377
411
  return e.code && e.sqlMessage && e.sqlState;
378
412
  }
413
+ export function isKyselyError(e: any): e is KnexError {
414
+ return e.code && e.sqlMessage && e.sqlState;
415
+ }
379
416
 
380
417
  export type KnexColumnType =
381
418
  | "string"
@@ -394,7 +431,7 @@ export type KnexColumnType =
394
431
  | "float"
395
432
  | "date"
396
433
  | "time"
397
- | "dateTime";
434
+ | "datetime";
398
435
  export type MigrationColumn = {
399
436
  name: string;
400
437
  type: KnexColumnType;
@@ -554,6 +591,11 @@ export namespace ApiParamType {
554
591
  export function isRefKnex(v: any): v is ApiParamType.Ref {
555
592
  return v?.t === "ref" && v.id === "Knex";
556
593
  }
594
+ export function isRefKysely(v: any): v is ApiParamType.Ref {
595
+ return (
596
+ v?.t === "ref" && (v.id === "Kysely" || v.id.startsWith("Transaction"))
597
+ );
598
+ }
557
599
  export function isTypeParam(v: any): v is ApiParamType.TypeParam {
558
600
  return v?.t === "type-param";
559
601
  }
@@ -684,6 +726,7 @@ export const TemplateOptions = z.object({
684
726
  entityId: z.string(),
685
727
  enumId: z.string(),
686
728
  }),
729
+ kysely_interface: z.object({}),
687
730
  });
688
731
  export type TemplateOptions = z.infer<typeof TemplateOptions>;
689
732
 
@@ -706,6 +749,7 @@ export const TemplateKey = z.enum([
706
749
  "view_enums_select",
707
750
  "view_enums_dropdown",
708
751
  "view_enums_buttonset",
752
+ "kysely_interface",
709
753
  ]);
710
754
  export type TemplateKey = z.infer<typeof TemplateKey>;
711
755
 
@@ -750,3 +794,9 @@ export type FixtureImportResult = {
750
794
  [key: string]: any;
751
795
  };
752
796
  };
797
+
798
+ export type RelationNode = {
799
+ fixtureId: string;
800
+ entityId: string;
801
+ related: Set<string>;
802
+ };
@@ -1,6 +1,7 @@
1
1
  import path from "path";
2
2
  import glob from "glob";
3
3
  import fs from "fs-extra";
4
+ import _ from "lodash";
4
5
 
5
6
  export function globAsync(pathPattern: string): Promise<string[]> {
6
7
  return new Promise((resolve, reject) => {
@@ -20,10 +21,16 @@ export async function importMultiple(
20
21
  const results: { filePath: string; imported: any }[] = [];
21
22
 
22
23
  for (const filePath of filePaths) {
23
- const importPath = "./" + path.relative(__dirname, filePath);
24
+ let importPath = "./" + path.relative(__dirname, filePath);
25
+
24
26
  if (doRefresh) {
25
- delete require.cache[require.resolve(importPath)];
27
+ if (require.resolve) {
28
+ delete require.cache[require.resolve(importPath)];
29
+ } else {
30
+ importPath += `?t=${Date.now()}`;
31
+ }
26
32
  }
33
+
27
34
  const imported = await import(importPath);
28
35
  results.push({
29
36
  filePath,
@@ -34,11 +41,11 @@ export async function importMultiple(
34
41
  return results;
35
42
  }
36
43
  export async function findAppRootPath() {
37
- const apiRootPath = await findApiRootPath();
44
+ const apiRootPath = findApiRootPath();
38
45
  return apiRootPath.split(path.sep).slice(0, -1).join(path.sep);
39
46
  }
40
47
 
41
- export async function findApiRootPath() {
48
+ export function findApiRootPath() {
42
49
  const basePath = require.main?.path ?? __dirname;
43
50
  let dir = path.dirname(basePath);
44
51
  if (dir.includes("/.yarn/")) {
@@ -56,3 +63,48 @@ export async function findApiRootPath() {
56
63
  export function nonNullable<T>(value: T): value is NonNullable<T> {
57
64
  return value !== null && value !== undefined;
58
65
  }
66
+
67
+ export function hydrate<T>(rows: T[]): T[] {
68
+ return rows.map((row: any) => {
69
+ // nullable relation인 경우 관련된 필드가 전부 null로 생성되는 것 방지하는 코드
70
+ const nestedKeys = Object.keys(row).filter((key) => key.includes("__"));
71
+ const groups = _.groupBy(nestedKeys, (key) => key.split("__")[0]);
72
+ const nullKeys = Object.keys(groups).filter(
73
+ (key) =>
74
+ groups[key].length > 1 &&
75
+ groups[key].every((field) => row[field] === null)
76
+ );
77
+
78
+ const hydrated = Object.keys(row).reduce((r, field) => {
79
+ if (!field.includes("__")) {
80
+ if (Array.isArray(row[field]) && _.isObject(row[field][0])) {
81
+ r[field] = hydrate(row[field]);
82
+ return r;
83
+ } else {
84
+ r[field] = row[field];
85
+ return r;
86
+ }
87
+ }
88
+
89
+ const parts = field.split("__");
90
+ const objPath =
91
+ parts[0] +
92
+ parts
93
+ .slice(1)
94
+ .map((part) => `[${part}]`)
95
+ .join("");
96
+ _.set(
97
+ r,
98
+ objPath,
99
+ row[field] && Array.isArray(row[field]) && _.isObject(row[field][0])
100
+ ? hydrate(row[field])
101
+ : row[field]
102
+ );
103
+
104
+ return r;
105
+ }, {} as any);
106
+ nullKeys.map((nullKey) => (hydrated[nullKey] = null));
107
+
108
+ return hydrated;
109
+ });
110
+ }
@@ -0,0 +1,189 @@
1
+ import { z } from "zod";
2
+
3
+ type ValidationError = {
4
+ path: string[];
5
+ message: string;
6
+ };
7
+
8
+ export function humanizeZodError(error: z.ZodError): ValidationError[] {
9
+ return error.issues.map((issue) => {
10
+ const pathAsStrings = issue.path.map(String);
11
+ const path = issue.path.reduce((acc, cur, i) => {
12
+ if (typeof cur === "number") {
13
+ return `${acc}[${cur}]`;
14
+ }
15
+ return i === 0 ? cur : `${acc}.${cur}`;
16
+ }, "");
17
+
18
+ switch (issue.code) {
19
+ case z.ZodIssueCode.invalid_type:
20
+ return {
21
+ path: pathAsStrings,
22
+ message: `${path} should be a ${issue.expected}, received ${issue.received}.`,
23
+ };
24
+
25
+ case z.ZodIssueCode.unrecognized_keys:
26
+ return {
27
+ path: pathAsStrings,
28
+ message: `Unrecognized keys in ${path}: ${issue.keys.join(", ")}.`,
29
+ };
30
+
31
+ case z.ZodIssueCode.invalid_union:
32
+ return {
33
+ path: pathAsStrings,
34
+ message: `${path} failed union validation. Inner errors: ${issue.unionErrors
35
+ .map((e) => e.issues.map((i) => i.message).join("; "))
36
+ .join(" OR ")}.`,
37
+ };
38
+
39
+ case z.ZodIssueCode.invalid_enum_value:
40
+ return {
41
+ path: pathAsStrings,
42
+ message: `${path} must be one of: ${issue.options.join(", ")}.`,
43
+ };
44
+
45
+ case z.ZodIssueCode.invalid_arguments:
46
+ return {
47
+ path: pathAsStrings,
48
+ message: `Invalid function arguments: ${issue.argumentsError.issues
49
+ .map((i) => i.message)
50
+ .join("; ")}.`,
51
+ };
52
+
53
+ case z.ZodIssueCode.invalid_return_type:
54
+ return {
55
+ path: pathAsStrings,
56
+ message: `Invalid function return type: ${issue.returnTypeError.issues
57
+ .map((i) => i.message)
58
+ .join("; ")}.`,
59
+ };
60
+
61
+ case z.ZodIssueCode.invalid_date:
62
+ return {
63
+ path: pathAsStrings,
64
+ message: `${path} must be a valid date.`,
65
+ };
66
+
67
+ case z.ZodIssueCode.invalid_string:
68
+ const validationType = issue.validation;
69
+ return {
70
+ path: pathAsStrings,
71
+ message: `${path} must be a valid ${validationType}.`,
72
+ };
73
+
74
+ case z.ZodIssueCode.too_small:
75
+ return {
76
+ path: pathAsStrings,
77
+ message: `${path} ${getMinimumMessage(issue)}.`,
78
+ };
79
+
80
+ case z.ZodIssueCode.too_big:
81
+ return {
82
+ path: pathAsStrings,
83
+ message: `${path} ${getMaximumMessage(issue)}`,
84
+ };
85
+
86
+ case z.ZodIssueCode.not_multiple_of:
87
+ return {
88
+ path: pathAsStrings,
89
+ message: `${path} must be a multiple of ${issue.multipleOf.toString()}.`,
90
+ };
91
+
92
+ case z.ZodIssueCode.custom:
93
+ return {
94
+ path: pathAsStrings,
95
+ message: issue.message || `${path} failed custom validation.`,
96
+ };
97
+
98
+ default:
99
+ return {
100
+ path: pathAsStrings,
101
+ message: issue.message,
102
+ };
103
+ }
104
+ });
105
+ }
106
+
107
+ function getMinimumMessage(
108
+ issue: z.ZodIssue & { code: typeof z.ZodIssueCode.too_small }
109
+ ) {
110
+ switch (issue.type) {
111
+ case "string":
112
+ return `must be ${
113
+ issue.exact ? "exactly" : issue.inclusive ? "at least" : "more than"
114
+ } ${issue.minimum} character${issue.minimum === 1 ? "" : "s"}`;
115
+
116
+ case "number":
117
+ return `must be ${
118
+ issue.exact
119
+ ? "exactly"
120
+ : issue.inclusive
121
+ ? "greater than or equal to"
122
+ : "greater than"
123
+ } ${issue.minimum.toString()}`;
124
+
125
+ case "array":
126
+ case "set":
127
+ return `must contain ${
128
+ issue.exact ? "exactly" : issue.inclusive ? "at least" : "more than"
129
+ } ${issue.minimum} item${issue.minimum === 1 ? "" : "s"}`;
130
+
131
+ case "date":
132
+ return `must be ${
133
+ issue.exact ? "exactly" : issue.inclusive ? "at or after" : "after"
134
+ } ${formatDateConstraint(issue.minimum)}`;
135
+
136
+ default:
137
+ return "is too small";
138
+ }
139
+ }
140
+
141
+ function getMaximumMessage(
142
+ issue: z.ZodIssue & { code: typeof z.ZodIssueCode.too_big }
143
+ ) {
144
+ switch (issue.type) {
145
+ case "string":
146
+ return `must be ${
147
+ issue.exact ? "exactly" : issue.inclusive ? "at most" : "less than"
148
+ } ${issue.maximum} character${issue.maximum === 1 ? "" : "s"}`;
149
+
150
+ case "number":
151
+ return `must be ${
152
+ issue.exact
153
+ ? "exactly"
154
+ : issue.inclusive
155
+ ? "less than or equal to"
156
+ : "less than"
157
+ } ${issue.maximum.toString()}`;
158
+
159
+ case "array":
160
+ case "set":
161
+ return `must contain ${
162
+ issue.exact ? "exactly" : issue.inclusive ? "at most" : "fewer than"
163
+ } ${issue.maximum} item${issue.maximum === 1 ? "" : "s"}`;
164
+
165
+ case "date":
166
+ return `must be ${
167
+ issue.exact ? "exactly" : issue.inclusive ? "at or before" : "before"
168
+ } ${formatDateConstraint(issue.maximum)}`;
169
+
170
+ default:
171
+ return "is too big";
172
+ }
173
+ }
174
+
175
+ function formatDateConstraint(value: number | bigint): string {
176
+ try {
177
+ if (typeof value === "bigint") {
178
+ if (
179
+ value > BigInt(Number.MAX_SAFE_INTEGER) ||
180
+ value < BigInt(Number.MIN_SAFE_INTEGER)
181
+ ) {
182
+ return value.toString();
183
+ }
184
+ }
185
+ return new Date(Number(value)).toISOString();
186
+ } catch {
187
+ return value.toString();
188
+ }
189
+ }
package/tsconfig.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  /* Basic Options */
4
- "target": "es6",
5
- "module": "commonjs",
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
6
  "outDir": "dist",
7
7
  "sourceMap": true,
8
8
  "lib": ["esnext", "dom"],
package/tsup.config.js CHANGED
@@ -1,23 +1,24 @@
1
1
  // tsup.config.js
2
2
  import { defineConfig } from "tsup";
3
+ import { ImportToRequirePlugin } from "./import-to-require";
3
4
 
4
5
  export default defineConfig({
5
- entry: ["src/index.ts", "src/bin/cli.ts", "src/bin/cli-wrapper.ts"],
6
+ entry: [
7
+ "src/index.ts",
8
+ "src/bin/cli.ts",
9
+ "src/bin/cli-wrapper.ts",
10
+ "src/database/drivers/knex/base-model.ts",
11
+ "src/database/drivers/kysely/base-model.ts",
12
+ ],
6
13
  dts: true,
7
- format: ["esm", "cjs"],
8
- target: "es2020",
14
+ format: ["cjs", "esm"],
15
+ target: "esnext",
9
16
  clean: true,
10
17
  sourcemap: true,
11
18
  shims: true,
12
19
  platform: "node",
13
20
  splitting: true,
14
- // banner(ctx) {
15
- // if (ctx.format === "esm") {
16
- // return {
17
- // js: `const require = (await import('module')).createRequire(import.meta.url);`,
18
- // };
19
- // }
20
- // },
21
+ esbuildPlugins: [ImportToRequirePlugin],
21
22
  external: [
22
23
  "chalk",
23
24
  "dotenv",