@casekit/orm2-cli 0.0.0-20250331181319 → 0.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.
Files changed (65) hide show
  1. package/build/cli.js +1 -2
  2. package/build/commands/db-drop/handler.js +0 -3
  3. package/build/commands/db-drop.test.js +1 -0
  4. package/build/commands/db-pull/handler.js +21 -8
  5. package/build/commands/db-pull/options.d.ts +5 -0
  6. package/build/commands/db-pull/options.js +5 -0
  7. package/build/commands/db-pull/util/relationNames.d.ts +3 -0
  8. package/build/commands/db-pull/util/relationNames.js +14 -0
  9. package/build/commands/db-pull/util/relationNames.test.js +61 -0
  10. package/build/commands/db-pull/util/renderDefault.d.ts +3 -0
  11. package/build/commands/db-pull/util/renderDefault.js +46 -0
  12. package/build/commands/db-pull/util/renderDefault.test.d.ts +1 -0
  13. package/build/commands/db-pull/util/renderDefault.test.js +67 -0
  14. package/build/commands/db-pull/util/renderFieldDefinition.d.ts +2 -0
  15. package/build/commands/db-pull/util/renderFieldDefinition.js +50 -0
  16. package/build/commands/db-pull/util/renderFieldDefinition.test.d.ts +1 -0
  17. package/build/commands/db-pull/util/renderFieldDefinition.test.js +182 -0
  18. package/build/commands/db-pull/util/renderModel.basic.test.d.ts +1 -0
  19. package/build/commands/db-pull/util/renderModel.basic.test.js +169 -0
  20. package/build/commands/db-pull/util/renderModel.constraints.test.d.ts +1 -0
  21. package/build/commands/db-pull/util/renderModel.constraints.test.js +677 -0
  22. package/build/commands/db-pull/util/renderModel.d.ts +2 -0
  23. package/build/commands/db-pull/util/renderModel.defaultValues.test.d.ts +1 -0
  24. package/build/commands/db-pull/util/renderModel.defaultValues.test.js +518 -0
  25. package/build/commands/db-pull/util/renderModel.js +88 -0
  26. package/build/commands/db-pull/util/renderModel.relations.test.d.ts +1 -0
  27. package/build/commands/db-pull/util/renderModel.relations.test.js +880 -0
  28. package/build/commands/db-pull/util/renderModel.types.test.d.ts +1 -0
  29. package/build/commands/db-pull/util/renderModel.types.test.js +703 -0
  30. package/build/commands/db-pull/util/renderRelations.d.ts +2 -0
  31. package/build/commands/db-pull/util/renderRelations.js +55 -0
  32. package/build/commands/db-pull/util/renderRelations.test.d.ts +1 -0
  33. package/build/commands/db-pull/util/renderRelations.test.js +165 -0
  34. package/build/commands/db-pull/util/renderType.d.ts +6 -0
  35. package/build/commands/db-pull/util/renderType.js +55 -0
  36. package/build/commands/db-pull/util/renderType.test.d.ts +1 -0
  37. package/build/commands/db-pull/util/renderType.test.js +46 -0
  38. package/build/commands/db-pull.d.ts +10 -0
  39. package/build/commands/db-pull.test.js +625 -0
  40. package/build/commands/db-push/handler.js +0 -3
  41. package/build/commands/init/handler.js +16 -3
  42. package/build/commands/init/util/generateConfigFile.js +2 -3
  43. package/build/commands/init/util/generateDbFile.js +13 -24
  44. package/build/commands/init/util/generateModelsFile.d.ts +1 -1
  45. package/build/commands/init/util/generateModelsFile.js +6 -2
  46. package/build/commands/init.test.js +19 -31
  47. package/build/types.d.ts +2 -2
  48. package/build/util/createOrOverwriteFile.d.ts +1 -1
  49. package/build/util/createOrOverwriteFile.js +1 -1
  50. package/build/util/loadConfig.js +5 -1
  51. package/package.json +26 -25
  52. package/build/commands/generate-model/handler.d.ts +0 -3
  53. package/build/commands/generate-model/handler.js +0 -10
  54. package/build/commands/generate-model/options.d.ts +0 -18
  55. package/build/commands/generate-model/options.js +0 -18
  56. package/build/commands/generate-model/util/generateModelFile.d.ts +0 -3
  57. package/build/commands/generate-model/util/generateModelFile.js +0 -59
  58. package/build/commands/generate-model/util/generateModelFile.test.js +0 -67
  59. package/build/commands/generate-model/util/regenerateModelsFile.d.ts +0 -2
  60. package/build/commands/generate-model/util/regenerateModelsFile.js +0 -50
  61. package/build/commands/generate-model.d.ts +0 -40
  62. package/build/commands/generate-model.js +0 -8
  63. package/build/commands/generate-model.test.js +0 -55
  64. /package/build/commands/{generate-model/util/generateModelFile.test.d.ts → db-pull/util/relationNames.test.d.ts} +0 -0
  65. /package/build/commands/{generate-model.test.d.ts → db-pull.test.d.ts} +0 -0
package/build/cli.js CHANGED
@@ -3,7 +3,6 @@ import { hideBin } from "yargs/helpers";
3
3
  import { dbDrop } from "#commands/db-drop.js";
4
4
  import { dbPush } from "#commands/db-push.js";
5
5
  import { dbPull } from "./commands/db-pull.js";
6
- import { generateModel } from "./commands/generate-model.js";
7
6
  import { init } from "./commands/init.js";
8
7
  import { globalOptions } from "./options.js";
9
8
  await yargs(hideBin(process.argv))
@@ -11,10 +10,10 @@ await yargs(hideBin(process.argv))
11
10
  .scriptName("orm")
12
11
  .options(globalOptions)
13
12
  .command("db", "Commands for managing your database", (yargs) => yargs.command(dbDrop).command(dbPush).command(dbPull))
14
- .command("generate", "Commands for scaffolding files", (yargs) => yargs.command(generateModel))
15
13
  .command(init)
16
14
  .help()
17
15
  .showHelpOnFail(true)
18
16
  .demandCommand()
19
17
  .recommendCommands()
20
18
  .parse();
19
+ process.exit();
@@ -13,7 +13,4 @@ export const handler = async (opts) => {
13
13
  process.exitCode = 1;
14
14
  throw e;
15
15
  }
16
- finally {
17
- await db.close();
18
- }
19
16
  };
@@ -12,6 +12,7 @@ describe("db drop", () => {
12
12
  afterEach(async () => {
13
13
  await db.end();
14
14
  });
15
+ // TODO: does this actually test anything?
15
16
  test("drops the schema(s) for the models defined in the config", async () => {
16
17
  await yargs().command(dbPush).parseAsync("push");
17
18
  await yargs().command(dbDrop).parseAsync("drop");
@@ -1,20 +1,33 @@
1
+ import { dir } from "console";
2
+ import { camelCase } from "es-toolkit/string";
3
+ import path from "path";
1
4
  import { migrate } from "@casekit/orm2-migrate";
5
+ import { createOrOverwriteFile } from "#util/createOrOverwriteFile.js";
2
6
  import { loadConfig } from "#util/loadConfig.js";
7
+ import { prettify } from "../../util/prettify.js";
8
+ import { generateModelsFile } from "../init/util/generateModelsFile.js";
9
+ import { renderModel } from "./util/renderModel.js";
3
10
  export const handler = async (opts) => {
4
- const { db } = await loadConfig(opts);
5
- const schema = opts.schema.length > 0 ? opts.schema : [db.config.schema];
11
+ const config = await loadConfig(opts);
12
+ const schema = opts.schema.length > 0 ? opts.schema : [config.db.config.schema];
6
13
  try {
14
+ await config.db.connect();
7
15
  console.log("Pulling schema from database");
8
- await db.connect();
9
- const pulledSchemas = await migrate.pull(db, schema);
16
+ const tables = await migrate.pull(config.db, schema);
17
+ console.log(`Found ${tables.length} tables`);
18
+ // Generate model files for each table
19
+ for (const table of tables) {
20
+ console.log(`Generating model for table: ${table.name}`);
21
+ const modelName = camelCase(table.name);
22
+ const modelFile = await renderModel(table, tables);
23
+ await createOrOverwriteFile(`${config.directory}/models/${modelName}.ts`, modelFile, opts.force);
24
+ }
25
+ const modelsFile = generateModelsFile(tables.map((t) => camelCase(t.name)));
26
+ await createOrOverwriteFile(`${dir}/models/index.ts`, await prettify(path.join(process.cwd(), `${dir}/models/index.ts`), modelsFile), opts.force);
10
27
  console.log("✅ Done");
11
- console.log({ pulledSchemas });
12
28
  }
13
29
  catch (e) {
14
30
  console.error("Error pulling schema from database", e);
15
31
  process.exitCode = 1;
16
32
  }
17
- finally {
18
- await db.close();
19
- }
20
33
  };
@@ -5,4 +5,9 @@ export declare const builder: {
5
5
  array: true;
6
6
  default: never[];
7
7
  };
8
+ force: {
9
+ type: "boolean";
10
+ desc: string;
11
+ default: boolean;
12
+ };
8
13
  };
@@ -5,4 +5,9 @@ export const builder = {
5
5
  array: true,
6
6
  default: [],
7
7
  },
8
+ force: {
9
+ type: "boolean",
10
+ desc: "Force overwrite existing model files without prompting",
11
+ default: false,
12
+ },
8
13
  };
@@ -0,0 +1,3 @@
1
+ import type { ForeignKey } from "@casekit/orm2-migrate";
2
+ export declare const guessManyToOneRelationName: (fk: ForeignKey) => string;
3
+ export declare const guessOneToManyRelationName: (fk: ForeignKey) => string;
@@ -0,0 +1,14 @@
1
+ import { camelCase, lowerFirst, pascalCase, upperFirst, } from "es-toolkit/string";
2
+ import pluralize from "pluralize";
3
+ export const guessManyToOneRelationName = (fk) => {
4
+ return lowerFirst(fk.columnsFrom.map((c) => pascalCase(c).replace(/Id$/, "")).join(""));
5
+ };
6
+ export const guessOneToManyRelationName = (fk) => {
7
+ const foreignName = lowerFirst(fk.columnsFrom.map((c) => pascalCase(c).replace(/Id$/, "")).join(""));
8
+ const tableTo = camelCase(pluralize.singular(fk.tableTo));
9
+ const tableFrom = camelCase(pluralize.plural(fk.tableFrom));
10
+ if (tableTo === foreignName) {
11
+ return pluralize.plural(tableFrom);
12
+ }
13
+ return `${foreignName}${upperFirst(pluralize.plural(tableFrom))}`;
14
+ };
@@ -0,0 +1,61 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { guessManyToOneRelationName, guessOneToManyRelationName, } from "./relationNames.js";
3
+ describe("guessManyToOneRelationName", () => {
4
+ const createForeignKey = (columnsFrom, tableFrom, tableTo) => ({
5
+ constraintName: "test_fk",
6
+ schema: "public",
7
+ tableFrom,
8
+ tableTo,
9
+ columnsFrom,
10
+ columnsTo: ["id"],
11
+ });
12
+ it("handles single column foreign keys", () => {
13
+ expect(guessManyToOneRelationName(createForeignKey(["user_id"], "posts", "users"))).toBe("user");
14
+ expect(guessManyToOneRelationName(createForeignKey(["author_id"], "posts", "users"))).toBe("author");
15
+ expect(guessManyToOneRelationName(createForeignKey(["created_by_id"], "posts", "users"))).toBe("createdBy");
16
+ });
17
+ it("handles foreign key columns without _id suffix", () => {
18
+ expect(guessManyToOneRelationName(createForeignKey(["user"], "posts", "users"))).toBe("user");
19
+ expect(guessManyToOneRelationName(createForeignKey(["author"], "posts", "users"))).toBe("author");
20
+ });
21
+ it("handles multi-column foreign keys", () => {
22
+ expect(guessManyToOneRelationName(createForeignKey(["tenant_id", "user_id"], "posts", "users"))).toBe("tenantUser");
23
+ expect(guessManyToOneRelationName(createForeignKey(["org_id", "dept_id", "user_id"], "posts", "users"))).toBe("orgDeptUser");
24
+ });
25
+ it("converts snake_case to camelCase", () => {
26
+ expect(guessManyToOneRelationName(createForeignKey(["parent_category_id"], "categories", "categories"))).toBe("parentCategory");
27
+ expect(guessManyToOneRelationName(createForeignKey(["primary_contact_id"], "accounts", "contacts"))).toBe("primaryContact");
28
+ });
29
+ });
30
+ describe("guessOneToManyRelationName", () => {
31
+ const createForeignKey = (columnsFrom, tableFrom, tableTo) => ({
32
+ constraintName: "test_fk",
33
+ schema: "public",
34
+ tableFrom,
35
+ tableTo,
36
+ columnsFrom,
37
+ columnsTo: ["id"],
38
+ });
39
+ it("handles standard one-to-many relationships", () => {
40
+ expect(guessOneToManyRelationName(createForeignKey(["user_id"], "posts", "users"))).toBe("posts");
41
+ expect(guessOneToManyRelationName(createForeignKey(["author_id"], "posts", "users"))).toBe("authorPosts");
42
+ expect(guessOneToManyRelationName(createForeignKey(["category_id"], "posts", "categories"))).toBe("posts");
43
+ });
44
+ it("handles self-referential relationships", () => {
45
+ expect(guessOneToManyRelationName(createForeignKey(["parent_id"], "categories", "categories"))).toBe("parentCategories");
46
+ expect(guessOneToManyRelationName(createForeignKey(["manager_id"], "users", "users"))).toBe("managerUsers");
47
+ });
48
+ it("handles multi-column foreign keys", () => {
49
+ expect(guessOneToManyRelationName(createForeignKey(["tenant_id", "user_id"], "posts", "users"))).toBe("tenantUserPosts");
50
+ expect(guessOneToManyRelationName(createForeignKey(["org_id", "dept_id"], "employees", "departments"))).toBe("orgDeptEmployees");
51
+ });
52
+ it("pluralizes table names correctly", () => {
53
+ expect(guessOneToManyRelationName(createForeignKey(["user_id"], "comment", "users"))).toBe("comments");
54
+ expect(guessOneToManyRelationName(createForeignKey(["user_id"], "reply", "users"))).toBe("replies");
55
+ expect(guessOneToManyRelationName(createForeignKey(["user_id"], "child", "users"))).toBe("children");
56
+ });
57
+ it("converts snake_case to camelCase and adds proper spacing", () => {
58
+ expect(guessOneToManyRelationName(createForeignKey(["created_by_id"], "posts", "users"))).toBe("createdByPosts");
59
+ expect(guessOneToManyRelationName(createForeignKey(["assigned_to_id"], "tasks", "users"))).toBe("assignedToTasks");
60
+ });
61
+ });
@@ -0,0 +1,3 @@
1
+ export declare const isNumeric: (value: string) => boolean;
2
+ export declare const renderDefaultValue: (type: string, d: string | null) => string | boolean | null;
3
+ export declare const renderDefault: (type: string, d: string | null) => string;
@@ -0,0 +1,46 @@
1
+ export const isNumeric = (value) => {
2
+ return /^-?\d+(\.\d+)?$/.test(value);
3
+ };
4
+ export const renderDefaultValue = (type, d) => {
5
+ if (d === null) {
6
+ return null;
7
+ }
8
+ else if (type === "bigint" && isNumeric(d)) {
9
+ return `"${d.replace(/^'(.*)'::bigint$/, "$1")}"`;
10
+ }
11
+ else if (isNumeric(d)) {
12
+ return d;
13
+ }
14
+ else if (d.toLowerCase() === "true") {
15
+ return true;
16
+ }
17
+ else if (d.toLowerCase() === "false") {
18
+ return false;
19
+ }
20
+ else if (/^'.*'$/.test(d)) {
21
+ return `"${d.replace(/^'(.*)'$/, "$1")}"`;
22
+ }
23
+ else if (/NULL::.*/.test(d)) {
24
+ return null;
25
+ }
26
+ else if (/'.*'::text$/.test(d)) {
27
+ return d.replace(/^'(.*)'::text$/, '"$1"');
28
+ }
29
+ else if (/'.*'::numeric/.test(d)) {
30
+ return d.replace(/^'(.*)'::numeric$/, "$1");
31
+ }
32
+ else if (/.*::numeric$/.test(d)) {
33
+ return d.replace(/^(.*)::numeric$/, "$1");
34
+ }
35
+ else {
36
+ return `sql\`${d}\``;
37
+ }
38
+ };
39
+ export const renderDefault = (type, d) => {
40
+ if (type.endsWith("SERIAL"))
41
+ return "";
42
+ const value = renderDefaultValue(type, d);
43
+ if (value === null)
44
+ return "";
45
+ return `default: ${value},`;
46
+ };
@@ -0,0 +1,67 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { isNumeric, renderDefault } from "./renderDefault.js";
3
+ describe("isNumeric", () => {
4
+ test("should return true for integers", () => {
5
+ expect(isNumeric("42")).toBe(true);
6
+ expect(isNumeric("-123")).toBe(true);
7
+ expect(isNumeric("0")).toBe(true);
8
+ });
9
+ test("should return true for decimal numbers", () => {
10
+ expect(isNumeric("3.14")).toBe(true);
11
+ expect(isNumeric("-0.5")).toBe(true);
12
+ expect(isNumeric("0.0")).toBe(true);
13
+ });
14
+ test("should return false for non-numeric strings", () => {
15
+ expect(isNumeric("abc")).toBe(false);
16
+ expect(isNumeric("12.34.56")).toBe(false);
17
+ expect(isNumeric("12abc")).toBe(false);
18
+ expect(isNumeric("")).toBe(false);
19
+ expect(isNumeric(" ")).toBe(false);
20
+ });
21
+ });
22
+ describe("renderDefault", () => {
23
+ test("should return empty string for SERIAL types", () => {
24
+ expect(renderDefault("SERIAL", "1")).toBe("");
25
+ expect(renderDefault("BIGSERIAL", "1")).toBe("");
26
+ });
27
+ test("should return empty string for null values", () => {
28
+ expect(renderDefault("TEXT", null)).toBe("");
29
+ });
30
+ test("should handle numeric defaults", () => {
31
+ expect(renderDefault("INTEGER", "42")).toBe("default: 42,");
32
+ expect(renderDefault("DECIMAL", "-1.5")).toBe("default: -1.5,");
33
+ });
34
+ test("should handle boolean defaults", () => {
35
+ expect(renderDefault("BOOLEAN", "true")).toBe("default: true,");
36
+ expect(renderDefault("BOOLEAN", "false")).toBe("default: false,");
37
+ expect(renderDefault("BOOLEAN", "TRUE")).toBe("default: true,");
38
+ expect(renderDefault("BOOLEAN", "FALSE")).toBe("default: false,");
39
+ });
40
+ test("should handle text defaults", () => {
41
+ expect(renderDefault("TEXT", "'hello'::text")).toBe('default: "hello",');
42
+ });
43
+ test("should handle numeric with type specifier", () => {
44
+ expect(renderDefault("NUMERIC", "'123'::numeric")).toBe("default: 123,");
45
+ expect(renderDefault("NUMERIC", "456::numeric")).toBe("default: 456,");
46
+ });
47
+ test("should wrap functions in sql tag", () => {
48
+ expect(renderDefault("TIMESTAMP", "now()")).toBe("default: sql`now()`,");
49
+ expect(renderDefault("UUID", "gen_random_uuid()")).toBe("default: sql`gen_random_uuid()`,");
50
+ });
51
+ test("should handle array defaults", () => {
52
+ expect(renderDefault("INTEGER[]", "ARRAY[1, 2, 3]")).toBe("default: sql`ARRAY[1, 2, 3]`,");
53
+ expect(renderDefault("TEXT[]", "ARRAY['foo', 'bar']")).toBe("default: sql`ARRAY['foo', 'bar']`,");
54
+ });
55
+ test("should handle JSON defaults", () => {
56
+ expect(renderDefault("JSONB", `'{"key": "value"}'::jsonb`)).toBe(`default: sql\`'{"key": "value"}'::jsonb\`,`);
57
+ expect(renderDefault("JSON", "'[]'::json")).toBe("default: sql`'[]'::json`,");
58
+ });
59
+ test("should handle enum defaults", () => {
60
+ expect(renderDefault("mood", "'happy'::mood")).toBe("default: sql`'happy'::mood`,");
61
+ expect(renderDefault("user_role", "'admin'::user_role")).toBe("default: sql`'admin'::user_role`,");
62
+ });
63
+ test("should handle compound defaults", () => {
64
+ expect(renderDefault("point", "'(0,0)'::point")).toBe("default: sql`'(0,0)'::point`,");
65
+ expect(renderDefault("interval", "'1 day'::interval")).toBe("default: sql`'1 day'::interval`,");
66
+ });
67
+ });
@@ -0,0 +1,2 @@
1
+ import type { Column, Table } from "@casekit/orm2-migrate";
2
+ export declare const renderFieldDefinition: (column: Column, table: Table) => string;
@@ -0,0 +1,50 @@
1
+ import { camelCase } from "es-toolkit/string";
2
+ import { renderDefaultValue } from "./renderDefault.js";
3
+ import { renderType } from "./renderType.js";
4
+ export const renderFieldDefinition = (column, table) => {
5
+ const parts = [];
6
+ // Column name if different from field name
7
+ const fieldName = camelCase(column.column);
8
+ if (fieldName !== column.column) {
9
+ parts.push(`column: "${column.column}"`);
10
+ }
11
+ // Type
12
+ parts.push(`type: "${renderType(column)}"`);
13
+ // Primary key (single column)
14
+ const isPrimaryKey = table.primaryKey &&
15
+ table.primaryKey.columns.length === 1 &&
16
+ table.primaryKey.columns[0] === column.column;
17
+ if (isPrimaryKey) {
18
+ parts.push("primaryKey: true");
19
+ }
20
+ // Unique constraint (single column)
21
+ const uniqueConstraint = table.uniqueConstraints.find((uc) => uc.definition.includes(`(${column.column})`) ||
22
+ uc.definition.includes(`("${column.column}")`));
23
+ if (uniqueConstraint) {
24
+ const hasNullsNotDistinct = uniqueConstraint.definition.includes("NULLS NOT DISTINCT");
25
+ if (hasNullsNotDistinct) {
26
+ parts.push("unique: { nullsNotDistinct: true }");
27
+ }
28
+ else {
29
+ parts.push("unique: true");
30
+ }
31
+ }
32
+ // Nullable
33
+ if (column.nullable) {
34
+ parts.push("nullable: true");
35
+ }
36
+ // Default (skip for SERIAL columns as they handle their own default)
37
+ if (column.default && !column.isSerial) {
38
+ const defaultValue = renderDefaultValue(column.type, column.default);
39
+ if (defaultValue !== null) {
40
+ parts.push(`default: ${defaultValue}`);
41
+ }
42
+ }
43
+ // Foreign key reference (single column)
44
+ const foreignKey = table.foreignKeys.find((fk) => fk.columnsFrom.length === 1 && fk.columnsFrom[0] === column.column);
45
+ if (foreignKey) {
46
+ const toField = foreignKey.columnsTo[0];
47
+ parts.push(`references: { model: "${camelCase(foreignKey.tableTo)}", field: "${camelCase(toField)}" }`);
48
+ }
49
+ return `{ ${parts.join(", ")} }`;
50
+ };
@@ -0,0 +1,182 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { renderFieldDefinition } from "./renderFieldDefinition.js";
3
+ describe("renderFieldDefinition", () => {
4
+ const createColumn = (overrides) => ({
5
+ schema: "public",
6
+ table: "test",
7
+ column: "test_column",
8
+ ordinalPosition: 1,
9
+ type: "text",
10
+ default: null,
11
+ nullable: false,
12
+ udtSchema: "pg_catalog",
13
+ udt: "text",
14
+ elementType: null,
15
+ elementTypeSchema: null,
16
+ cardinality: 0,
17
+ size: null,
18
+ isSerial: false,
19
+ ...overrides,
20
+ });
21
+ const createTable = (overrides = {}) => ({
22
+ schema: "public",
23
+ name: "test",
24
+ columns: [],
25
+ primaryKey: null,
26
+ uniqueConstraints: [],
27
+ foreignKeys: [],
28
+ ...overrides,
29
+ });
30
+ it("renders basic field definition", () => {
31
+ const column = createColumn({ column: "name", type: "text" });
32
+ const table = createTable();
33
+ expect(renderFieldDefinition(column, table)).toBe('{ type: "text" }');
34
+ });
35
+ it("handles nullable fields", () => {
36
+ const column = createColumn({
37
+ column: "email",
38
+ type: "text",
39
+ nullable: true,
40
+ });
41
+ const table = createTable();
42
+ expect(renderFieldDefinition(column, table)).toBe('{ type: "text", nullable: true }');
43
+ });
44
+ it("handles default values", () => {
45
+ const column = createColumn({
46
+ column: "status",
47
+ type: "text",
48
+ default: "'active'::text",
49
+ });
50
+ const table = createTable();
51
+ expect(renderFieldDefinition(column, table)).toBe('{ type: "text", default: "active" }');
52
+ });
53
+ it("marks single-column primary keys", () => {
54
+ const column = createColumn({ column: "id", type: "integer" });
55
+ const table = createTable({
56
+ primaryKey: {
57
+ schema: "public",
58
+ table: "test",
59
+ constraintName: "test_pkey",
60
+ columns: ["id"],
61
+ },
62
+ });
63
+ expect(renderFieldDefinition(column, table)).toBe('{ type: "integer", primaryKey: true }');
64
+ });
65
+ it("doesn't mark primary key for multi-column primary keys", () => {
66
+ const column = createColumn({ column: "id", type: "integer" });
67
+ const table = createTable({
68
+ primaryKey: {
69
+ schema: "public",
70
+ table: "test",
71
+ constraintName: "test_pkey",
72
+ columns: ["id", "tenant_id"],
73
+ },
74
+ });
75
+ expect(renderFieldDefinition(column, table)).toBe('{ type: "integer" }');
76
+ });
77
+ it("marks single-column unique constraints", () => {
78
+ const column = createColumn({ column: "email", type: "text" });
79
+ const table = createTable({
80
+ uniqueConstraints: [
81
+ {
82
+ schema: "public",
83
+ table: "test",
84
+ name: "test_email_key",
85
+ definition: "UNIQUE (email)",
86
+ },
87
+ ],
88
+ });
89
+ expect(renderFieldDefinition(column, table)).toBe('{ type: "text", unique: true }');
90
+ });
91
+ it("handles unique constraints with NULLS NOT DISTINCT", () => {
92
+ const column = createColumn({ column: "email", type: "text" });
93
+ const table = createTable({
94
+ uniqueConstraints: [
95
+ {
96
+ schema: "public",
97
+ table: "test",
98
+ name: "test_email_key",
99
+ definition: "UNIQUE NULLS NOT DISTINCT (email)",
100
+ },
101
+ ],
102
+ });
103
+ expect(renderFieldDefinition(column, table)).toBe('{ type: "text", unique: { nullsNotDistinct: true } }');
104
+ });
105
+ it("handles single-column foreign key references", () => {
106
+ const column = createColumn({ column: "user_id", type: "integer" });
107
+ const table = createTable({
108
+ foreignKeys: [
109
+ {
110
+ constraintName: "test_user_id_fkey",
111
+ schema: "public",
112
+ tableFrom: "test",
113
+ tableTo: "users",
114
+ columnsFrom: ["user_id"],
115
+ columnsTo: ["id"],
116
+ },
117
+ ],
118
+ });
119
+ expect(renderFieldDefinition(column, table)).toBe('{ column: "user_id", type: "integer", references: { model: "users", field: "id" } }');
120
+ });
121
+ it("handles foreign key references to non-id fields", () => {
122
+ const column = createColumn({ column: "email", type: "text" });
123
+ const table = createTable({
124
+ foreignKeys: [
125
+ {
126
+ constraintName: "test_email_fkey",
127
+ schema: "public",
128
+ tableFrom: "test",
129
+ tableTo: "users",
130
+ columnsFrom: ["email"],
131
+ columnsTo: ["email"],
132
+ },
133
+ ],
134
+ });
135
+ expect(renderFieldDefinition(column, table)).toBe('{ type: "text", references: { model: "users", field: "email" } }');
136
+ });
137
+ it("doesn't add references for multi-column foreign keys", () => {
138
+ const column = createColumn({ column: "user_id", type: "integer" });
139
+ const table = createTable({
140
+ foreignKeys: [
141
+ {
142
+ constraintName: "test_user_tenant_fkey",
143
+ schema: "public",
144
+ tableFrom: "test",
145
+ tableTo: "users",
146
+ columnsFrom: ["user_id", "tenant_id"],
147
+ columnsTo: ["id", "tenant_id"],
148
+ },
149
+ ],
150
+ });
151
+ expect(renderFieldDefinition(column, table)).toBe('{ column: "user_id", type: "integer" }');
152
+ });
153
+ it("handles complex field with multiple attributes", () => {
154
+ const column = createColumn({
155
+ column: "email",
156
+ type: "varchar",
157
+ nullable: true,
158
+ default: "'default@example.com'::text",
159
+ });
160
+ const table = createTable({
161
+ uniqueConstraints: [
162
+ {
163
+ schema: "public",
164
+ table: "test",
165
+ name: "test_email_key",
166
+ definition: 'UNIQUE ("email")',
167
+ },
168
+ ],
169
+ });
170
+ expect(renderFieldDefinition(column, table)).toBe('{ type: "varchar", unique: true, nullable: true, default: "default@example.com" }');
171
+ });
172
+ it("handles array types correctly", () => {
173
+ const column = createColumn({
174
+ column: "tags",
175
+ type: "ARRAY",
176
+ elementType: "text",
177
+ cardinality: 1,
178
+ });
179
+ const table = createTable();
180
+ expect(renderFieldDefinition(column, table)).toBe('{ type: "text[]" }');
181
+ });
182
+ });