@casekit/orm2-config 0.0.0-20250331202540 → 1.0.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/package.json +19 -19
- package/{build/index.d.ts → src/index.ts} +7 -1
- package/{build/normalize/defaultZodSchema.test.js → src/normalize/defaultZodSchema.test.ts} +13 -5
- package/{build/normalize/defaultZodSchema.js → src/normalize/defaultZodSchema.ts} +22 -30
- package/{build/normalize/getColumns.test.js → src/normalize/getColumns.test.ts} +17 -4
- package/{build/normalize/getColumns.js → src/normalize/getColumns.ts} +6 -1
- package/{build/normalize/normalizeConfig.test.js → src/normalize/normalizeConfig.test.ts} +70 -24
- package/{build/normalize/normalizeConfig.js → src/normalize/normalizeConfig.ts} +7 -2
- package/{build/normalize/normalizeField.test.js → src/normalize/normalizeField.test.ts} +12 -7
- package/src/normalize/normalizeField.ts +16 -0
- package/{build/normalize/normalizeForeignKeys.test.js → src/normalize/normalizeForeignKeys.test.ts} +26 -7
- package/src/normalize/normalizeForeignKeys.ts +89 -0
- package/{build/normalize/normalizeModel.test.js → src/normalize/normalizeModel.test.ts} +42 -18
- package/{build/normalize/normalizeModel.js → src/normalize/normalizeModel.ts} +8 -1
- package/{build/normalize/normalizePrimaryKey.test.js → src/normalize/normalizePrimaryKey.test.ts} +31 -7
- package/src/normalize/normalizePrimaryKey.ts +30 -0
- package/{build/normalize/normalizeRelations.test.js → src/normalize/normalizeRelations.test.ts} +38 -9
- package/{build/normalize/normalizeRelations.js → src/normalize/normalizeRelations.ts} +34 -12
- package/{build/normalize/normalizeUniqueConstraints.test.js → src/normalize/normalizeUniqueConstraints.test.ts} +46 -15
- package/src/normalize/normalizeUniqueConstraints.ts +58 -0
- package/src/normalize/populateField.test.ts +253 -0
- package/{build/normalize/populateField.js → src/normalize/populateField.ts} +9 -1
- package/{build/normalize/populateModels.test.js → src/normalize/populateModels.test.ts} +35 -14
- package/{build/normalize/populateModels.js → src/normalize/populateModels.ts} +11 -2
- package/{build/types/NormalizedConfig.d.ts → src/types/NormalizedConfig.ts} +8 -1
- package/{build/types/NormalizedFieldDefinition.d.ts → src/types/NormalizedFieldDefinition.ts} +2 -1
- package/{build/types/NormalizedModelDefinition.d.ts → src/types/NormalizedModelDefinition.ts} +1 -0
- package/{build/types/NormalizedRelationDefinition.d.ts → src/types/NormalizedRelationDefinition.ts} +7 -1
- package/{build/types/NormalizedUniqueConstraintDefinition.d.ts → src/types/NormalizedUniqueConstraintDefinition.ts} +1 -0
- package/{build/types/PopulatedFieldDefinition.d.ts → src/types/PopulatedFieldDefinition.ts} +1 -0
- package/{build/types/PopulatedModelDefinition.d.ts → src/types/PopulatedModelDefinition.ts} +2 -0
- package/src/util.ts +38 -0
- package/build/index.js +0 -4
- package/build/normalize/defaultZodSchema.d.ts +0 -8
- package/build/normalize/defaultZodSchema.test.d.ts +0 -1
- package/build/normalize/getColumns.d.ts +0 -2
- package/build/normalize/getColumns.test.d.ts +0 -1
- package/build/normalize/normalizeConfig.d.ts +0 -3
- package/build/normalize/normalizeConfig.test.d.ts +0 -1
- package/build/normalize/normalizeField.d.ts +0 -3
- package/build/normalize/normalizeField.js +0 -11
- package/build/normalize/normalizeField.test.d.ts +0 -1
- package/build/normalize/normalizeForeignKeys.d.ts +0 -5
- package/build/normalize/normalizeForeignKeys.js +0 -50
- package/build/normalize/normalizeForeignKeys.test.d.ts +0 -1
- package/build/normalize/normalizeModel.d.ts +0 -3
- package/build/normalize/normalizeModel.test.d.ts +0 -1
- package/build/normalize/normalizePrimaryKey.d.ts +0 -3
- package/build/normalize/normalizePrimaryKey.js +0 -18
- package/build/normalize/normalizePrimaryKey.test.d.ts +0 -1
- package/build/normalize/normalizeRelations.d.ts +0 -3
- package/build/normalize/normalizeRelations.test.d.ts +0 -1
- package/build/normalize/normalizeUniqueConstraints.d.ts +0 -5
- package/build/normalize/normalizeUniqueConstraints.js +0 -29
- package/build/normalize/normalizeUniqueConstraints.test.d.ts +0 -1
- package/build/normalize/populateField.d.ts +0 -3
- package/build/normalize/populateField.test.d.ts +0 -1
- package/build/normalize/populateField.test.js +0 -198
- package/build/normalize/populateModels.d.ts +0 -3
- package/build/normalize/populateModels.test.d.ts +0 -1
- package/build/types/NormalizedConfig.js +0 -1
- package/build/types/NormalizedFieldDefinition.js +0 -1
- package/build/types/NormalizedForeignKeyDefinition.js +0 -1
- package/build/types/NormalizedModelDefinition.js +0 -1
- package/build/types/NormalizedPrimaryKey.js +0 -1
- package/build/types/NormalizedRelationDefinition.js +0 -1
- package/build/types/NormalizedUniqueConstraintDefinition.js +0 -1
- package/build/types/PopulatedFieldDefinition.js +0 -1
- package/build/types/PopulatedModelDefinition.js +0 -1
- package/build/util.d.ts +0 -6
- package/build/util.js +0 -21
- /package/{build/types/NormalizedForeignKeyDefinition.d.ts → src/types/NormalizedForeignKeyDefinition.ts} +0 -0
- /package/{build/types/NormalizedPrimaryKey.d.ts → src/types/NormalizedPrimaryKey.ts} +0 -0
package/{build/normalize/normalizeForeignKeys.test.js → src/normalize/normalizeForeignKeys.test.ts}
RENAMED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { snakeCase } from "es-toolkit";
|
|
2
2
|
import { describe, expect, test } from "vitest";
|
|
3
|
+
|
|
3
4
|
import { normalizeForeignKeys } from "./normalizeForeignKeys.js";
|
|
4
5
|
import { populateModels } from "./populateModels.js";
|
|
6
|
+
|
|
5
7
|
describe("normalizeForeignKeys", () => {
|
|
6
8
|
test("normalizes foreign keys defined at the top level", () => {
|
|
7
9
|
const models = populateModels({
|
|
@@ -23,7 +25,8 @@ describe("normalizeForeignKeys", () => {
|
|
|
23
25
|
},
|
|
24
26
|
},
|
|
25
27
|
});
|
|
26
|
-
|
|
28
|
+
|
|
29
|
+
expect(normalizeForeignKeys(models, models["post"]!)).toEqual([
|
|
27
30
|
{
|
|
28
31
|
name: "post_user_id_fkey",
|
|
29
32
|
fields: ["userId"],
|
|
@@ -40,6 +43,7 @@ describe("normalizeForeignKeys", () => {
|
|
|
40
43
|
},
|
|
41
44
|
]);
|
|
42
45
|
});
|
|
46
|
+
|
|
43
47
|
test("normalizes foreign keys defined in foreignKeys array", () => {
|
|
44
48
|
const models = populateModels({
|
|
45
49
|
naming: { column: snakeCase },
|
|
@@ -66,7 +70,8 @@ describe("normalizeForeignKeys", () => {
|
|
|
66
70
|
},
|
|
67
71
|
},
|
|
68
72
|
});
|
|
69
|
-
|
|
73
|
+
|
|
74
|
+
expect(normalizeForeignKeys(models, models["post"]!)).toEqual([
|
|
70
75
|
{
|
|
71
76
|
name: "post_user_id_fkey",
|
|
72
77
|
fields: ["userId"],
|
|
@@ -83,6 +88,7 @@ describe("normalizeForeignKeys", () => {
|
|
|
83
88
|
},
|
|
84
89
|
]);
|
|
85
90
|
});
|
|
91
|
+
|
|
86
92
|
test("throws error when referenced model doesn't exist", () => {
|
|
87
93
|
const models = populateModels({
|
|
88
94
|
naming: { column: snakeCase },
|
|
@@ -98,8 +104,12 @@ describe("normalizeForeignKeys", () => {
|
|
|
98
104
|
},
|
|
99
105
|
},
|
|
100
106
|
});
|
|
101
|
-
|
|
107
|
+
|
|
108
|
+
expect(() => normalizeForeignKeys(models, models["post"]!)).toThrow(
|
|
109
|
+
'Referenced model "nonexistent" not found in models',
|
|
110
|
+
);
|
|
102
111
|
});
|
|
112
|
+
|
|
103
113
|
test("respects custom onDelete and onUpdate actions", () => {
|
|
104
114
|
const models = populateModels({
|
|
105
115
|
naming: { column: snakeCase },
|
|
@@ -125,7 +135,8 @@ describe("normalizeForeignKeys", () => {
|
|
|
125
135
|
},
|
|
126
136
|
},
|
|
127
137
|
});
|
|
128
|
-
|
|
138
|
+
|
|
139
|
+
expect(normalizeForeignKeys(models, models["post"]!)).toEqual([
|
|
129
140
|
{
|
|
130
141
|
name: "post_user_id_fkey",
|
|
131
142
|
fields: ["userId"],
|
|
@@ -142,6 +153,7 @@ describe("normalizeForeignKeys", () => {
|
|
|
142
153
|
},
|
|
143
154
|
]);
|
|
144
155
|
});
|
|
156
|
+
|
|
145
157
|
test("throws error on duplicate foreign keys", () => {
|
|
146
158
|
const models = populateModels({
|
|
147
159
|
naming: { column: snakeCase },
|
|
@@ -171,8 +183,12 @@ describe("normalizeForeignKeys", () => {
|
|
|
171
183
|
},
|
|
172
184
|
},
|
|
173
185
|
});
|
|
174
|
-
|
|
186
|
+
|
|
187
|
+
expect(() => normalizeForeignKeys(models, models["post"]!)).toThrow(
|
|
188
|
+
'Duplicate foreign key defined in model "post"',
|
|
189
|
+
);
|
|
175
190
|
});
|
|
191
|
+
|
|
176
192
|
test("handles custom foreign key names", () => {
|
|
177
193
|
const models = populateModels({
|
|
178
194
|
naming: { column: snakeCase },
|
|
@@ -200,7 +216,8 @@ describe("normalizeForeignKeys", () => {
|
|
|
200
216
|
},
|
|
201
217
|
},
|
|
202
218
|
});
|
|
203
|
-
|
|
219
|
+
|
|
220
|
+
expect(normalizeForeignKeys(models, models["post"]!)).toEqual([
|
|
204
221
|
{
|
|
205
222
|
name: "custom_fk_name",
|
|
206
223
|
fields: ["userId"],
|
|
@@ -217,6 +234,7 @@ describe("normalizeForeignKeys", () => {
|
|
|
217
234
|
},
|
|
218
235
|
]);
|
|
219
236
|
});
|
|
237
|
+
|
|
220
238
|
test("handles foreign keys in custom schema", () => {
|
|
221
239
|
const models = populateModels({
|
|
222
240
|
naming: { column: snakeCase },
|
|
@@ -238,7 +256,8 @@ describe("normalizeForeignKeys", () => {
|
|
|
238
256
|
},
|
|
239
257
|
},
|
|
240
258
|
});
|
|
241
|
-
|
|
259
|
+
|
|
260
|
+
expect(normalizeForeignKeys(models, models["post"]!)).toEqual([
|
|
242
261
|
{
|
|
243
262
|
name: "post_user_id_fkey",
|
|
244
263
|
fields: ["userId"],
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { isEqual } from "es-toolkit";
|
|
2
|
+
|
|
3
|
+
import { FieldDefinition, ForeignKeyDefinition } from "@casekit/orm2-schema";
|
|
4
|
+
import { MarkNonNullable } from "@casekit/toolbox";
|
|
5
|
+
|
|
6
|
+
import { NormalizedForeignKeyDefinition } from "#types/NormalizedForeignKeyDefinition.js";
|
|
7
|
+
import { PopulatedFieldDefinition } from "#types/PopulatedFieldDefinition.js";
|
|
8
|
+
import { PopulatedModelDefinition } from "#types/PopulatedModelDefinition.js";
|
|
9
|
+
import { getColumns } from "./getColumns.js";
|
|
10
|
+
|
|
11
|
+
export const normalizeForeignKeys = (
|
|
12
|
+
models: Record<string, PopulatedModelDefinition>,
|
|
13
|
+
model: PopulatedModelDefinition,
|
|
14
|
+
): NormalizedForeignKeyDefinition[] => {
|
|
15
|
+
const columnLevelForeignKeys = Object.values(model.fields)
|
|
16
|
+
.filter(hasReference)
|
|
17
|
+
.map(referenceToForeignKey)
|
|
18
|
+
.map((fk) => normalizeForeignKey(models, model, fk));
|
|
19
|
+
|
|
20
|
+
const modelLevelForeignKeys = model.foreignKeys.map((fk) =>
|
|
21
|
+
normalizeForeignKey(models, model, fk),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
for (const fk of columnLevelForeignKeys) {
|
|
25
|
+
if (
|
|
26
|
+
modelLevelForeignKeys.some((other) =>
|
|
27
|
+
isEqual(fk.columns, other.columns),
|
|
28
|
+
)
|
|
29
|
+
) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Duplicate foreign key defined in model "${model.name}"`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return [...columnLevelForeignKeys, ...modelLevelForeignKeys];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const normalizeForeignKey = (
|
|
40
|
+
models: Record<string, PopulatedModelDefinition>,
|
|
41
|
+
model: PopulatedModelDefinition,
|
|
42
|
+
fk: ForeignKeyDefinition,
|
|
43
|
+
): NormalizedForeignKeyDefinition => {
|
|
44
|
+
const referencedModel = models[fk.references.model];
|
|
45
|
+
if (!referencedModel) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Referenced model "${fk.references.model}" not found in models`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const columns = getColumns(model, fk.fields);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
name: fk.name ?? [model.table, ...columns, "fkey"].join("_"),
|
|
55
|
+
fields: fk.fields,
|
|
56
|
+
columns: columns,
|
|
57
|
+
references: {
|
|
58
|
+
model: fk.references.model,
|
|
59
|
+
fields: fk.references.fields,
|
|
60
|
+
schema: referencedModel.schema,
|
|
61
|
+
table: referencedModel.table,
|
|
62
|
+
columns: getColumns(referencedModel, fk.references.fields),
|
|
63
|
+
},
|
|
64
|
+
onUpdate: fk.onUpdate ?? null,
|
|
65
|
+
onDelete: fk.onDelete ?? null,
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const referenceToForeignKey = (
|
|
70
|
+
field: MarkNonNullable<PopulatedFieldDefinition, "references">,
|
|
71
|
+
): ForeignKeyDefinition => {
|
|
72
|
+
return {
|
|
73
|
+
fields: [field.name],
|
|
74
|
+
references: {
|
|
75
|
+
model: field.references.model,
|
|
76
|
+
fields: [field.references.field],
|
|
77
|
+
},
|
|
78
|
+
onUpdate: field.references.onUpdate,
|
|
79
|
+
onDelete: field.references.onDelete,
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const hasReference = (
|
|
84
|
+
field: FieldDefinition,
|
|
85
|
+
): field is PopulatedFieldDefinition & {
|
|
86
|
+
references: MarkNonNullable<PopulatedFieldDefinition, "references">;
|
|
87
|
+
} => {
|
|
88
|
+
return !!field.references;
|
|
89
|
+
};
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { snakeCase } from "es-toolkit";
|
|
2
2
|
import { describe, expect, test } from "vitest";
|
|
3
|
-
import {
|
|
3
|
+
import { ZodType, z } from "zod";
|
|
4
|
+
|
|
4
5
|
import { sql } from "@casekit/sql";
|
|
6
|
+
|
|
5
7
|
import { normalizeModel } from "./normalizeModel.js";
|
|
6
8
|
import { populateModels } from "./populateModels.js";
|
|
9
|
+
|
|
7
10
|
describe("normalizeModel", () => {
|
|
8
11
|
test("normalizes complete model definition", () => {
|
|
9
12
|
const models = populateModels({
|
|
@@ -19,7 +22,7 @@ describe("normalizeModel", () => {
|
|
|
19
22
|
email: {
|
|
20
23
|
type: "text",
|
|
21
24
|
unique: true,
|
|
22
|
-
zodSchema: z.
|
|
25
|
+
zodSchema: z.email(),
|
|
23
26
|
},
|
|
24
27
|
name: {
|
|
25
28
|
type: "text",
|
|
@@ -27,7 +30,7 @@ describe("normalizeModel", () => {
|
|
|
27
30
|
},
|
|
28
31
|
createdAt: {
|
|
29
32
|
type: "timestamp",
|
|
30
|
-
default: sql
|
|
33
|
+
default: sql`NOW()`,
|
|
31
34
|
provided: true,
|
|
32
35
|
},
|
|
33
36
|
},
|
|
@@ -63,32 +66,38 @@ describe("normalizeModel", () => {
|
|
|
63
66
|
},
|
|
64
67
|
},
|
|
65
68
|
});
|
|
66
|
-
|
|
69
|
+
|
|
70
|
+
const result = normalizeModel(models, models["user"]!);
|
|
71
|
+
|
|
67
72
|
// Test basic model properties
|
|
68
73
|
expect(result.name).toBe("user");
|
|
69
74
|
expect(result.schema).toBe("auth");
|
|
70
75
|
expect(result.table).toBe("user");
|
|
76
|
+
|
|
71
77
|
// Test fields
|
|
72
|
-
expect(result.fields["id"]).toEqual({
|
|
78
|
+
expect(result.fields["id"]!).toEqual({
|
|
73
79
|
name: "id",
|
|
74
80
|
column: "id",
|
|
75
81
|
type: "serial",
|
|
76
|
-
zodSchema: expect.any(
|
|
82
|
+
zodSchema: expect.any(ZodType),
|
|
77
83
|
nullable: false,
|
|
78
84
|
default: null,
|
|
79
85
|
provided: false,
|
|
80
86
|
});
|
|
81
|
-
|
|
87
|
+
|
|
88
|
+
expect(result.fields["email"]!).toEqual({
|
|
82
89
|
name: "email",
|
|
83
90
|
column: "email",
|
|
84
91
|
type: "text",
|
|
85
|
-
zodSchema: expect.any(
|
|
92
|
+
zodSchema: expect.any(ZodType),
|
|
86
93
|
nullable: false,
|
|
87
94
|
default: null,
|
|
88
95
|
provided: false,
|
|
89
96
|
});
|
|
97
|
+
|
|
90
98
|
// Test primary key
|
|
91
99
|
expect(result.primaryKey).toEqual([{ field: "id", column: "id" }]);
|
|
100
|
+
|
|
92
101
|
// Test unique constraints
|
|
93
102
|
expect(result.uniqueConstraints).toEqual([
|
|
94
103
|
{
|
|
@@ -99,8 +108,9 @@ describe("normalizeModel", () => {
|
|
|
99
108
|
nullsNotDistinct: false,
|
|
100
109
|
},
|
|
101
110
|
]);
|
|
111
|
+
|
|
102
112
|
// Test relations
|
|
103
|
-
expect(result.relations["posts"]).toEqual({
|
|
113
|
+
expect(result.relations["posts"]!).toEqual({
|
|
104
114
|
name: "posts",
|
|
105
115
|
type: "1:N",
|
|
106
116
|
model: "post",
|
|
@@ -114,7 +124,8 @@ describe("normalizeModel", () => {
|
|
|
114
124
|
columns: ["author_id"],
|
|
115
125
|
},
|
|
116
126
|
});
|
|
117
|
-
|
|
127
|
+
|
|
128
|
+
expect(result.relations["likedPosts"]!).toEqual({
|
|
118
129
|
name: "likedPosts",
|
|
119
130
|
type: "N:N",
|
|
120
131
|
model: "post",
|
|
@@ -127,6 +138,7 @@ describe("normalizeModel", () => {
|
|
|
127
138
|
},
|
|
128
139
|
});
|
|
129
140
|
});
|
|
141
|
+
|
|
130
142
|
test("normalizes model with column name transformations", () => {
|
|
131
143
|
const models = populateModels({
|
|
132
144
|
naming: { column: snakeCase },
|
|
@@ -141,12 +153,16 @@ describe("normalizeModel", () => {
|
|
|
141
153
|
},
|
|
142
154
|
},
|
|
143
155
|
});
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
expect(result.fields["
|
|
148
|
-
expect(result.
|
|
156
|
+
|
|
157
|
+
const result = normalizeModel(models, models["userProfile"]!);
|
|
158
|
+
|
|
159
|
+
expect(result.fields["firstName"]!.column).toBe("first_name");
|
|
160
|
+
expect(result.fields["lastName"]!.column).toBe("last_name");
|
|
161
|
+
expect(result.fields["emailAddress"]!.column).toBe("email_address");
|
|
162
|
+
|
|
163
|
+
expect(result.uniqueConstraints[0]!.columns).toEqual(["email_address"]);
|
|
149
164
|
});
|
|
165
|
+
|
|
150
166
|
test("normalizes model with custom schema and table names", () => {
|
|
151
167
|
const models = populateModels({
|
|
152
168
|
models: {
|
|
@@ -159,10 +175,13 @@ describe("normalizeModel", () => {
|
|
|
159
175
|
},
|
|
160
176
|
},
|
|
161
177
|
});
|
|
162
|
-
|
|
178
|
+
|
|
179
|
+
const result = normalizeModel(models, models["user"]!);
|
|
180
|
+
|
|
163
181
|
expect(result.schema).toBe("custom_schema");
|
|
164
182
|
expect(result.table).toBe("custom_table");
|
|
165
183
|
});
|
|
184
|
+
|
|
166
185
|
test("normalizes model with composite primary key", () => {
|
|
167
186
|
const models = populateModels({
|
|
168
187
|
naming: { column: snakeCase },
|
|
@@ -176,12 +195,15 @@ describe("normalizeModel", () => {
|
|
|
176
195
|
},
|
|
177
196
|
},
|
|
178
197
|
});
|
|
179
|
-
|
|
198
|
+
|
|
199
|
+
const result = normalizeModel(models, models["orderLine"]!);
|
|
200
|
+
|
|
180
201
|
expect(result.primaryKey).toEqual([
|
|
181
202
|
{ field: "orderId", column: "order_id" },
|
|
182
203
|
{ field: "lineNumber", column: "line_number" },
|
|
183
204
|
]);
|
|
184
205
|
});
|
|
206
|
+
|
|
185
207
|
test("normalizes model with foreign keys", () => {
|
|
186
208
|
const models = populateModels({
|
|
187
209
|
naming: { column: snakeCase },
|
|
@@ -206,7 +228,9 @@ describe("normalizeModel", () => {
|
|
|
206
228
|
},
|
|
207
229
|
},
|
|
208
230
|
});
|
|
209
|
-
|
|
231
|
+
|
|
232
|
+
const result = normalizeModel(models, models["order"]!);
|
|
233
|
+
|
|
210
234
|
expect(result.foreignKeys).toEqual([
|
|
211
235
|
{
|
|
212
236
|
name: "order_customer_id_fkey",
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { mapValues } from "es-toolkit";
|
|
2
|
+
|
|
3
|
+
import { NormalizedModelDefinition } from "#types/NormalizedModelDefinition.js";
|
|
4
|
+
import { PopulatedModelDefinition } from "#types/PopulatedModelDefinition.js";
|
|
2
5
|
import { normalizeField } from "./normalizeField.js";
|
|
3
6
|
import { normalizeForeignKeys } from "./normalizeForeignKeys.js";
|
|
4
7
|
import { normalizePrimaryKey } from "./normalizePrimaryKey.js";
|
|
5
8
|
import { normalizeRelations } from "./normalizeRelations.js";
|
|
6
9
|
import { normalizeUniqueConstraints } from "./normalizeUniqueConstraints.js";
|
|
7
|
-
|
|
10
|
+
|
|
11
|
+
export const normalizeModel = (
|
|
12
|
+
models: Record<string, PopulatedModelDefinition>,
|
|
13
|
+
model: PopulatedModelDefinition,
|
|
14
|
+
): NormalizedModelDefinition => {
|
|
8
15
|
return {
|
|
9
16
|
name: model.name,
|
|
10
17
|
schema: model.schema,
|
package/{build/normalize/normalizePrimaryKey.test.js → src/normalize/normalizePrimaryKey.test.ts}
RENAMED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { snakeCase } from "es-toolkit";
|
|
2
2
|
import { describe, expect, test } from "vitest";
|
|
3
|
+
|
|
3
4
|
import { normalizePrimaryKey } from "./normalizePrimaryKey.js";
|
|
4
5
|
import { populateModels } from "./populateModels.js";
|
|
6
|
+
|
|
5
7
|
describe("normalizePrimaryKey", () => {
|
|
6
8
|
test("normalizes field-level primary key", () => {
|
|
7
9
|
const models = populateModels({
|
|
@@ -14,7 +16,9 @@ describe("normalizePrimaryKey", () => {
|
|
|
14
16
|
},
|
|
15
17
|
},
|
|
16
18
|
});
|
|
17
|
-
|
|
19
|
+
|
|
20
|
+
const result = normalizePrimaryKey(models["user"]!);
|
|
21
|
+
|
|
18
22
|
expect(result).toEqual([
|
|
19
23
|
{
|
|
20
24
|
field: "id",
|
|
@@ -22,6 +26,7 @@ describe("normalizePrimaryKey", () => {
|
|
|
22
26
|
},
|
|
23
27
|
]);
|
|
24
28
|
});
|
|
29
|
+
|
|
25
30
|
test("normalizes model-level primary key", () => {
|
|
26
31
|
const models = populateModels({
|
|
27
32
|
models: {
|
|
@@ -35,7 +40,9 @@ describe("normalizePrimaryKey", () => {
|
|
|
35
40
|
},
|
|
36
41
|
},
|
|
37
42
|
});
|
|
38
|
-
|
|
43
|
+
|
|
44
|
+
const result = normalizePrimaryKey(models["userRole"]!);
|
|
45
|
+
|
|
39
46
|
expect(result).toEqual([
|
|
40
47
|
{
|
|
41
48
|
field: "userId",
|
|
@@ -47,6 +54,7 @@ describe("normalizePrimaryKey", () => {
|
|
|
47
54
|
},
|
|
48
55
|
]);
|
|
49
56
|
});
|
|
57
|
+
|
|
50
58
|
test("throws error when primary key is defined at both levels", () => {
|
|
51
59
|
const models = populateModels({
|
|
52
60
|
models: {
|
|
@@ -59,8 +67,12 @@ describe("normalizePrimaryKey", () => {
|
|
|
59
67
|
},
|
|
60
68
|
},
|
|
61
69
|
});
|
|
62
|
-
|
|
70
|
+
|
|
71
|
+
expect(() => normalizePrimaryKey(models["user"]!)).toThrow(
|
|
72
|
+
'Model "user" has primary key fields defined at both the model and field levels.',
|
|
73
|
+
);
|
|
63
74
|
});
|
|
75
|
+
|
|
64
76
|
test("handles column name transformations", () => {
|
|
65
77
|
const models = populateModels({
|
|
66
78
|
naming: { column: snakeCase },
|
|
@@ -73,7 +85,9 @@ describe("normalizePrimaryKey", () => {
|
|
|
73
85
|
},
|
|
74
86
|
},
|
|
75
87
|
});
|
|
76
|
-
|
|
88
|
+
|
|
89
|
+
const result = normalizePrimaryKey(models["user"]!);
|
|
90
|
+
|
|
77
91
|
expect(result).toEqual([
|
|
78
92
|
{
|
|
79
93
|
field: "userId",
|
|
@@ -81,6 +95,7 @@ describe("normalizePrimaryKey", () => {
|
|
|
81
95
|
},
|
|
82
96
|
]);
|
|
83
97
|
});
|
|
98
|
+
|
|
84
99
|
test("handles custom column names", () => {
|
|
85
100
|
const models = populateModels({
|
|
86
101
|
models: {
|
|
@@ -95,7 +110,9 @@ describe("normalizePrimaryKey", () => {
|
|
|
95
110
|
},
|
|
96
111
|
},
|
|
97
112
|
});
|
|
98
|
-
|
|
113
|
+
|
|
114
|
+
const result = normalizePrimaryKey(models["user"]!);
|
|
115
|
+
|
|
99
116
|
expect(result).toEqual([
|
|
100
117
|
{
|
|
101
118
|
field: "id",
|
|
@@ -103,6 +120,7 @@ describe("normalizePrimaryKey", () => {
|
|
|
103
120
|
},
|
|
104
121
|
]);
|
|
105
122
|
});
|
|
123
|
+
|
|
106
124
|
test("throws error for non-existent primary key field in model-level definition", () => {
|
|
107
125
|
const models = populateModels({
|
|
108
126
|
models: {
|
|
@@ -115,8 +133,12 @@ describe("normalizePrimaryKey", () => {
|
|
|
115
133
|
},
|
|
116
134
|
},
|
|
117
135
|
});
|
|
118
|
-
|
|
136
|
+
|
|
137
|
+
expect(() => normalizePrimaryKey(models["user"]!)).toThrow(
|
|
138
|
+
'Primary key field "nonexistent" does not exist in model "user".',
|
|
139
|
+
);
|
|
119
140
|
});
|
|
141
|
+
|
|
120
142
|
test("handles multiple field-level primary keys", () => {
|
|
121
143
|
const models = populateModels({
|
|
122
144
|
models: {
|
|
@@ -129,7 +151,9 @@ describe("normalizePrimaryKey", () => {
|
|
|
129
151
|
},
|
|
130
152
|
},
|
|
131
153
|
});
|
|
132
|
-
|
|
154
|
+
|
|
155
|
+
const result = normalizePrimaryKey(models["userRole"]!);
|
|
156
|
+
|
|
133
157
|
expect(result).toEqual([
|
|
134
158
|
{
|
|
135
159
|
field: "userId",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { NormalizedPrimaryKey } from "#types/NormalizedPrimaryKey.js";
|
|
2
|
+
import { PopulatedModelDefinition } from "#types/PopulatedModelDefinition.js";
|
|
3
|
+
|
|
4
|
+
export const normalizePrimaryKey = (
|
|
5
|
+
model: PopulatedModelDefinition,
|
|
6
|
+
): NormalizedPrimaryKey[] => {
|
|
7
|
+
const fieldLevelPrimaryKey = Object.entries(model.fields)
|
|
8
|
+
.filter(([, field]) => field.primaryKey)
|
|
9
|
+
.map(([name]) => name);
|
|
10
|
+
|
|
11
|
+
if (model.primaryKey && fieldLevelPrimaryKey.length > 0) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
`Model "${model.name}" has primary key fields defined at both the model and field levels.`,
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const fields = model.primaryKey ?? fieldLevelPrimaryKey;
|
|
18
|
+
|
|
19
|
+
return fields.map((name) => {
|
|
20
|
+
if (!model.fields[name]) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Primary key field "${name}" does not exist in model "${model.name}".`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
field: name,
|
|
27
|
+
column: model.fields[name].column,
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
};
|
package/{build/normalize/normalizeRelations.test.js → src/normalize/normalizeRelations.test.ts}
RENAMED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { snakeCase } from "es-toolkit";
|
|
2
2
|
import { describe, expect, test } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { NormalizedOneToManyRelationDefinition } from "../types/NormalizedRelationDefinition.js";
|
|
3
5
|
import { normalizeRelations } from "./normalizeRelations.js";
|
|
4
6
|
import { populateModels } from "./populateModels.js";
|
|
7
|
+
|
|
5
8
|
describe("normalizeRelations", () => {
|
|
6
9
|
test("normalizes one-to-many relation", () => {
|
|
7
10
|
const models = populateModels({
|
|
@@ -28,7 +31,9 @@ describe("normalizeRelations", () => {
|
|
|
28
31
|
},
|
|
29
32
|
},
|
|
30
33
|
});
|
|
31
|
-
|
|
34
|
+
|
|
35
|
+
const result = normalizeRelations(models, models["user"]!);
|
|
36
|
+
|
|
32
37
|
expect(result).toEqual({
|
|
33
38
|
posts: {
|
|
34
39
|
name: "posts",
|
|
@@ -46,6 +51,7 @@ describe("normalizeRelations", () => {
|
|
|
46
51
|
},
|
|
47
52
|
});
|
|
48
53
|
});
|
|
54
|
+
|
|
49
55
|
test("normalizes many-to-one relation", () => {
|
|
50
56
|
const models = populateModels({
|
|
51
57
|
naming: { column: snakeCase },
|
|
@@ -71,7 +77,9 @@ describe("normalizeRelations", () => {
|
|
|
71
77
|
},
|
|
72
78
|
},
|
|
73
79
|
});
|
|
74
|
-
|
|
80
|
+
|
|
81
|
+
const result = normalizeRelations(models, models["post"]!);
|
|
82
|
+
|
|
75
83
|
expect(result).toEqual({
|
|
76
84
|
author: {
|
|
77
85
|
name: "author",
|
|
@@ -90,6 +98,7 @@ describe("normalizeRelations", () => {
|
|
|
90
98
|
},
|
|
91
99
|
});
|
|
92
100
|
});
|
|
101
|
+
|
|
93
102
|
test("normalizes many-to-many relation", () => {
|
|
94
103
|
const models = populateModels({
|
|
95
104
|
naming: { column: snakeCase },
|
|
@@ -123,7 +132,9 @@ describe("normalizeRelations", () => {
|
|
|
123
132
|
},
|
|
124
133
|
},
|
|
125
134
|
});
|
|
126
|
-
|
|
135
|
+
|
|
136
|
+
const result = normalizeRelations(models, models["user"]!);
|
|
137
|
+
|
|
127
138
|
expect(result).toEqual({
|
|
128
139
|
likedPosts: {
|
|
129
140
|
model: "post",
|
|
@@ -139,6 +150,7 @@ describe("normalizeRelations", () => {
|
|
|
139
150
|
},
|
|
140
151
|
});
|
|
141
152
|
});
|
|
153
|
+
|
|
142
154
|
test("handles composite keys in relations", () => {
|
|
143
155
|
const models = populateModels({
|
|
144
156
|
naming: { column: snakeCase },
|
|
@@ -167,7 +179,9 @@ describe("normalizeRelations", () => {
|
|
|
167
179
|
},
|
|
168
180
|
},
|
|
169
181
|
});
|
|
170
|
-
|
|
182
|
+
|
|
183
|
+
const result = normalizeRelations(models, models["order"]!);
|
|
184
|
+
|
|
171
185
|
expect(result).toEqual({
|
|
172
186
|
product: {
|
|
173
187
|
name: "product",
|
|
@@ -186,6 +200,7 @@ describe("normalizeRelations", () => {
|
|
|
186
200
|
},
|
|
187
201
|
});
|
|
188
202
|
});
|
|
203
|
+
|
|
189
204
|
test("throws error for non-existent related model", () => {
|
|
190
205
|
const models = populateModels({
|
|
191
206
|
models: {
|
|
@@ -204,8 +219,12 @@ describe("normalizeRelations", () => {
|
|
|
204
219
|
},
|
|
205
220
|
},
|
|
206
221
|
});
|
|
207
|
-
|
|
222
|
+
|
|
223
|
+
expect(() => normalizeRelations(models, models["user"]!)).toThrow(
|
|
224
|
+
'Model "user" has relation "posts" that references non-existent model "nonexistent".',
|
|
225
|
+
);
|
|
208
226
|
});
|
|
227
|
+
|
|
209
228
|
test("throws error for non-existent join model", () => {
|
|
210
229
|
const models = populateModels({
|
|
211
230
|
models: {
|
|
@@ -232,8 +251,12 @@ describe("normalizeRelations", () => {
|
|
|
232
251
|
},
|
|
233
252
|
},
|
|
234
253
|
});
|
|
235
|
-
|
|
254
|
+
|
|
255
|
+
expect(() => normalizeRelations(models, models["user"]!)).toThrow(
|
|
256
|
+
'Model "user" has relation "likedPosts" with join model "nonexistent" that does not exist.',
|
|
257
|
+
);
|
|
236
258
|
});
|
|
259
|
+
|
|
237
260
|
test("throws error for non-existent field in relation", () => {
|
|
238
261
|
const models = populateModels({
|
|
239
262
|
models: {
|
|
@@ -258,8 +281,12 @@ describe("normalizeRelations", () => {
|
|
|
258
281
|
},
|
|
259
282
|
},
|
|
260
283
|
});
|
|
261
|
-
|
|
284
|
+
|
|
285
|
+
expect(() => normalizeRelations(models, models["user"]!)).toThrow(
|
|
286
|
+
'Model "user" has relation with non-existent field "nonexistent".',
|
|
287
|
+
);
|
|
262
288
|
});
|
|
289
|
+
|
|
263
290
|
test("handles custom column names", () => {
|
|
264
291
|
const models = populateModels({
|
|
265
292
|
models: {
|
|
@@ -290,8 +317,10 @@ describe("normalizeRelations", () => {
|
|
|
290
317
|
},
|
|
291
318
|
},
|
|
292
319
|
});
|
|
293
|
-
|
|
294
|
-
const
|
|
320
|
+
|
|
321
|
+
const result = normalizeRelations(models, models["user"]!);
|
|
322
|
+
|
|
323
|
+
const posts = result["posts"]! as NormalizedOneToManyRelationDefinition;
|
|
295
324
|
expect(posts.from.columns).toEqual(["user_identifier"]);
|
|
296
325
|
expect(posts.to.columns).toEqual(["created_by_user"]);
|
|
297
326
|
});
|