@casekit/orm2-config 0.0.0-20250322230249

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 (67) hide show
  1. package/build/index.d.ts +11 -0
  2. package/build/index.js +4 -0
  3. package/build/normalize/defaultZodSchema.d.ts +8 -0
  4. package/build/normalize/defaultZodSchema.js +127 -0
  5. package/build/normalize/defaultZodSchema.test.d.ts +1 -0
  6. package/build/normalize/defaultZodSchema.test.js +153 -0
  7. package/build/normalize/getColumns.d.ts +2 -0
  8. package/build/normalize/getColumns.js +8 -0
  9. package/build/normalize/getColumns.test.d.ts +1 -0
  10. package/build/normalize/getColumns.test.js +63 -0
  11. package/build/normalize/normalizeConfig.d.ts +3 -0
  12. package/build/normalize/normalizeConfig.js +20 -0
  13. package/build/normalize/normalizeConfig.test.d.ts +1 -0
  14. package/build/normalize/normalizeConfig.test.js +217 -0
  15. package/build/normalize/normalizeField.d.ts +3 -0
  16. package/build/normalize/normalizeField.js +11 -0
  17. package/build/normalize/normalizeField.test.d.ts +1 -0
  18. package/build/normalize/normalizeField.test.js +59 -0
  19. package/build/normalize/normalizeForeignKeys.d.ts +5 -0
  20. package/build/normalize/normalizeForeignKeys.js +50 -0
  21. package/build/normalize/normalizeForeignKeys.test.d.ts +1 -0
  22. package/build/normalize/normalizeForeignKeys.test.js +258 -0
  23. package/build/normalize/normalizeModel.d.ts +3 -0
  24. package/build/normalize/normalizeModel.js +18 -0
  25. package/build/normalize/normalizeModel.test.d.ts +1 -0
  26. package/build/normalize/normalizeModel.test.js +227 -0
  27. package/build/normalize/normalizePrimaryKey.d.ts +3 -0
  28. package/build/normalize/normalizePrimaryKey.js +18 -0
  29. package/build/normalize/normalizePrimaryKey.test.d.ts +1 -0
  30. package/build/normalize/normalizePrimaryKey.test.js +144 -0
  31. package/build/normalize/normalizeRelations.d.ts +3 -0
  32. package/build/normalize/normalizeRelations.js +58 -0
  33. package/build/normalize/normalizeRelations.test.d.ts +1 -0
  34. package/build/normalize/normalizeRelations.test.js +298 -0
  35. package/build/normalize/normalizeUniqueConstraints.d.ts +5 -0
  36. package/build/normalize/normalizeUniqueConstraints.js +29 -0
  37. package/build/normalize/normalizeUniqueConstraints.test.d.ts +1 -0
  38. package/build/normalize/normalizeUniqueConstraints.test.js +241 -0
  39. package/build/normalize/populateField.d.ts +3 -0
  40. package/build/normalize/populateField.js +15 -0
  41. package/build/normalize/populateField.test.d.ts +1 -0
  42. package/build/normalize/populateField.test.js +198 -0
  43. package/build/normalize/populateModels.d.ts +3 -0
  44. package/build/normalize/populateModels.js +16 -0
  45. package/build/normalize/populateModels.test.d.ts +1 -0
  46. package/build/normalize/populateModels.test.js +240 -0
  47. package/build/types/NormalizedConfig.d.ts +16 -0
  48. package/build/types/NormalizedConfig.js +1 -0
  49. package/build/types/NormalizedFieldDefinition.d.ts +10 -0
  50. package/build/types/NormalizedFieldDefinition.js +1 -0
  51. package/build/types/NormalizedForeignKeyDefinition.d.ts +14 -0
  52. package/build/types/NormalizedForeignKeyDefinition.js +1 -0
  53. package/build/types/NormalizedModelDefinition.d.ts +15 -0
  54. package/build/types/NormalizedModelDefinition.js +1 -0
  55. package/build/types/NormalizedPrimaryKey.d.ts +4 -0
  56. package/build/types/NormalizedPrimaryKey.js +1 -0
  57. package/build/types/NormalizedRelationDefinition.d.ts +42 -0
  58. package/build/types/NormalizedRelationDefinition.js +1 -0
  59. package/build/types/NormalizedUniqueConstraintDefinition.d.ts +8 -0
  60. package/build/types/NormalizedUniqueConstraintDefinition.js +1 -0
  61. package/build/types/PopulatedFieldDefinition.d.ts +4 -0
  62. package/build/types/PopulatedFieldDefinition.js +1 -0
  63. package/build/types/PopulatedModelDefinition.d.ts +6 -0
  64. package/build/types/PopulatedModelDefinition.js +1 -0
  65. package/build/util.d.ts +6 -0
  66. package/build/util.js +21 -0
  67. package/package.json +54 -0
@@ -0,0 +1,11 @@
1
+ export { normalizeConfig } from "./normalize/normalizeConfig.js";
2
+ export { normalizeModel } from "./normalize/normalizeModel.js";
3
+ export { normalizeUniqueConstraint } from "./normalize/normalizeUniqueConstraints.js";
4
+ export type { NormalizedConfig } from "./types/NormalizedConfig.js";
5
+ export type { NormalizedFieldDefinition } from "./types/NormalizedFieldDefinition.js";
6
+ export type { NormalizedForeignKeyDefinition } from "./types/NormalizedForeignKeyDefinition.js";
7
+ export type { NormalizedModelDefinition } from "./types/NormalizedModelDefinition.js";
8
+ export type { NormalizedPrimaryKey } from "./types/NormalizedPrimaryKey.js";
9
+ export type { NormalizedManyToManyRelationDefinition, NormalizedManyToOneRelationDefinition, NormalizedOneToManyRelationDefinition, NormalizedRelationDefinition, } from "./types/NormalizedRelationDefinition.js";
10
+ export type { NormalizedUniqueConstraintDefinition } from "./types/NormalizedUniqueConstraintDefinition.js";
11
+ export { getField, getModel, getRelation } from "./util.js";
package/build/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { normalizeConfig } from "./normalize/normalizeConfig.js";
2
+ export { normalizeModel } from "./normalize/normalizeModel.js";
3
+ export { normalizeUniqueConstraint } from "./normalize/normalizeUniqueConstraints.js";
4
+ export { getField, getModel, getRelation } from "./util.js";
@@ -0,0 +1,8 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * WARNING!!! The schemas in this file must be kept in sync
4
+ * with DefaultFieldType in packages/orm-schema/src/helper/DefaultFieldType.ts.
5
+ * If you make a change here, make sure to update the
6
+ * corresponding type.
7
+ */
8
+ export declare const defaultZodSchema: (type: string) => z.ZodSchema<unknown>;
@@ -0,0 +1,127 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * WARNING!!! The schemas in this file must be kept in sync
4
+ * with DefaultFieldType in packages/orm-schema/src/helper/DefaultFieldType.ts.
5
+ * If you make a change here, make sure to update the
6
+ * corresponding type.
7
+ */
8
+ export const defaultZodSchema = (type) => {
9
+ if (type.endsWith("[]"))
10
+ return z.array(defaultZodSchema(type.slice(0, -2)));
11
+ if (type.startsWith("bit "))
12
+ return z.string();
13
+ if (type.startsWith("bit("))
14
+ return z.string();
15
+ if (type.startsWith("character varying"))
16
+ return z.string();
17
+ if (type.startsWith("character"))
18
+ return z.string();
19
+ if (type.startsWith("numeric "))
20
+ return z.string();
21
+ if (type.startsWith("numeric("))
22
+ return z.string();
23
+ if (type.startsWith("timestamp "))
24
+ return z.date();
25
+ if (type.startsWith("timestamp("))
26
+ return z.date();
27
+ if (type.startsWith("time "))
28
+ return z.string();
29
+ if (type.startsWith("time("))
30
+ return z.string();
31
+ if (type.startsWith("varchar "))
32
+ return z.string();
33
+ if (type.startsWith("varchar("))
34
+ return z.string();
35
+ // if (type.startsWith("interval"))
36
+ // return z.object({
37
+ // years: z.number().optional(),
38
+ // months: z.number().optional(),
39
+ // days: z.number().optional(),
40
+ // hours: z.number().optional(),
41
+ // minutes: z.number().optional(),
42
+ // seconds: z.number().optional(),
43
+ // milliseconds: z.number().optional(),
44
+ // });
45
+ switch (type) {
46
+ case "bigint":
47
+ case "bigserial":
48
+ return z.coerce.bigint();
49
+ case "decimal":
50
+ case "double precision":
51
+ case "integer":
52
+ case "oid":
53
+ case "real":
54
+ case "smallint":
55
+ case "smallserial":
56
+ case "serial":
57
+ return z.number();
58
+ case "bpchar":
59
+ case "bit":
60
+ case "box":
61
+ case "cidr":
62
+ case "daterange":
63
+ case "inet":
64
+ case "int4range":
65
+ case "int8range":
66
+ case "int2vector":
67
+ case "pg_lsn":
68
+ case "regclass":
69
+ case "regconfig":
70
+ case "regdictionary":
71
+ case "regnamespace":
72
+ case "regoper":
73
+ case "regoperator":
74
+ case "regproc":
75
+ case "regprocedure":
76
+ case "regrole":
77
+ case "regtype":
78
+ case "tid":
79
+ case "xid":
80
+ case "numrange":
81
+ case "tsrange":
82
+ case "tstzrange":
83
+ case "line":
84
+ case "lseg":
85
+ case "macaddr":
86
+ case "macaddr8":
87
+ case "money":
88
+ case "numeric":
89
+ case "path":
90
+ case "polygon":
91
+ case "text":
92
+ case "time":
93
+ case "timetz":
94
+ case "tsquery":
95
+ case "tsvector":
96
+ case "txid_snapshot":
97
+ case "varchar":
98
+ case "char":
99
+ case "xml":
100
+ return z.string();
101
+ case "bytea":
102
+ return z.instanceof(Buffer);
103
+ // case "circle":
104
+ // return z.object({
105
+ // x: z.number(),
106
+ // y: z.number(),
107
+ // radius: z.number(),
108
+ // });
109
+ // case "point":
110
+ // return z.object({ x: z.number(), y: z.number() });
111
+ case "json":
112
+ case "jsonb":
113
+ return z.record(z.any());
114
+ case "uuid":
115
+ return z.string().uuid();
116
+ case "boolean":
117
+ return z.boolean();
118
+ case "date":
119
+ case "timestamp":
120
+ case "timestamptz":
121
+ return z.date();
122
+ default:
123
+ throw new Error("Unsupported type: " +
124
+ type +
125
+ " - please specify a zod schema in the field definition");
126
+ }
127
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,153 @@
1
+ import pg from "pg";
2
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
3
+ import { defaultZodSchema } from "./defaultZodSchema.js";
4
+ describe("defaultZodSchema", () => {
5
+ let db;
6
+ beforeAll(async () => {
7
+ db = new pg.Client();
8
+ await db.connect();
9
+ });
10
+ afterEach(async () => {
11
+ await db.query("ROLLBACK");
12
+ });
13
+ afterAll(async () => {
14
+ await db.end();
15
+ });
16
+ test.for([
17
+ ["char", "a", "a"],
18
+ ["character", "a", "a"],
19
+ ["character(24)", "abc", "abc "],
20
+ ["character varying", "abc", "abc"],
21
+ ["character varying(24)", "abc", "abc"],
22
+ ["varchar", "abc", "abc"],
23
+ ["bit", "1", "1"],
24
+ ["bit", "0", "0"],
25
+ ["bit(3)", "101", "101"],
26
+ ["bit varying", "101", "101"],
27
+ ["numeric", 123, "123"],
28
+ ["numeric(5,2)", 123.45, "123.45"],
29
+ ["numeric (5,2)", 123.45, "123.45"],
30
+ ["integer", 42, 42],
31
+ ["bigint", "9007199254740991", 9007199254740991n],
32
+ ["double precision", 3.14159, 3.14159],
33
+ ["real", 3.14, 3.14],
34
+ ["text", "hello world", "hello world"],
35
+ [
36
+ "uuid",
37
+ "123e4567-e89b-12d3-a456-426614174000",
38
+ "123e4567-e89b-12d3-a456-426614174000",
39
+ ],
40
+ ["boolean", true, true],
41
+ ["bpchar", "abc", "abc"],
42
+ ["date", "2023-01-01", new Date("2023-01-01")],
43
+ ["timetz", "13:45:30", "13:45:30+00"],
44
+ ["timestamptz", "2023-01-01 13:45:30", new Date("2023-01-01T13:45:30")],
45
+ ["time", "13:45:30", "13:45:30"],
46
+ ["time(3)", "13:45:30", "13:45:30"],
47
+ ["timestamp", "2023-01-01 13:45:30", new Date("2023-01-01T13:45:30")],
48
+ [
49
+ "timestamp(6)",
50
+ "2023-01-01 13:45:30",
51
+ new Date("2023-01-01T13:45:30"),
52
+ ],
53
+ // ["interval", "1 year 2 months", { months: 2, years: 1 }],
54
+ // [
55
+ // "interval",
56
+ // "3 days 12 hours 7 minutes 2 seconds 555 milliseconds",
57
+ // {
58
+ // days: 3,
59
+ // hours: 12,
60
+ // minutes: 7,
61
+ // seconds: 2,
62
+ // milliseconds: 555,
63
+ // },
64
+ // ],
65
+ ["json", '{"key":"value"}', { key: "value" }],
66
+ ["jsonb", '{"key":"value"}', { key: "value" }],
67
+ // ["point", "(1,2)", { x: 1, y: 2 }],
68
+ ["line", "{1,2,3}", "{1,2,3}"],
69
+ ["lseg", "((1,2),(3,4))", "[(1,2),(3,4)]"],
70
+ ["box", "(3,4),(1,2)", "(3,4),(1,2)"],
71
+ ["polygon", "((1,2),(3,4),(5,6))", "((1,2),(3,4),(5,6))"],
72
+ // ["circle", "<(1,2),3>", { x: 1, y: 2, radius: 3 }],
73
+ ["cidr", "192.168.100.128/25", "192.168.100.128/25"],
74
+ ["inet", "192.168.100.128", "192.168.100.128"],
75
+ ["macaddr", "08:00:2b:01:02:03", "08:00:2b:01:02:03"],
76
+ ["macaddr8", "08:00:2b:01:02:03:04:05", "08:00:2b:01:02:03:04:05"],
77
+ ["bit varying(3)", "101", "101"],
78
+ ["smallint", 32767, 32767],
79
+ ["serial", undefined, 1],
80
+ ["bigserial", undefined, 1n],
81
+ ["smallserial", undefined, 1],
82
+ ["money", 19.99, "$19.99"],
83
+ [
84
+ "timestamp with time zone",
85
+ "2023-01-01 13:45:30+00",
86
+ new Date("2023-01-01T13:45:30Z"),
87
+ ],
88
+ ["time with time zone", "13:45:30+00", "13:45:30+00"],
89
+ ["bytea", Buffer.from("hello"), Buffer.from("hello")],
90
+ ["xml", "<root>test</root>", "<root>test</root>"],
91
+ ["path", "((1,2),(3,4),(5,6))", "((1,2),(3,4),(5,6))"],
92
+ ["tsquery", "'fat' & 'rat'", "'fat' & 'rat'"],
93
+ [
94
+ "tsvector",
95
+ "a fat cat sat on a mat",
96
+ "'a' 'cat' 'fat' 'mat' 'on' 'sat'",
97
+ ],
98
+ ["int4range", "[1,4)", "[1,4)"],
99
+ ["int8range", "[1,9223372036854775807)", "[1,9223372036854775807)"],
100
+ ["numrange", "[1.123,4.456)", "[1.123,4.456)"],
101
+ [
102
+ "tsrange",
103
+ "[2023-01-01 00:00:00,2023-01-02 00:00:00)",
104
+ `["2023-01-01 00:00:00","2023-01-02 00:00:00")`,
105
+ ],
106
+ [
107
+ "tstzrange",
108
+ "[2023-01-01 00:00:00+00,2023-01-02 00:00:00+00)",
109
+ `["2023-01-01 00:00:00+00","2023-01-02 00:00:00+00")`,
110
+ ],
111
+ ["daterange", "[2023-01-01,2023-01-02)", "[2023-01-01,2023-01-02)"],
112
+ ["int2vector", "1 2 3", "1 2 3"],
113
+ ["oid", "1234", 1234],
114
+ ["pg_lsn", "0/1234567", "0/1234567"],
115
+ ["regclass", "pg_class", "pg_class"],
116
+ ["regnamespace", "pg_catalog", "pg_catalog"],
117
+ ["regproc", "now", "now"],
118
+ ["regprocedure", "sum(integer)", "sum(integer)"],
119
+ ["regrole", "orm", "orm"],
120
+ ["regtype", "integer", "integer"],
121
+ ["tid", "(0,1)", "(0,1)"],
122
+ ["xid", "1234", "1234"],
123
+ ["txid_snapshot", "10:20:10,14,15", "10:20:10,14,15"],
124
+ [
125
+ "text[][][]",
126
+ "{{{a,b},{c,d}},{{e,f},{g,h}}}",
127
+ [
128
+ [
129
+ ["a", "b"],
130
+ ["c", "d"],
131
+ ],
132
+ [
133
+ ["e", "f"],
134
+ ["g", "h"],
135
+ ],
136
+ ],
137
+ ],
138
+ ])("%s columns", async ([datatype, value, expected = value]) => {
139
+ await db.query("BEGIN TRANSACTION");
140
+ await db.query(`CREATE TABLE foo (value ${datatype})`);
141
+ if (datatype.endsWith("serial")) {
142
+ await db.query("INSERT INTO foo DEFAULT VALUES");
143
+ }
144
+ else {
145
+ await db.query(`INSERT INTO foo (value) VALUES ($1::${datatype})`, [
146
+ value,
147
+ ]);
148
+ }
149
+ const result = await db.query(`SELECT * FROM foo`);
150
+ const parsed = defaultZodSchema(datatype).parse(result.rows[0]?.value);
151
+ expect(parsed).toEqual(expected);
152
+ });
153
+ });
@@ -0,0 +1,2 @@
1
+ import { PopulatedModelDefinition } from "#types/PopulatedModelDefinition.js";
2
+ export declare const getColumns: (model: PopulatedModelDefinition, fields: string[]) => string[];
@@ -0,0 +1,8 @@
1
+ export const getColumns = (model, fields) => {
2
+ return fields.map((f) => {
3
+ if (!model.fields[f]) {
4
+ throw new Error(`Field "${f}" not found in model "${model.name}"`);
5
+ }
6
+ return model.fields[f].column;
7
+ });
8
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,63 @@
1
+ import { snakeCase } from "es-toolkit";
2
+ import { describe, expect, test } from "vitest";
3
+ import { getColumns } from "./getColumns.js";
4
+ import { populateModels } from "./populateModels.js";
5
+ describe("getColumns", () => {
6
+ test("returns column names for given fields", () => {
7
+ const models = populateModels({
8
+ naming: { column: snakeCase },
9
+ models: {
10
+ user: {
11
+ fields: {
12
+ id: { type: "serial", primaryKey: true },
13
+ fullName: { type: "text" },
14
+ emailAddress: { type: "text" },
15
+ },
16
+ },
17
+ },
18
+ });
19
+ expect(getColumns(models["user"], ["id", "fullName", "emailAddress"])).toEqual(["id", "full_name", "email_address"]);
20
+ });
21
+ test("throws error for non-existent field", () => {
22
+ const models = populateModels({
23
+ naming: { column: snakeCase },
24
+ models: {
25
+ user: {
26
+ fields: {
27
+ id: { type: "serial", primaryKey: true },
28
+ },
29
+ },
30
+ },
31
+ });
32
+ expect(() => getColumns(models["user"], ["id", "nonexistent"])).toThrow('Field "nonexistent" not found in model "user"');
33
+ });
34
+ test("returns empty array for empty fields array", () => {
35
+ const models = populateModels({
36
+ naming: { column: snakeCase },
37
+ models: {
38
+ user: {
39
+ fields: {
40
+ id: { type: "serial", primaryKey: true },
41
+ },
42
+ },
43
+ },
44
+ });
45
+ expect(getColumns(models["user"], [])).toEqual([]);
46
+ });
47
+ test("handles custom column names", () => {
48
+ const models = populateModels({
49
+ models: {
50
+ user: {
51
+ fields: {
52
+ id: { type: "serial", primaryKey: true },
53
+ name: { type: "text", column: "user_full_name" },
54
+ },
55
+ },
56
+ },
57
+ });
58
+ expect(getColumns(models["user"], ["id", "name"])).toEqual([
59
+ "id",
60
+ "user_full_name",
61
+ ]);
62
+ });
63
+ });
@@ -0,0 +1,3 @@
1
+ import { Config } from "@casekit/orm2-schema";
2
+ import { NormalizedConfig } from "#types/NormalizedConfig.js";
3
+ export declare const normalizeConfig: (config: Config) => NormalizedConfig;
@@ -0,0 +1,20 @@
1
+ import { identity, mapValues } from "es-toolkit";
2
+ import { normalizeModel } from "./normalizeModel.js";
3
+ import { populateModels } from "./populateModels.js";
4
+ export const normalizeConfig = (config) => {
5
+ const models = populateModels(config);
6
+ return {
7
+ ...config,
8
+ schema: config.schema ?? "public",
9
+ models: mapValues(models, (model) => normalizeModel(models, model)),
10
+ operators: config.operators ?? { where: {} },
11
+ extensions: config.extensions ?? [],
12
+ connection: config.connection ?? null,
13
+ pool: config.pool ?? false,
14
+ logger: config.logger ?? console,
15
+ naming: {
16
+ column: config.naming?.column ?? identity,
17
+ table: config.naming?.table ?? identity,
18
+ },
19
+ };
20
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,217 @@
1
+ import { snakeCase } from "es-toolkit";
2
+ import { describe, expect, test } from "vitest";
3
+ import { z } from "zod";
4
+ import { sql } from "@casekit/sql";
5
+ import { normalizeConfig } from "./normalizeConfig.js";
6
+ describe("normalizeConfig", () => {
7
+ test("normalizes minimal config with defaults", () => {
8
+ const config = {
9
+ models: {
10
+ user: {
11
+ fields: {
12
+ id: { type: "serial", primaryKey: true },
13
+ name: { type: "text" },
14
+ },
15
+ },
16
+ },
17
+ };
18
+ const result = normalizeConfig(config);
19
+ expect(result.schema).toBe("public");
20
+ expect(result.operators).toEqual({ where: {} });
21
+ expect(result.extensions).toEqual([]);
22
+ expect(result.connection).toBeNull();
23
+ expect(result.pool).toBe(false);
24
+ expect(result.logger).toBe(console);
25
+ expect(typeof result.naming.column).toBe("function");
26
+ expect(typeof result.naming.table).toBe("function");
27
+ // Test that default identity functions don't transform names
28
+ expect(result.naming.column("userName")).toBe("userName");
29
+ expect(result.naming.table("UserProfile")).toBe("UserProfile");
30
+ });
31
+ test("preserves provided config values", () => {
32
+ const customLogger = {
33
+ debug: () => {
34
+ /* empty */
35
+ },
36
+ info: () => {
37
+ /* empty */
38
+ },
39
+ warn: () => {
40
+ /* empty */
41
+ },
42
+ error: () => {
43
+ /* empty */
44
+ },
45
+ };
46
+ const $contains = Symbol("contains");
47
+ const config = {
48
+ schema: "custom",
49
+ models: {
50
+ user: {
51
+ fields: {
52
+ id: { type: "serial", primaryKey: true },
53
+ },
54
+ },
55
+ },
56
+ operators: {
57
+ where: {
58
+ [$contains]: () => sql ``,
59
+ },
60
+ },
61
+ extensions: ["uuid-ossp"],
62
+ connection: {
63
+ host: "localhost",
64
+ database: "test",
65
+ },
66
+ pool: true,
67
+ logger: customLogger,
68
+ naming: {
69
+ column: snakeCase,
70
+ table: snakeCase,
71
+ },
72
+ };
73
+ const result = normalizeConfig(config);
74
+ expect(result.schema).toBe("custom");
75
+ expect(result.operators).toBe(config.operators);
76
+ expect(result.extensions).toEqual(["uuid-ossp"]);
77
+ expect(result.connection).toBe(config.connection);
78
+ expect(result.pool).toBe(true);
79
+ expect(result.logger).toBe(customLogger);
80
+ expect(result.naming.column).toBe(snakeCase);
81
+ expect(result.naming.table).toBe(snakeCase);
82
+ });
83
+ test("normalizes models with relationships", () => {
84
+ const config = {
85
+ models: {
86
+ user: {
87
+ fields: {
88
+ id: { type: "serial", primaryKey: true },
89
+ email: { type: "text", unique: true },
90
+ },
91
+ relations: {
92
+ posts: {
93
+ type: "1:N",
94
+ model: "post",
95
+ fromField: "id",
96
+ toField: "authorId",
97
+ },
98
+ },
99
+ },
100
+ post: {
101
+ fields: {
102
+ id: { type: "serial", primaryKey: true },
103
+ authorId: { type: "integer" },
104
+ },
105
+ },
106
+ },
107
+ };
108
+ const result = normalizeConfig(config);
109
+ expect(result.models["user"].relations["posts"]).toEqual({
110
+ name: "posts",
111
+ type: "1:N",
112
+ model: "post",
113
+ table: "post",
114
+ from: {
115
+ fields: ["id"],
116
+ columns: ["id"],
117
+ },
118
+ to: {
119
+ fields: ["authorId"],
120
+ columns: ["authorId"],
121
+ },
122
+ });
123
+ });
124
+ test("applies naming functions to all models", () => {
125
+ const config = {
126
+ naming: {
127
+ column: snakeCase,
128
+ table: snakeCase,
129
+ },
130
+ models: {
131
+ userProfile: {
132
+ fields: {
133
+ userId: { type: "serial", primaryKey: true },
134
+ firstName: { type: "text" },
135
+ lastName: { type: "text" },
136
+ },
137
+ },
138
+ orderItem: {
139
+ fields: {
140
+ orderId: { type: "integer" },
141
+ productId: { type: "integer" },
142
+ unitPrice: { type: "numeric" },
143
+ },
144
+ },
145
+ },
146
+ };
147
+ const result = normalizeConfig(config);
148
+ // Check table names
149
+ expect(result.models["userProfile"].table).toBe("user_profile");
150
+ expect(result.models["orderItem"].table).toBe("order_item");
151
+ // Check column names in first model
152
+ expect(result.models["userProfile"].fields["userId"].column).toBe("user_id");
153
+ expect(result.models["userProfile"].fields["firstName"].column).toBe("first_name");
154
+ expect(result.models["userProfile"].fields["lastName"].column).toBe("last_name");
155
+ // Check column names in second model
156
+ expect(result.models["orderItem"].fields["orderId"].column).toBe("order_id");
157
+ expect(result.models["orderItem"].fields["productId"].column).toBe("product_id");
158
+ expect(result.models["orderItem"].fields["unitPrice"].column).toBe("unit_price");
159
+ });
160
+ test("normalizes custom Zod schemas in models", () => {
161
+ const config = {
162
+ models: {
163
+ user: {
164
+ fields: {
165
+ id: { type: "serial", primaryKey: true },
166
+ email: {
167
+ type: "text",
168
+ zodSchema: z.string().email(),
169
+ },
170
+ age: {
171
+ type: "integer",
172
+ zodSchema: z.number().min(0).max(150),
173
+ },
174
+ },
175
+ },
176
+ },
177
+ };
178
+ const result = normalizeConfig(config);
179
+ // Verify that Zod schemas are preserved
180
+ expect(result.models["user"].fields["email"].zodSchema).toBeInstanceOf(z.ZodString);
181
+ expect(result.models["user"].fields["age"].zodSchema).toBeInstanceOf(z.ZodNumber);
182
+ });
183
+ test("handles partial naming configuration", () => {
184
+ const config = {
185
+ naming: {
186
+ column: snakeCase,
187
+ // table naming function omitted
188
+ },
189
+ models: {
190
+ userProfile: {
191
+ fields: {
192
+ userId: { type: "serial", primaryKey: true },
193
+ },
194
+ },
195
+ },
196
+ };
197
+ const result = normalizeConfig(config);
198
+ // Column should be transformed
199
+ expect(result.models["userProfile"].fields["userId"].column).toBe("user_id");
200
+ // Table should remain unchanged due to default identity function
201
+ expect(result.models["userProfile"].table).toBe("userProfile");
202
+ });
203
+ test("handles empty extensions array", () => {
204
+ const config = {
205
+ models: {
206
+ user: {
207
+ fields: {
208
+ id: { type: "serial", primaryKey: true },
209
+ },
210
+ },
211
+ },
212
+ extensions: [],
213
+ };
214
+ const result = normalizeConfig(config);
215
+ expect(result.extensions).toEqual([]);
216
+ });
217
+ });
@@ -0,0 +1,3 @@
1
+ import { NormalizedFieldDefinition } from "#types/NormalizedFieldDefinition.js";
2
+ import { PopulatedFieldDefinition } from "#types/PopulatedFieldDefinition.js";
3
+ export declare const normalizeField: (field: PopulatedFieldDefinition) => NormalizedFieldDefinition;
@@ -0,0 +1,11 @@
1
+ export const normalizeField = (field) => {
2
+ return {
3
+ name: field.name,
4
+ column: field.column,
5
+ type: field.type,
6
+ zodSchema: field.zodSchema,
7
+ nullable: field.nullable,
8
+ default: field.default,
9
+ provided: field.provided,
10
+ };
11
+ };
@@ -0,0 +1 @@
1
+ export {};