@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,481 @@
1
+ import { importGenerator } from "../../util/import-generator";
2
+ import { ident, parseVarchar } from "../../util/parse";
3
+ import {
4
+ type AnyColumn,
5
+ type AnySchema,
6
+ type AnyTable,
7
+ InternalIdColumn,
8
+ } from "../../schema/create";
9
+ import type { SQLProvider } from "../../shared/providers";
10
+ import { schemaToDBType, type DBTypeLiteral } from "../../schema/serialize";
11
+
12
+ // ============================================================================
13
+ // PROVIDER CONFIGURATION
14
+ // ============================================================================
15
+
16
+ const PROVIDER_IMPORTS = {
17
+ mysql: "drizzle-orm/mysql-core",
18
+ postgresql: "drizzle-orm/pg-core",
19
+ sqlite: "drizzle-orm/sqlite-core",
20
+ } as const;
21
+
22
+ const PROVIDER_TABLE_FUNCTIONS = {
23
+ mysql: "mysqlTable",
24
+ postgresql: "pgTable",
25
+ sqlite: "sqliteTable",
26
+ } as const;
27
+
28
+ export type SupportedProvider = Exclude<SQLProvider, "cockroachdb" | "mssql">;
29
+
30
+ // ============================================================================
31
+ // CONTEXT
32
+ // ============================================================================
33
+
34
+ interface GeneratorContext {
35
+ provider: SupportedProvider;
36
+ imports: ReturnType<typeof importGenerator>;
37
+ importSource: string;
38
+ generatedCustomTypes: Set<string>;
39
+ idGeneratorImport?: { name: string; from: string };
40
+ }
41
+
42
+ function createContext(
43
+ provider: SupportedProvider,
44
+ idGeneratorImport?: { name: string; from: string },
45
+ ): GeneratorContext {
46
+ return {
47
+ provider,
48
+ imports: importGenerator(),
49
+ importSource: PROVIDER_IMPORTS[provider],
50
+ generatedCustomTypes: new Set<string>(),
51
+ idGeneratorImport,
52
+ };
53
+ }
54
+
55
+ // ============================================================================
56
+ // CUSTOM TYPE GENERATION
57
+ // ============================================================================
58
+
59
+ interface CustomTypeOptions {
60
+ dataType: string;
61
+ driverDataType: string;
62
+ databaseDataType: string;
63
+ fromDriverCode: string;
64
+ toDriverCode: string;
65
+ }
66
+
67
+ function generateCustomType(
68
+ ctx: GeneratorContext,
69
+ name: string,
70
+ options: CustomTypeOptions,
71
+ ): string | undefined {
72
+ if (ctx.generatedCustomTypes.has(name)) {
73
+ return undefined;
74
+ }
75
+
76
+ ctx.imports.addImport("customType", ctx.importSource);
77
+ ctx.generatedCustomTypes.add(name);
78
+
79
+ return `const ${name} = customType<
80
+ {
81
+ data: ${options.dataType};
82
+ driverData: ${options.driverDataType};
83
+ }
84
+ >({
85
+ dataType() {
86
+ return "${options.databaseDataType}";
87
+ },
88
+ fromDriver(value) {
89
+ ${options.fromDriverCode}
90
+ },
91
+ toDriver(value) {
92
+ ${options.toDriverCode}
93
+ }
94
+ });`;
95
+ }
96
+
97
+ function generateBinaryCustomType(ctx: GeneratorContext, customTypes: string[]): string {
98
+ const name = "customBinary";
99
+ const code = generateCustomType(ctx, name, {
100
+ dataType: "Uint8Array",
101
+ driverDataType: "Buffer",
102
+ databaseDataType: schemaToDBType({ type: "binary" }, ctx.provider),
103
+ fromDriverCode: "return new Uint8Array(value.buffer, value.byteOffset, value.byteLength)",
104
+ toDriverCode: `return value instanceof Buffer? value : Buffer.from(value)`,
105
+ });
106
+
107
+ if (code) {
108
+ customTypes.push(code);
109
+ }
110
+ return name;
111
+ }
112
+
113
+ // ============================================================================
114
+ // COLUMN TYPE MAPPING
115
+ // ============================================================================
116
+
117
+ interface ColumnTypeFunction {
118
+ name: string;
119
+ isCustomType?: boolean;
120
+ params?: string[];
121
+ }
122
+
123
+ /**
124
+ * Maps SQL database types to Drizzle function names and parameters.
125
+ * Uses schemaToDBType as the source of truth for type conversion.
126
+ */
127
+ function getColumnTypeFunction(
128
+ ctx: GeneratorContext,
129
+ column: AnyColumn,
130
+ customTypes: string[],
131
+ ): ColumnTypeFunction {
132
+ // Get the canonical database type from schemaToDBType
133
+ const dbType = schemaToDBType(column, ctx.provider);
134
+
135
+ // Map database types to Drizzle function names
136
+ return mapDBTypeToDrizzleFunction(ctx, dbType, column, customTypes);
137
+ }
138
+
139
+ /**
140
+ * Maps a database type string to a Drizzle function name and parameters.
141
+ */
142
+ function mapDBTypeToDrizzleFunction(
143
+ ctx: GeneratorContext,
144
+ dbType: DBTypeLiteral,
145
+ column: AnyColumn,
146
+ customTypes: string[],
147
+ ): ColumnTypeFunction {
148
+ // Handle provider-specific types
149
+ if (ctx.provider === "postgresql") {
150
+ switch (dbType) {
151
+ case "bigserial":
152
+ // bigserial requires a mode parameter in Drizzle
153
+ return { name: "bigserial", params: [`{ mode: "number" }`] };
154
+ case "serial":
155
+ return { name: "serial" };
156
+ case "boolean":
157
+ return { name: "boolean" };
158
+ case "bytea":
159
+ return { name: generateBinaryCustomType(ctx, customTypes), isCustomType: true };
160
+ case "json":
161
+ return { name: "json" };
162
+ case "text":
163
+ return { name: "text" };
164
+ case "bigint":
165
+ return { name: "bigint", params: [`{ mode: "number" }`] };
166
+ default:
167
+ if (dbType.startsWith("varchar(")) {
168
+ const length = parseVarchar(dbType);
169
+ return { name: "varchar", params: [`{ length: ${length} }`] };
170
+ }
171
+ return { name: dbType };
172
+ }
173
+ }
174
+
175
+ if (ctx.provider === "mysql") {
176
+ switch (dbType) {
177
+ case "boolean":
178
+ return { name: "boolean" };
179
+ case "text":
180
+ return { name: "text" };
181
+ case "longblob":
182
+ return { name: generateBinaryCustomType(ctx, customTypes), isCustomType: true };
183
+ case "bigint":
184
+ return { name: "bigint" };
185
+ default:
186
+ if (dbType.startsWith("varchar(")) {
187
+ const length = parseVarchar(dbType);
188
+ return { name: "varchar", params: [`{ length: ${length} }`] };
189
+ }
190
+ return { name: dbType };
191
+ }
192
+ }
193
+
194
+ if (ctx.provider === "sqlite") {
195
+ switch (dbType) {
196
+ case "integer":
197
+ // Need to determine the mode based on the original column type
198
+ if (column.type === "bool") {
199
+ return { name: "integer", params: [`{ mode: "boolean" }`] };
200
+ }
201
+ if (column.type === "timestamp" || column.type === "date") {
202
+ return { name: "integer", params: [`{ mode: "timestamp" }`] };
203
+ }
204
+ return { name: "integer" };
205
+ case "blob":
206
+ // Need to determine the mode based on the original column type
207
+ if (column.type === "bigint") {
208
+ return { name: "blob", params: [`{ mode: "bigint" }`] };
209
+ }
210
+ return { name: generateBinaryCustomType(ctx, customTypes), isCustomType: true };
211
+ case "text":
212
+ // Check if it's JSON
213
+ if (column.type === "json") {
214
+ return { name: "blob", params: [`{ mode: "json" }`] };
215
+ }
216
+ return { name: "text" };
217
+ case "real":
218
+ return { name: "real" };
219
+ default:
220
+ return { name: dbType };
221
+ }
222
+ }
223
+
224
+ // Fallback for other providers
225
+ return { name: dbType };
226
+ }
227
+
228
+ // ============================================================================
229
+ // COLUMN GENERATION
230
+ // ============================================================================
231
+
232
+ function generateColumnDefinition(
233
+ ctx: GeneratorContext,
234
+ column: AnyColumn,
235
+ customTypes: string[],
236
+ ): string {
237
+ const parts: string[] = [];
238
+ const typeFn = getColumnTypeFunction(ctx, column, customTypes);
239
+
240
+ // Column type with parameters
241
+ const params: string[] = [`"${column.name}"`, ...(typeFn.params ?? [])];
242
+ if (!typeFn.isCustomType) {
243
+ ctx.imports.addImport(typeFn.name, ctx.importSource);
244
+ }
245
+ parts.push(`${typeFn.name}(${params.join(", ")})`);
246
+
247
+ // Primary key for internal ID
248
+ if (column instanceof InternalIdColumn || column.role === "internal-id") {
249
+ parts.push("primaryKey()");
250
+
251
+ // Auto-increment based on provider
252
+ // Note: PostgreSQL uses bigserial/serial which handle auto-increment automatically
253
+ if (ctx.provider === "mysql" || ctx.provider === "sqlite") {
254
+ parts.push("autoincrement()");
255
+ }
256
+ }
257
+
258
+ // Nullability
259
+ if (!column.isNullable) {
260
+ parts.push("notNull()");
261
+ }
262
+
263
+ // Default values
264
+ if (column.default) {
265
+ if ("value" in column.default) {
266
+ let value: string;
267
+ if (typeof column.default.value === "bigint") {
268
+ ctx.imports.addImport("sql", "drizzle-orm");
269
+ value = `sql\`${column.default.value.toString()}\``;
270
+ } else {
271
+ value = JSON.stringify(column.default.value);
272
+ }
273
+ parts.push(`default(${value})`);
274
+ } else if (column.default.runtime === "auto") {
275
+ const idGen = ctx.idGeneratorImport ?? { name: "createId", from: "@fragno-dev/db/id" };
276
+ ctx.imports.addImport(idGen.name, idGen.from);
277
+ parts.push(`$defaultFn(() => ${idGen.name}())`);
278
+ } else if (column.default.runtime === "now") {
279
+ parts.push("defaultNow()");
280
+ }
281
+ }
282
+
283
+ return ` ${column.ormName}: ${parts.join(".")}`;
284
+ }
285
+
286
+ function generateAllColumns(
287
+ ctx: GeneratorContext,
288
+ table: AnyTable,
289
+ customTypes: string[],
290
+ ): string[] {
291
+ return Object.values(table.columns).map((column) =>
292
+ generateColumnDefinition(ctx, column, customTypes),
293
+ );
294
+ }
295
+
296
+ // ============================================================================
297
+ // CONSTRAINT GENERATION
298
+ // ============================================================================
299
+
300
+ function generateForeignKeys(ctx: GeneratorContext, table: AnyTable): string[] {
301
+ const keys: string[] = [];
302
+
303
+ for (const relation of Object.values(table.relations)) {
304
+ // Only "one" relations generate foreign keys
305
+ // "many" relations don't have foreign keys (they're on the other side)
306
+ if (relation.type === "many") {
307
+ continue;
308
+ }
309
+
310
+ const columns: string[] = [];
311
+ const foreignColumns: string[] = [];
312
+ const isSelfReference = relation.table.ormName === table.ormName;
313
+
314
+ for (const [localCol, refCol] of relation.on) {
315
+ columns.push(`table.${localCol}`);
316
+ // Foreign keys always reference internal IDs
317
+ const actualRefCol = refCol === "id" ? "_internalId" : refCol;
318
+ // For self-referencing foreign keys, use table parameter instead of table constant
319
+ if (isSelfReference) {
320
+ foreignColumns.push(`table.${actualRefCol}`);
321
+ } else {
322
+ foreignColumns.push(`${relation.table.ormName}.${actualRefCol}`);
323
+ }
324
+ }
325
+
326
+ ctx.imports.addImport("foreignKey", ctx.importSource);
327
+ const fkName = `${table.ormName}_${relation.table.ormName}_${relation.name}_fk`;
328
+
329
+ keys.push(`foreignKey({
330
+ columns: [${columns.join(", ")}],
331
+ foreignColumns: [${foreignColumns.join(", ")}],
332
+ name: "${fkName}"
333
+ })`);
334
+ }
335
+
336
+ return keys;
337
+ }
338
+
339
+ function generateIndexes(ctx: GeneratorContext, table: AnyTable): string[] {
340
+ const indexes: string[] = [];
341
+
342
+ for (const idx of Object.values(table.indexes)) {
343
+ const columns = idx.columns.map((col) => `table.${col.ormName}`).join(", ");
344
+
345
+ if (idx.unique) {
346
+ ctx.imports.addImport("uniqueIndex", ctx.importSource);
347
+ indexes.push(`uniqueIndex("${idx.name}").on(${columns})`);
348
+ } else {
349
+ ctx.imports.addImport("index", ctx.importSource);
350
+ indexes.push(`index("${idx.name}").on(${columns})`);
351
+ }
352
+ }
353
+
354
+ return indexes;
355
+ }
356
+
357
+ function generateTableConstraints(ctx: GeneratorContext, table: AnyTable): string[] {
358
+ return [...generateForeignKeys(ctx, table), ...generateIndexes(ctx, table)];
359
+ }
360
+
361
+ // ============================================================================
362
+ // TABLE GENERATION
363
+ // ============================================================================
364
+
365
+ function generateTable(ctx: GeneratorContext, table: AnyTable, customTypes: string[]): string {
366
+ const tableFn = PROVIDER_TABLE_FUNCTIONS[ctx.provider];
367
+ ctx.imports.addImport(tableFn, ctx.importSource);
368
+
369
+ const columns = generateAllColumns(ctx, table, customTypes);
370
+ const constraints = generateTableConstraints(ctx, table);
371
+
372
+ const args: string[] = [`"${table.ormName}"`, `{\n${columns.join(",\n")}\n}`];
373
+
374
+ if (constraints.length > 0) {
375
+ args.push(`(table) => [\n${ident(constraints.join(",\n"))}\n]`);
376
+ }
377
+
378
+ return `export const ${table.ormName} = ${tableFn}(${args.join(", ")})`;
379
+ }
380
+
381
+ // ============================================================================
382
+ // RELATION GENERATION
383
+ // ============================================================================
384
+
385
+ function generateRelation(ctx: GeneratorContext, table: AnyTable): string | undefined {
386
+ const relations: string[] = [];
387
+ let hasOne = false;
388
+ let hasMany = false;
389
+
390
+ for (const relation of Object.values(table.relations)) {
391
+ const options: string[] = [`relationName: "${relation.id}"`];
392
+
393
+ // Track which relation types are used
394
+ if (relation.type === "one") {
395
+ hasOne = true;
396
+ } else if (relation.type === "many") {
397
+ hasMany = true;
398
+ }
399
+
400
+ // For "one" relations, specify fields and references
401
+ if (relation.type === "one") {
402
+ const fields: string[] = [];
403
+ const references: string[] = [];
404
+
405
+ for (const [left, right] of relation.on) {
406
+ fields.push(`${table.ormName}.${left}`);
407
+ // Relations reference internal IDs
408
+ const actualRight = right === "id" ? "_internalId" : right;
409
+ references.push(`${relation.table.ormName}.${actualRight}`);
410
+ }
411
+
412
+ options.push(`fields: [${fields.join(", ")}]`, `references: [${references.join(", ")}]`);
413
+ }
414
+
415
+ const args: string[] = [relation.table.ormName];
416
+ if (options.length > 0) {
417
+ args.push(`{\n${ident(options.join(",\n"))}\n}`);
418
+ }
419
+
420
+ relations.push(ident(`${relation.name}: ${relation.type}(${args.join(", ")})`));
421
+ }
422
+
423
+ if (relations.length === 0) {
424
+ return undefined;
425
+ }
426
+
427
+ // Only include the relation types that are actually used
428
+ const params: string[] = [];
429
+ if (hasOne) {
430
+ params.push("one");
431
+ }
432
+ if (hasMany) {
433
+ params.push("many");
434
+ }
435
+ const relationParams = params.length > 0 ? `{ ${params.join(", ")} }` : "{}";
436
+
437
+ ctx.imports.addImport("relations", "drizzle-orm");
438
+ return `export const ${table.ormName}Relations = relations(${table.ormName}, (${relationParams}) => ({
439
+ ${relations.join(",\n")}
440
+ }));`;
441
+ }
442
+
443
+ // ============================================================================
444
+ // MAIN GENERATION
445
+ // ============================================================================
446
+
447
+ export interface GenerateSchemaOptions {
448
+ /** Custom ID generator import configuration */
449
+ idGeneratorImport?: {
450
+ /** Function name to import */
451
+ name: string;
452
+ /** Module to import from */
453
+ from: string;
454
+ };
455
+ }
456
+
457
+ export function generateSchema(
458
+ schema: AnySchema,
459
+ provider: SupportedProvider,
460
+ options?: GenerateSchemaOptions,
461
+ ): string {
462
+ const ctx = createContext(provider, options?.idGeneratorImport);
463
+ const customTypes: string[] = [];
464
+ const tables: string[] = [];
465
+
466
+ // Generate tables and collect custom types
467
+ for (const table of Object.values(schema.tables)) {
468
+ // Custom types might be generated during column processing
469
+ const tableCode = generateTable(ctx, table, customTypes);
470
+ tables.push(tableCode);
471
+
472
+ const relationCode = generateRelation(ctx, table);
473
+ if (relationCode) {
474
+ tables.push(relationCode);
475
+ }
476
+ }
477
+
478
+ // Assemble final output
479
+ const lines: string[] = [ctx.imports.format(), ...customTypes, ...tables];
480
+ return lines.join("\n\n");
481
+ }
@@ -0,0 +1,79 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { getOrderedJoinColumns } from "./join-column-utils";
3
+ import { schema, column, idColumn } from "../../schema/create";
4
+
5
+ describe("getOrderedJoinColumns", () => {
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
+ })
15
+ .addTable("posts", (t) => {
16
+ return t
17
+ .addColumn("id", idColumn())
18
+ .addColumn("title", column("string"))
19
+ .addColumn("content", column("string"));
20
+ });
21
+ });
22
+
23
+ const usersTable = testSchema.tables["users"];
24
+ const postsTable = testSchema.tables["posts"];
25
+
26
+ it("should return all columns when select is true", () => {
27
+ const columns = getOrderedJoinColumns(usersTable, true);
28
+
29
+ // Should include all columns including hidden ones (_internalId, _version)
30
+ expect(columns).toEqual(["id", "name", "email", "age", "_internalId", "_version"]);
31
+ });
32
+
33
+ it("should return selected columns plus hidden columns when select is array", () => {
34
+ const columns = getOrderedJoinColumns(usersTable, ["name", "email"]);
35
+
36
+ // Selected columns first, then hidden columns
37
+ expect(columns).toEqual(["name", "email", "_internalId", "_version"]);
38
+ });
39
+
40
+ it("should return only id plus hidden columns when selecting just id", () => {
41
+ const columns = getOrderedJoinColumns(usersTable, ["id"]);
42
+
43
+ expect(columns).toEqual(["id", "_internalId", "_version"]);
44
+ });
45
+
46
+ it("should handle table with no nullable columns", () => {
47
+ const columns = getOrderedJoinColumns(postsTable, true);
48
+
49
+ expect(columns).toEqual(["id", "title", "content", "_internalId", "_version"]);
50
+ });
51
+
52
+ it("should handle selecting specific columns from different table", () => {
53
+ const columns = getOrderedJoinColumns(postsTable, ["title"]);
54
+
55
+ expect(columns).toEqual(["title", "_internalId", "_version"]);
56
+ });
57
+
58
+ it("should deduplicate hidden columns if already selected", () => {
59
+ // This shouldn't happen in practice, but the function should handle it
60
+ const columns = getOrderedJoinColumns(usersTable, ["name", "_internalId"]);
61
+
62
+ // _internalId should not be duplicated
63
+ expect(columns).toEqual(["name", "_internalId", "_version"]);
64
+ });
65
+
66
+ it("should maintain column order as defined in table", () => {
67
+ const columns = getOrderedJoinColumns(usersTable, ["email", "name", "id"]);
68
+
69
+ // Even though we select in different order, should follow selected order
70
+ expect(columns).toEqual(["email", "name", "id", "_internalId", "_version"]);
71
+ });
72
+
73
+ it("should skip unknown column keys", () => {
74
+ const columns = getOrderedJoinColumns(usersTable, ["name", "unknownColumn", "email"]);
75
+
76
+ // Should only include known columns
77
+ expect(columns).toEqual(["name", "email", "_internalId", "_version"]);
78
+ });
79
+ });
@@ -0,0 +1,39 @@
1
+ import type { AnyTable } from "../../schema/create";
2
+
3
+ /**
4
+ * Determine the ordered list of columns for a join selection.
5
+ *
6
+ * This logic is shared between the compiler (which builds the SQL)
7
+ * and the decoder (which maps the result array back to objects).
8
+ * The order MUST match exactly for the decoder to work correctly.
9
+ *
10
+ * @param targetTable - The table being joined
11
+ * @param select - Selection options (true for all columns, or array of column keys)
12
+ * @returns Array of column ORM names in the order they appear in the SQL/result
13
+ */
14
+ export function getOrderedJoinColumns(targetTable: AnyTable, select: true | string[]): string[] {
15
+ const orderedColumns: string[] = [];
16
+
17
+ if (select === true) {
18
+ // All columns selected - iterate in the order they appear in targetTable.columns
19
+ for (const col of Object.values(targetTable.columns)) {
20
+ orderedColumns.push(col.ormName);
21
+ }
22
+ } else {
23
+ // Specific columns selected
24
+ for (const colKey of select) {
25
+ const col = targetTable.columns[colKey];
26
+ if (col) {
27
+ orderedColumns.push(col.ormName);
28
+ }
29
+ }
30
+ // Add hidden columns at the end (for FragnoId construction)
31
+ for (const col of Object.values(targetTable.columns)) {
32
+ if (col && col.isHidden && !orderedColumns.includes(col.ormName)) {
33
+ orderedColumns.push(col.ormName);
34
+ }
35
+ }
36
+ }
37
+
38
+ return orderedColumns;
39
+ }