@fragno-dev/db 0.0.1 → 0.1.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 (200) hide show
  1. package/.turbo/turbo-build.log +137 -13
  2. package/.turbo/turbo-test.log +36 -0
  3. package/CHANGELOG.md +7 -0
  4. package/dist/adapters/adapters.d.ts +18 -0
  5. package/dist/adapters/adapters.d.ts.map +1 -0
  6. package/dist/adapters/drizzle/drizzle-adapter.d.ts +21 -0
  7. package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -0
  8. package/dist/adapters/drizzle/drizzle-adapter.js +62 -0
  9. package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -0
  10. package/dist/adapters/drizzle/drizzle-query.d.ts +17 -0
  11. package/dist/adapters/drizzle/drizzle-query.d.ts.map +1 -0
  12. package/dist/adapters/drizzle/drizzle-query.js +139 -0
  13. package/dist/adapters/drizzle/drizzle-query.js.map +1 -0
  14. package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts +9 -0
  15. package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts.map +1 -0
  16. package/dist/adapters/drizzle/drizzle-uow-compiler.js +300 -0
  17. package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -0
  18. package/dist/adapters/drizzle/drizzle-uow-decoder.js +82 -0
  19. package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -0
  20. package/dist/adapters/drizzle/drizzle-uow-executor.js +125 -0
  21. package/dist/adapters/drizzle/drizzle-uow-executor.js.map +1 -0
  22. package/dist/adapters/drizzle/generate.js +273 -0
  23. package/dist/adapters/drizzle/generate.js.map +1 -0
  24. package/dist/adapters/drizzle/join-column-utils.js +28 -0
  25. package/dist/adapters/drizzle/join-column-utils.js.map +1 -0
  26. package/dist/adapters/drizzle/shared.js +11 -0
  27. package/dist/adapters/drizzle/shared.js.map +1 -0
  28. package/dist/adapters/kysely/kysely-adapter.d.ts +23 -0
  29. package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -0
  30. package/dist/adapters/kysely/kysely-adapter.js +119 -0
  31. package/dist/adapters/kysely/kysely-adapter.js.map +1 -0
  32. package/dist/adapters/kysely/kysely-query-builder.js +306 -0
  33. package/dist/adapters/kysely/kysely-query-builder.js.map +1 -0
  34. package/dist/adapters/kysely/kysely-query-compiler.js +67 -0
  35. package/dist/adapters/kysely/kysely-query-compiler.js.map +1 -0
  36. package/dist/adapters/kysely/kysely-query.js +158 -0
  37. package/dist/adapters/kysely/kysely-query.js.map +1 -0
  38. package/dist/adapters/kysely/kysely-uow-compiler.js +139 -0
  39. package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -0
  40. package/dist/adapters/kysely/kysely-uow-executor.js +89 -0
  41. package/dist/adapters/kysely/kysely-uow-executor.js.map +1 -0
  42. package/dist/adapters/kysely/migration/execute.js +176 -0
  43. package/dist/adapters/kysely/migration/execute.js.map +1 -0
  44. package/dist/fragment.d.ts +54 -0
  45. package/dist/fragment.d.ts.map +1 -0
  46. package/dist/fragment.js +92 -0
  47. package/dist/fragment.js.map +1 -0
  48. package/dist/id.d.ts +2 -0
  49. package/dist/migration-engine/auto-from-schema.js +116 -0
  50. package/dist/migration-engine/auto-from-schema.js.map +1 -0
  51. package/dist/migration-engine/create.d.ts +41 -0
  52. package/dist/migration-engine/create.d.ts.map +1 -0
  53. package/dist/migration-engine/create.js +58 -0
  54. package/dist/migration-engine/create.js.map +1 -0
  55. package/dist/migration-engine/shared.d.ts +90 -0
  56. package/dist/migration-engine/shared.d.ts.map +1 -0
  57. package/dist/migration-engine/shared.js +8 -0
  58. package/dist/migration-engine/shared.js.map +1 -0
  59. package/dist/mod.d.ts +55 -2
  60. package/dist/mod.d.ts.map +1 -1
  61. package/dist/mod.js +111 -2
  62. package/dist/mod.js.map +1 -1
  63. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column-builder.js +108 -0
  64. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column-builder.js.map +1 -0
  65. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column.js +55 -0
  66. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column.js.map +1 -0
  67. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/entity.js +18 -0
  68. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/entity.js.map +1 -0
  69. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/common.js +183 -0
  70. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/common.js.map +1 -0
  71. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/enum.js +58 -0
  72. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/enum.js.map +1 -0
  73. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/foreign-keys.js +68 -0
  74. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/foreign-keys.js.map +1 -0
  75. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/unique-constraint.js +56 -0
  76. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/unique-constraint.js.map +1 -0
  77. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/utils/array.js +65 -0
  78. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/utils/array.js.map +1 -0
  79. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/conditions.js +81 -0
  80. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/conditions.js.map +1 -0
  81. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/select.js +13 -0
  82. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/select.js.map +1 -0
  83. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/functions/aggregate.js +10 -0
  84. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/functions/aggregate.js.map +1 -0
  85. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/sql.js +372 -0
  86. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/sql.js.map +1 -0
  87. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/subquery.js +23 -0
  88. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/subquery.js.map +1 -0
  89. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.js +62 -0
  90. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.js.map +1 -0
  91. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.utils.js +6 -0
  92. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.utils.js.map +1 -0
  93. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing-utils.js +8 -0
  94. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing-utils.js.map +1 -0
  95. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing.js +8 -0
  96. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing.js.map +1 -0
  97. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/view-common.js +6 -0
  98. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/view-common.js.map +1 -0
  99. package/dist/query/condition-builder.d.ts +41 -0
  100. package/dist/query/condition-builder.d.ts.map +1 -0
  101. package/dist/query/condition-builder.js +93 -0
  102. package/dist/query/condition-builder.js.map +1 -0
  103. package/dist/query/cursor.d.ts +88 -0
  104. package/dist/query/cursor.d.ts.map +1 -0
  105. package/dist/query/cursor.js +103 -0
  106. package/dist/query/cursor.js.map +1 -0
  107. package/dist/query/orm/orm.d.ts +18 -0
  108. package/dist/query/orm/orm.d.ts.map +1 -0
  109. package/dist/query/orm/orm.js +48 -0
  110. package/dist/query/orm/orm.js.map +1 -0
  111. package/dist/query/query.d.ts +79 -0
  112. package/dist/query/query.d.ts.map +1 -0
  113. package/dist/query/query.js +1 -0
  114. package/dist/query/result-transform.js +155 -0
  115. package/dist/query/result-transform.js.map +1 -0
  116. package/dist/query/unit-of-work.d.ts +435 -0
  117. package/dist/query/unit-of-work.d.ts.map +1 -0
  118. package/dist/query/unit-of-work.js +549 -0
  119. package/dist/query/unit-of-work.js.map +1 -0
  120. package/dist/schema/create.d.ts +273 -116
  121. package/dist/schema/create.d.ts.map +1 -1
  122. package/dist/schema/create.js +410 -222
  123. package/dist/schema/create.js.map +1 -1
  124. package/dist/schema/serialize.js +101 -0
  125. package/dist/schema/serialize.js.map +1 -0
  126. package/dist/schema-generator/schema-generator.d.ts +15 -0
  127. package/dist/schema-generator/schema-generator.d.ts.map +1 -0
  128. package/dist/shared/providers.d.ts +6 -0
  129. package/dist/shared/providers.d.ts.map +1 -0
  130. package/dist/util/import-generator.js +26 -0
  131. package/dist/util/import-generator.js.map +1 -0
  132. package/dist/util/parse.js +15 -0
  133. package/dist/util/parse.js.map +1 -0
  134. package/dist/util/types.d.ts +8 -0
  135. package/dist/util/types.d.ts.map +1 -0
  136. package/package.json +63 -2
  137. package/src/adapters/adapters.ts +22 -0
  138. package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +433 -0
  139. package/src/adapters/drizzle/drizzle-adapter.test.ts +122 -0
  140. package/src/adapters/drizzle/drizzle-adapter.ts +118 -0
  141. package/src/adapters/drizzle/drizzle-query.ts +234 -0
  142. package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +1084 -0
  143. package/src/adapters/drizzle/drizzle-uow-compiler.ts +546 -0
  144. package/src/adapters/drizzle/drizzle-uow-decoder.ts +165 -0
  145. package/src/adapters/drizzle/drizzle-uow-executor.ts +213 -0
  146. package/src/adapters/drizzle/generate.test.ts +643 -0
  147. package/src/adapters/drizzle/generate.ts +481 -0
  148. package/src/adapters/drizzle/join-column-utils.test.ts +79 -0
  149. package/src/adapters/drizzle/join-column-utils.ts +39 -0
  150. package/src/adapters/drizzle/migrate-drizzle.test.ts +226 -0
  151. package/src/adapters/drizzle/shared.ts +22 -0
  152. package/src/adapters/drizzle/test-utils.ts +56 -0
  153. package/src/adapters/kysely/kysely-adapter-pglite.test.ts +789 -0
  154. package/src/adapters/kysely/kysely-adapter.ts +196 -0
  155. package/src/adapters/kysely/kysely-query-builder.test.ts +1344 -0
  156. package/src/adapters/kysely/kysely-query-builder.ts +611 -0
  157. package/src/adapters/kysely/kysely-query-compiler.ts +124 -0
  158. package/src/adapters/kysely/kysely-query.ts +254 -0
  159. package/src/adapters/kysely/kysely-uow-compiler.test.ts +916 -0
  160. package/src/adapters/kysely/kysely-uow-compiler.ts +271 -0
  161. package/src/adapters/kysely/kysely-uow-executor.ts +149 -0
  162. package/src/adapters/kysely/kysely-uow-joins.test.ts +811 -0
  163. package/src/adapters/kysely/migration/execute-mysql.test.ts +1173 -0
  164. package/src/adapters/kysely/migration/execute-postgres.test.ts +2657 -0
  165. package/src/adapters/kysely/migration/execute.ts +382 -0
  166. package/src/adapters/kysely/migration/kysely-migrator.test.ts +197 -0
  167. package/src/fragment.test.ts +287 -0
  168. package/src/fragment.ts +198 -0
  169. package/src/migration-engine/auto-from-schema.test.ts +118 -58
  170. package/src/migration-engine/auto-from-schema.ts +103 -32
  171. package/src/migration-engine/create.test.ts +34 -46
  172. package/src/migration-engine/create.ts +41 -26
  173. package/src/migration-engine/shared.ts +26 -6
  174. package/src/mod.ts +197 -1
  175. package/src/query/condition-builder.test.ts +379 -0
  176. package/src/query/condition-builder.ts +294 -0
  177. package/src/query/cursor.test.ts +296 -0
  178. package/src/query/cursor.ts +147 -0
  179. package/src/query/orm/orm.ts +92 -0
  180. package/src/query/query-type.test.ts +429 -0
  181. package/src/query/query.ts +200 -0
  182. package/src/query/result-transform.test.ts +795 -0
  183. package/src/query/result-transform.ts +247 -0
  184. package/src/query/unit-of-work-types.test.ts +192 -0
  185. package/src/query/unit-of-work.test.ts +947 -0
  186. package/src/query/unit-of-work.ts +1199 -0
  187. package/src/schema/create.test.ts +653 -110
  188. package/src/schema/create.ts +708 -337
  189. package/src/schema/serialize.test.ts +559 -0
  190. package/src/schema/serialize.ts +359 -0
  191. package/src/schema-generator/schema-generator.ts +12 -0
  192. package/src/shared/config.ts +0 -8
  193. package/src/util/import-generator.ts +28 -0
  194. package/src/util/parse.ts +16 -0
  195. package/src/util/types.ts +4 -0
  196. package/tsconfig.json +1 -1
  197. package/tsdown.config.ts +11 -1
  198. package/vitest.config.ts +3 -0
  199. /package/dist/{cuid.js → id.js} +0 -0
  200. /package/src/{cuid.ts → id.ts} +0 -0
@@ -0,0 +1,643 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { column, idColumn, referenceColumn, schema } from "../../schema/create";
3
+ import { generateSchema } from "./generate";
4
+
5
+ describe("generateSchema", () => {
6
+ const testSchema = schema((s) => {
7
+ return s
8
+ .addTable("users", (t) => {
9
+ return t
10
+ .addColumn("id", idColumn())
11
+ .addColumn("name", column("string"))
12
+ .addColumn("email", column("string"))
13
+ .addColumn("age", column("integer").nullable())
14
+ .createIndex("idx_email", ["email"], { unique: true })
15
+ .createIndex("idx_name", ["name"]);
16
+ })
17
+ .addTable("posts", (t) => {
18
+ return t
19
+ .addColumn("id", idColumn())
20
+ .addColumn("title", column("string"))
21
+ .addColumn("content", column("string"))
22
+ .addColumn("userId", referenceColumn())
23
+ .addColumn("viewCount", column("integer").defaultTo(0))
24
+ .createIndex("idx_user", ["userId"])
25
+ .createIndex("idx_title", ["title"]);
26
+ })
27
+ .addReference("author", {
28
+ type: "one",
29
+ from: { table: "posts", column: "userId" },
30
+ to: { table: "users", column: "id" },
31
+ });
32
+ });
33
+
34
+ describe("postgresql", () => {
35
+ it("should generate PostgreSQL schema", () => {
36
+ const generated = generateSchema(testSchema, "postgresql");
37
+ expect(generated).toMatchInlineSnapshot(`
38
+ "import { pgTable, varchar, text, integer, bigserial, uniqueIndex, index, bigint, foreignKey } from "drizzle-orm/pg-core"
39
+ import { createId } from "@fragno-dev/db/id"
40
+ import { relations } from "drizzle-orm"
41
+
42
+ export const users = pgTable("users", {
43
+ id: varchar("id", { length: 30 }).notNull().$defaultFn(() => createId()),
44
+ name: text("name").notNull(),
45
+ email: text("email").notNull(),
46
+ age: integer("age"),
47
+ _internalId: bigserial("_internalId", { mode: "number" }).primaryKey().notNull(),
48
+ _version: integer("_version").notNull().default(0)
49
+ }, (table) => [
50
+ uniqueIndex("idx_email").on(table.email),
51
+ index("idx_name").on(table.name)
52
+ ])
53
+
54
+ export const posts = pgTable("posts", {
55
+ id: varchar("id", { length: 30 }).notNull().$defaultFn(() => createId()),
56
+ title: text("title").notNull(),
57
+ content: text("content").notNull(),
58
+ userId: bigint("userId", { mode: "number" }).notNull(),
59
+ viewCount: integer("viewCount").notNull().default(0),
60
+ _internalId: bigserial("_internalId", { mode: "number" }).primaryKey().notNull(),
61
+ _version: integer("_version").notNull().default(0)
62
+ }, (table) => [
63
+ foreignKey({
64
+ columns: [table.userId],
65
+ foreignColumns: [users._internalId],
66
+ name: "posts_users_author_fk"
67
+ }),
68
+ index("idx_user").on(table.userId),
69
+ index("idx_title").on(table.title)
70
+ ])
71
+
72
+ export const postsRelations = relations(posts, ({ one }) => ({
73
+ author: one(users, {
74
+ relationName: "posts_users",
75
+ fields: [posts.userId],
76
+ references: [users._internalId]
77
+ })
78
+ }));"
79
+ `);
80
+ });
81
+ });
82
+
83
+ describe("mysql", () => {
84
+ it("should generate MySQL schema", () => {
85
+ const generated = generateSchema(testSchema, "mysql");
86
+ expect(generated).toMatchInlineSnapshot(`
87
+ "import { mysqlTable, varchar, text, integer, bigint, uniqueIndex, index, foreignKey } from "drizzle-orm/mysql-core"
88
+ import { createId } from "@fragno-dev/db/id"
89
+ import { relations } from "drizzle-orm"
90
+
91
+ export const users = mysqlTable("users", {
92
+ id: varchar("id", { length: 30 }).notNull().$defaultFn(() => createId()),
93
+ name: text("name").notNull(),
94
+ email: text("email").notNull(),
95
+ age: integer("age"),
96
+ _internalId: bigint("_internalId").primaryKey().autoincrement().notNull(),
97
+ _version: integer("_version").notNull().default(0)
98
+ }, (table) => [
99
+ uniqueIndex("idx_email").on(table.email),
100
+ index("idx_name").on(table.name)
101
+ ])
102
+
103
+ export const posts = mysqlTable("posts", {
104
+ id: varchar("id", { length: 30 }).notNull().$defaultFn(() => createId()),
105
+ title: text("title").notNull(),
106
+ content: text("content").notNull(),
107
+ userId: bigint("userId").notNull(),
108
+ viewCount: integer("viewCount").notNull().default(0),
109
+ _internalId: bigint("_internalId").primaryKey().autoincrement().notNull(),
110
+ _version: integer("_version").notNull().default(0)
111
+ }, (table) => [
112
+ foreignKey({
113
+ columns: [table.userId],
114
+ foreignColumns: [users._internalId],
115
+ name: "posts_users_author_fk"
116
+ }),
117
+ index("idx_user").on(table.userId),
118
+ index("idx_title").on(table.title)
119
+ ])
120
+
121
+ export const postsRelations = relations(posts, ({ one }) => ({
122
+ author: one(users, {
123
+ relationName: "posts_users",
124
+ fields: [posts.userId],
125
+ references: [users._internalId]
126
+ })
127
+ }));"
128
+ `);
129
+ });
130
+ });
131
+
132
+ describe("sqlite", () => {
133
+ it("should generate SQLite schema", () => {
134
+ const generated = generateSchema(testSchema, "sqlite");
135
+ expect(generated).toMatchInlineSnapshot(`
136
+ "import { sqliteTable, text, integer, uniqueIndex, index, blob, foreignKey } from "drizzle-orm/sqlite-core"
137
+ import { createId } from "@fragno-dev/db/id"
138
+ import { relations } from "drizzle-orm"
139
+
140
+ export const users = sqliteTable("users", {
141
+ id: text("id").notNull().$defaultFn(() => createId()),
142
+ name: text("name").notNull(),
143
+ email: text("email").notNull(),
144
+ age: integer("age"),
145
+ _internalId: integer("_internalId").primaryKey().autoincrement().notNull(),
146
+ _version: integer("_version").notNull().default(0)
147
+ }, (table) => [
148
+ uniqueIndex("idx_email").on(table.email),
149
+ index("idx_name").on(table.name)
150
+ ])
151
+
152
+ export const posts = sqliteTable("posts", {
153
+ id: text("id").notNull().$defaultFn(() => createId()),
154
+ title: text("title").notNull(),
155
+ content: text("content").notNull(),
156
+ userId: blob("userId", { mode: "bigint" }).notNull(),
157
+ viewCount: integer("viewCount").notNull().default(0),
158
+ _internalId: integer("_internalId").primaryKey().autoincrement().notNull(),
159
+ _version: integer("_version").notNull().default(0)
160
+ }, (table) => [
161
+ foreignKey({
162
+ columns: [table.userId],
163
+ foreignColumns: [users._internalId],
164
+ name: "posts_users_author_fk"
165
+ }),
166
+ index("idx_user").on(table.userId),
167
+ index("idx_title").on(table.title)
168
+ ])
169
+
170
+ export const postsRelations = relations(posts, ({ one }) => ({
171
+ author: one(users, {
172
+ relationName: "posts_users",
173
+ fields: [posts.userId],
174
+ references: [users._internalId]
175
+ })
176
+ }));"
177
+ `);
178
+ });
179
+ });
180
+
181
+ describe("default values", () => {
182
+ it("should handle runtime default values", () => {
183
+ const timestampSchema = schema((s) => {
184
+ return s.addTable("events", (t) => {
185
+ return t
186
+ .addColumn("id", idColumn())
187
+ .addColumn("createdAt", column("timestamp").defaultTo$("now"));
188
+ });
189
+ });
190
+
191
+ const generated = generateSchema(timestampSchema, "postgresql");
192
+ expect(generated).toMatchInlineSnapshot(`
193
+ "import { pgTable, varchar, timestamp, bigserial, integer } from "drizzle-orm/pg-core"
194
+ import { createId } from "@fragno-dev/db/id"
195
+
196
+ export const events = pgTable("events", {
197
+ id: varchar("id", { length: 30 }).notNull().$defaultFn(() => createId()),
198
+ createdAt: timestamp("createdAt").notNull().defaultNow(),
199
+ _internalId: bigserial("_internalId", { mode: "number" }).primaryKey().notNull(),
200
+ _version: integer("_version").notNull().default(0)
201
+ })"
202
+ `);
203
+ });
204
+ });
205
+
206
+ describe("binary columns", () => {
207
+ it("should generate custom type for binary columns", () => {
208
+ const binarySchema = schema((s) => {
209
+ return s.addTable("files", (t) => {
210
+ return t.addColumn("id", idColumn()).addColumn("data", column("binary"));
211
+ });
212
+ });
213
+
214
+ const generated = generateSchema(binarySchema, "postgresql");
215
+ expect(generated).toMatchInlineSnapshot(`
216
+ "import { pgTable, varchar, customType, bigserial, integer } from "drizzle-orm/pg-core"
217
+ import { createId } from "@fragno-dev/db/id"
218
+
219
+ const customBinary = customType<
220
+ {
221
+ data: Uint8Array;
222
+ driverData: Buffer;
223
+ }
224
+ >({
225
+ dataType() {
226
+ return "bytea";
227
+ },
228
+ fromDriver(value) {
229
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength)
230
+ },
231
+ toDriver(value) {
232
+ return value instanceof Buffer? value : Buffer.from(value)
233
+ }
234
+ });
235
+
236
+ export const files = pgTable("files", {
237
+ id: varchar("id", { length: 30 }).notNull().$defaultFn(() => createId()),
238
+ data: customBinary("data").notNull(),
239
+ _internalId: bigserial("_internalId", { mode: "number" }).primaryKey().notNull(),
240
+ _version: integer("_version").notNull().default(0)
241
+ })"
242
+ `);
243
+ });
244
+ });
245
+
246
+ describe("many relations", () => {
247
+ const oneToManySchema = schema((s) => {
248
+ return s
249
+ .addTable("users", (t) => {
250
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
251
+ })
252
+ .addTable("posts", (t) => {
253
+ return t
254
+ .addColumn("id", idColumn())
255
+ .addColumn("title", column("string"))
256
+ .addColumn("userId", referenceColumn())
257
+ .createIndex("idx_user", ["userId"]);
258
+ })
259
+ .addReference("author", {
260
+ type: "one",
261
+ from: { table: "posts", column: "userId" },
262
+ to: { table: "users", column: "id" },
263
+ })
264
+ .addReference("posts", {
265
+ type: "many",
266
+ from: { table: "users", column: "id" },
267
+ to: { table: "posts", column: "userId" },
268
+ });
269
+ });
270
+
271
+ it("should generate PostgreSQL schema with many relations", () => {
272
+ const generated = generateSchema(oneToManySchema, "postgresql");
273
+ expect(generated).toMatchInlineSnapshot(`
274
+ "import { pgTable, varchar, text, bigserial, integer, bigint, foreignKey, index } from "drizzle-orm/pg-core"
275
+ import { createId } from "@fragno-dev/db/id"
276
+ import { relations } from "drizzle-orm"
277
+
278
+ export const users = pgTable("users", {
279
+ id: varchar("id", { length: 30 }).notNull().$defaultFn(() => createId()),
280
+ name: text("name").notNull(),
281
+ _internalId: bigserial("_internalId", { mode: "number" }).primaryKey().notNull(),
282
+ _version: integer("_version").notNull().default(0)
283
+ })
284
+
285
+ export const usersRelations = relations(users, ({ many }) => ({
286
+ posts: many(posts, {
287
+ relationName: "users_posts"
288
+ })
289
+ }));
290
+
291
+ export const posts = pgTable("posts", {
292
+ id: varchar("id", { length: 30 }).notNull().$defaultFn(() => createId()),
293
+ title: text("title").notNull(),
294
+ userId: bigint("userId", { mode: "number" }).notNull(),
295
+ _internalId: bigserial("_internalId", { mode: "number" }).primaryKey().notNull(),
296
+ _version: integer("_version").notNull().default(0)
297
+ }, (table) => [
298
+ foreignKey({
299
+ columns: [table.userId],
300
+ foreignColumns: [users._internalId],
301
+ name: "posts_users_author_fk"
302
+ }),
303
+ index("idx_user").on(table.userId)
304
+ ])
305
+
306
+ export const postsRelations = relations(posts, ({ one }) => ({
307
+ author: one(users, {
308
+ relationName: "posts_users",
309
+ fields: [posts.userId],
310
+ references: [users._internalId]
311
+ })
312
+ }));"
313
+ `);
314
+ });
315
+
316
+ it("should generate MySQL schema with many relations", () => {
317
+ const generated = generateSchema(oneToManySchema, "mysql");
318
+ expect(generated).toMatchInlineSnapshot(`
319
+ "import { mysqlTable, varchar, text, bigint, integer, foreignKey, index } from "drizzle-orm/mysql-core"
320
+ import { createId } from "@fragno-dev/db/id"
321
+ import { relations } from "drizzle-orm"
322
+
323
+ export const users = mysqlTable("users", {
324
+ id: varchar("id", { length: 30 }).notNull().$defaultFn(() => createId()),
325
+ name: text("name").notNull(),
326
+ _internalId: bigint("_internalId").primaryKey().autoincrement().notNull(),
327
+ _version: integer("_version").notNull().default(0)
328
+ })
329
+
330
+ export const usersRelations = relations(users, ({ many }) => ({
331
+ posts: many(posts, {
332
+ relationName: "users_posts"
333
+ })
334
+ }));
335
+
336
+ export const posts = mysqlTable("posts", {
337
+ id: varchar("id", { length: 30 }).notNull().$defaultFn(() => createId()),
338
+ title: text("title").notNull(),
339
+ userId: bigint("userId").notNull(),
340
+ _internalId: bigint("_internalId").primaryKey().autoincrement().notNull(),
341
+ _version: integer("_version").notNull().default(0)
342
+ }, (table) => [
343
+ foreignKey({
344
+ columns: [table.userId],
345
+ foreignColumns: [users._internalId],
346
+ name: "posts_users_author_fk"
347
+ }),
348
+ index("idx_user").on(table.userId)
349
+ ])
350
+
351
+ export const postsRelations = relations(posts, ({ one }) => ({
352
+ author: one(users, {
353
+ relationName: "posts_users",
354
+ fields: [posts.userId],
355
+ references: [users._internalId]
356
+ })
357
+ }));"
358
+ `);
359
+ });
360
+
361
+ it("should generate SQLite schema with many relations", () => {
362
+ const generated = generateSchema(oneToManySchema, "sqlite");
363
+ expect(generated).toMatchInlineSnapshot(`
364
+ "import { sqliteTable, text, integer, blob, foreignKey, index } from "drizzle-orm/sqlite-core"
365
+ import { createId } from "@fragno-dev/db/id"
366
+ import { relations } from "drizzle-orm"
367
+
368
+ export const users = sqliteTable("users", {
369
+ id: text("id").notNull().$defaultFn(() => createId()),
370
+ name: text("name").notNull(),
371
+ _internalId: integer("_internalId").primaryKey().autoincrement().notNull(),
372
+ _version: integer("_version").notNull().default(0)
373
+ })
374
+
375
+ export const usersRelations = relations(users, ({ many }) => ({
376
+ posts: many(posts, {
377
+ relationName: "users_posts"
378
+ })
379
+ }));
380
+
381
+ export const posts = sqliteTable("posts", {
382
+ id: text("id").notNull().$defaultFn(() => createId()),
383
+ title: text("title").notNull(),
384
+ userId: blob("userId", { mode: "bigint" }).notNull(),
385
+ _internalId: integer("_internalId").primaryKey().autoincrement().notNull(),
386
+ _version: integer("_version").notNull().default(0)
387
+ }, (table) => [
388
+ foreignKey({
389
+ columns: [table.userId],
390
+ foreignColumns: [users._internalId],
391
+ name: "posts_users_author_fk"
392
+ }),
393
+ index("idx_user").on(table.userId)
394
+ ])
395
+
396
+ export const postsRelations = relations(posts, ({ one }) => ({
397
+ author: one(users, {
398
+ relationName: "posts_users",
399
+ fields: [posts.userId],
400
+ references: [users._internalId]
401
+ })
402
+ }));"
403
+ `);
404
+ });
405
+
406
+ it("should handle table with only many relations (no foreign keys)", () => {
407
+ const manyOnlySchema = schema((s) => {
408
+ return s
409
+ .addTable("categories", (t) => {
410
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
411
+ })
412
+ .addTable("products", (t) => {
413
+ return t
414
+ .addColumn("id", idColumn())
415
+ .addColumn("name", column("string"))
416
+ .addColumn("categoryId", referenceColumn());
417
+ })
418
+ .addReference("products", {
419
+ type: "many",
420
+ from: { table: "categories", column: "id" },
421
+ to: { table: "products", column: "categoryId" },
422
+ });
423
+ });
424
+
425
+ const generated = generateSchema(manyOnlySchema, "postgresql");
426
+
427
+ // Categories table should NOT have a constraint callback (no foreign keys, no indexes)
428
+ const categoriesTableMatch = generated.match(
429
+ /export const categories = pgTable\("categories", \{[^}]+\}\)/,
430
+ );
431
+ expect(categoriesTableMatch).toBeTruthy();
432
+
433
+ // Should have relations with many
434
+ expect(generated).toContain(
435
+ "export const categoriesRelations = relations(categories, ({ many }) => ({",
436
+ );
437
+ expect(generated).toContain("products: many(products");
438
+ expect(generated).toMatchInlineSnapshot(`
439
+ "import { pgTable, varchar, text, bigserial, integer, bigint } from "drizzle-orm/pg-core"
440
+ import { createId } from "@fragno-dev/db/id"
441
+ import { relations } from "drizzle-orm"
442
+
443
+ export const categories = pgTable("categories", {
444
+ id: varchar("id", { length: 30 }).notNull().$defaultFn(() => createId()),
445
+ name: text("name").notNull(),
446
+ _internalId: bigserial("_internalId", { mode: "number" }).primaryKey().notNull(),
447
+ _version: integer("_version").notNull().default(0)
448
+ })
449
+
450
+ export const categoriesRelations = relations(categories, ({ many }) => ({
451
+ products: many(products, {
452
+ relationName: "categories_products"
453
+ })
454
+ }));
455
+
456
+ export const products = pgTable("products", {
457
+ id: varchar("id", { length: 30 }).notNull().$defaultFn(() => createId()),
458
+ name: text("name").notNull(),
459
+ categoryId: bigint("categoryId", { mode: "number" }).notNull(),
460
+ _internalId: bigserial("_internalId", { mode: "number" }).primaryKey().notNull(),
461
+ _version: integer("_version").notNull().default(0)
462
+ })"
463
+ `);
464
+ });
465
+
466
+ it("should handle self-referencing many relations", () => {
467
+ const selfManySchema = schema((s) => {
468
+ return s
469
+ .addTable("category", (t) => {
470
+ return t
471
+ .addColumn("id", idColumn())
472
+ .addColumn("name", column("string"))
473
+ .addColumn("parentId", referenceColumn().nullable());
474
+ })
475
+ .addReference("parent", {
476
+ type: "one",
477
+ from: { table: "category", column: "parentId" },
478
+ to: { table: "category", column: "id" },
479
+ })
480
+ .addReference("children", {
481
+ type: "many",
482
+ from: { table: "category", column: "id" },
483
+ to: { table: "category", column: "parentId" },
484
+ });
485
+ });
486
+
487
+ const generated = generateSchema(selfManySchema, "postgresql");
488
+
489
+ // Should have both one and many relations
490
+ expect(generated).toContain("parent: one(category");
491
+ expect(generated).toContain("children: many(category");
492
+
493
+ // Should only have one foreign key (from the "one" relation)
494
+ const fkMatches = generated.match(/foreignKey\(/g);
495
+ expect(fkMatches).toHaveLength(1);
496
+
497
+ expect(generated).toMatchInlineSnapshot(`
498
+ "import { pgTable, varchar, text, bigint, bigserial, integer, foreignKey } from "drizzle-orm/pg-core"
499
+ import { createId } from "@fragno-dev/db/id"
500
+ import { relations } from "drizzle-orm"
501
+
502
+ export const category = pgTable("category", {
503
+ id: varchar("id", { length: 30 }).notNull().$defaultFn(() => createId()),
504
+ name: text("name").notNull(),
505
+ parentId: bigint("parentId", { mode: "number" }),
506
+ _internalId: bigserial("_internalId", { mode: "number" }).primaryKey().notNull(),
507
+ _version: integer("_version").notNull().default(0)
508
+ }, (table) => [
509
+ foreignKey({
510
+ columns: [table.parentId],
511
+ foreignColumns: [table._internalId],
512
+ name: "category_category_parent_fk"
513
+ })
514
+ ])
515
+
516
+ export const categoryRelations = relations(category, ({ one, many }) => ({
517
+ parent: one(category, {
518
+ relationName: "category_category",
519
+ fields: [category.parentId],
520
+ references: [category._internalId]
521
+ }),
522
+ children: many(category, {
523
+ relationName: "category_category"
524
+ })
525
+ }));"
526
+ `);
527
+ });
528
+ });
529
+
530
+ describe("self-referencing foreign keys", () => {
531
+ const selfRefSchema = schema((s) => {
532
+ return s
533
+ .addTable("comment", (t) => {
534
+ return t
535
+ .addColumn("id", idColumn())
536
+ .addColumn("content", column("string"))
537
+ .addColumn("parentId", referenceColumn().nullable())
538
+ .createIndex("idx_parent", ["parentId"]);
539
+ })
540
+ .addReference("parent", {
541
+ type: "one",
542
+ from: { table: "comment", column: "parentId" },
543
+ to: { table: "comment", column: "id" },
544
+ });
545
+ });
546
+
547
+ it("should generate PostgreSQL self-referencing foreign key using table parameter", () => {
548
+ const generated = generateSchema(selfRefSchema, "postgresql");
549
+ expect(generated).toMatchInlineSnapshot(`
550
+ "import { pgTable, varchar, text, bigint, bigserial, integer, foreignKey, index } from "drizzle-orm/pg-core"
551
+ import { createId } from "@fragno-dev/db/id"
552
+ import { relations } from "drizzle-orm"
553
+
554
+ export const comment = pgTable("comment", {
555
+ id: varchar("id", { length: 30 }).notNull().$defaultFn(() => createId()),
556
+ content: text("content").notNull(),
557
+ parentId: bigint("parentId", { mode: "number" }),
558
+ _internalId: bigserial("_internalId", { mode: "number" }).primaryKey().notNull(),
559
+ _version: integer("_version").notNull().default(0)
560
+ }, (table) => [
561
+ foreignKey({
562
+ columns: [table.parentId],
563
+ foreignColumns: [table._internalId],
564
+ name: "comment_comment_parent_fk"
565
+ }),
566
+ index("idx_parent").on(table.parentId)
567
+ ])
568
+
569
+ export const commentRelations = relations(comment, ({ one }) => ({
570
+ parent: one(comment, {
571
+ relationName: "comment_comment",
572
+ fields: [comment.parentId],
573
+ references: [comment._internalId]
574
+ })
575
+ }));"
576
+ `);
577
+ });
578
+
579
+ it("should generate MySQL self-referencing foreign key using table parameter", () => {
580
+ const generated = generateSchema(selfRefSchema, "mysql");
581
+ expect(generated).toMatchInlineSnapshot(`
582
+ "import { mysqlTable, varchar, text, bigint, integer, foreignKey, index } from "drizzle-orm/mysql-core"
583
+ import { createId } from "@fragno-dev/db/id"
584
+ import { relations } from "drizzle-orm"
585
+
586
+ export const comment = mysqlTable("comment", {
587
+ id: varchar("id", { length: 30 }).notNull().$defaultFn(() => createId()),
588
+ content: text("content").notNull(),
589
+ parentId: bigint("parentId"),
590
+ _internalId: bigint("_internalId").primaryKey().autoincrement().notNull(),
591
+ _version: integer("_version").notNull().default(0)
592
+ }, (table) => [
593
+ foreignKey({
594
+ columns: [table.parentId],
595
+ foreignColumns: [table._internalId],
596
+ name: "comment_comment_parent_fk"
597
+ }),
598
+ index("idx_parent").on(table.parentId)
599
+ ])
600
+
601
+ export const commentRelations = relations(comment, ({ one }) => ({
602
+ parent: one(comment, {
603
+ relationName: "comment_comment",
604
+ fields: [comment.parentId],
605
+ references: [comment._internalId]
606
+ })
607
+ }));"
608
+ `);
609
+ });
610
+
611
+ it("should generate SQLite self-referencing foreign key using table parameter", () => {
612
+ const generated = generateSchema(selfRefSchema, "sqlite");
613
+ expect(generated).toMatchInlineSnapshot(`
614
+ "import { sqliteTable, text, blob, integer, foreignKey, index } from "drizzle-orm/sqlite-core"
615
+ import { createId } from "@fragno-dev/db/id"
616
+ import { relations } from "drizzle-orm"
617
+
618
+ export const comment = sqliteTable("comment", {
619
+ id: text("id").notNull().$defaultFn(() => createId()),
620
+ content: text("content").notNull(),
621
+ parentId: blob("parentId", { mode: "bigint" }),
622
+ _internalId: integer("_internalId").primaryKey().autoincrement().notNull(),
623
+ _version: integer("_version").notNull().default(0)
624
+ }, (table) => [
625
+ foreignKey({
626
+ columns: [table.parentId],
627
+ foreignColumns: [table._internalId],
628
+ name: "comment_comment_parent_fk"
629
+ }),
630
+ index("idx_parent").on(table.parentId)
631
+ ])
632
+
633
+ export const commentRelations = relations(comment, ({ one }) => ({
634
+ parent: one(comment, {
635
+ relationName: "comment_comment",
636
+ fields: [comment.parentId],
637
+ references: [comment._internalId]
638
+ })
639
+ }));"
640
+ `);
641
+ });
642
+ });
643
+ });