@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.
- package/.turbo/turbo-build.log +137 -13
- package/.turbo/turbo-test.log +36 -0
- package/CHANGELOG.md +7 -0
- package/dist/adapters/adapters.d.ts +18 -0
- package/dist/adapters/adapters.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle-adapter.d.ts +21 -0
- package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle-adapter.js +62 -0
- package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -0
- package/dist/adapters/drizzle/drizzle-query.d.ts +17 -0
- package/dist/adapters/drizzle/drizzle-query.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle-query.js +139 -0
- package/dist/adapters/drizzle/drizzle-query.js.map +1 -0
- package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts +9 -0
- package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle-uow-compiler.js +300 -0
- package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -0
- package/dist/adapters/drizzle/drizzle-uow-decoder.js +82 -0
- package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -0
- package/dist/adapters/drizzle/drizzle-uow-executor.js +125 -0
- package/dist/adapters/drizzle/drizzle-uow-executor.js.map +1 -0
- package/dist/adapters/drizzle/generate.js +273 -0
- package/dist/adapters/drizzle/generate.js.map +1 -0
- package/dist/adapters/drizzle/join-column-utils.js +28 -0
- package/dist/adapters/drizzle/join-column-utils.js.map +1 -0
- package/dist/adapters/drizzle/shared.js +11 -0
- package/dist/adapters/drizzle/shared.js.map +1 -0
- package/dist/adapters/kysely/kysely-adapter.d.ts +23 -0
- package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -0
- package/dist/adapters/kysely/kysely-adapter.js +119 -0
- package/dist/adapters/kysely/kysely-adapter.js.map +1 -0
- package/dist/adapters/kysely/kysely-query-builder.js +306 -0
- package/dist/adapters/kysely/kysely-query-builder.js.map +1 -0
- package/dist/adapters/kysely/kysely-query-compiler.js +67 -0
- package/dist/adapters/kysely/kysely-query-compiler.js.map +1 -0
- package/dist/adapters/kysely/kysely-query.js +158 -0
- package/dist/adapters/kysely/kysely-query.js.map +1 -0
- package/dist/adapters/kysely/kysely-uow-compiler.js +139 -0
- package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -0
- package/dist/adapters/kysely/kysely-uow-executor.js +89 -0
- package/dist/adapters/kysely/kysely-uow-executor.js.map +1 -0
- package/dist/adapters/kysely/migration/execute.js +176 -0
- package/dist/adapters/kysely/migration/execute.js.map +1 -0
- package/dist/fragment.d.ts +54 -0
- package/dist/fragment.d.ts.map +1 -0
- package/dist/fragment.js +92 -0
- package/dist/fragment.js.map +1 -0
- package/dist/id.d.ts +2 -0
- package/dist/migration-engine/auto-from-schema.js +116 -0
- package/dist/migration-engine/auto-from-schema.js.map +1 -0
- package/dist/migration-engine/create.d.ts +41 -0
- package/dist/migration-engine/create.d.ts.map +1 -0
- package/dist/migration-engine/create.js +58 -0
- package/dist/migration-engine/create.js.map +1 -0
- package/dist/migration-engine/shared.d.ts +90 -0
- package/dist/migration-engine/shared.d.ts.map +1 -0
- package/dist/migration-engine/shared.js +8 -0
- package/dist/migration-engine/shared.js.map +1 -0
- package/dist/mod.d.ts +55 -2
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +111 -2
- package/dist/mod.js.map +1 -1
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column-builder.js +108 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column-builder.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column.js +55 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/entity.js +18 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/entity.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/common.js +183 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/common.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/enum.js +58 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/enum.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/foreign-keys.js +68 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/foreign-keys.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/unique-constraint.js +56 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/unique-constraint.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/utils/array.js +65 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/utils/array.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/conditions.js +81 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/conditions.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/select.js +13 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/select.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/functions/aggregate.js +10 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/functions/aggregate.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/sql.js +372 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/sql.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/subquery.js +23 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/subquery.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.js +62 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.utils.js +6 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.utils.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing-utils.js +8 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing-utils.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing.js +8 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/view-common.js +6 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/view-common.js.map +1 -0
- package/dist/query/condition-builder.d.ts +41 -0
- package/dist/query/condition-builder.d.ts.map +1 -0
- package/dist/query/condition-builder.js +93 -0
- package/dist/query/condition-builder.js.map +1 -0
- package/dist/query/cursor.d.ts +88 -0
- package/dist/query/cursor.d.ts.map +1 -0
- package/dist/query/cursor.js +103 -0
- package/dist/query/cursor.js.map +1 -0
- package/dist/query/orm/orm.d.ts +18 -0
- package/dist/query/orm/orm.d.ts.map +1 -0
- package/dist/query/orm/orm.js +48 -0
- package/dist/query/orm/orm.js.map +1 -0
- package/dist/query/query.d.ts +79 -0
- package/dist/query/query.d.ts.map +1 -0
- package/dist/query/query.js +1 -0
- package/dist/query/result-transform.js +155 -0
- package/dist/query/result-transform.js.map +1 -0
- package/dist/query/unit-of-work.d.ts +435 -0
- package/dist/query/unit-of-work.d.ts.map +1 -0
- package/dist/query/unit-of-work.js +549 -0
- package/dist/query/unit-of-work.js.map +1 -0
- package/dist/schema/create.d.ts +273 -116
- package/dist/schema/create.d.ts.map +1 -1
- package/dist/schema/create.js +410 -222
- package/dist/schema/create.js.map +1 -1
- package/dist/schema/serialize.js +101 -0
- package/dist/schema/serialize.js.map +1 -0
- package/dist/schema-generator/schema-generator.d.ts +15 -0
- package/dist/schema-generator/schema-generator.d.ts.map +1 -0
- package/dist/shared/providers.d.ts +6 -0
- package/dist/shared/providers.d.ts.map +1 -0
- package/dist/util/import-generator.js +26 -0
- package/dist/util/import-generator.js.map +1 -0
- package/dist/util/parse.js +15 -0
- package/dist/util/parse.js.map +1 -0
- package/dist/util/types.d.ts +8 -0
- package/dist/util/types.d.ts.map +1 -0
- package/package.json +63 -2
- package/src/adapters/adapters.ts +22 -0
- package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +433 -0
- package/src/adapters/drizzle/drizzle-adapter.test.ts +122 -0
- package/src/adapters/drizzle/drizzle-adapter.ts +118 -0
- package/src/adapters/drizzle/drizzle-query.ts +234 -0
- package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +1084 -0
- package/src/adapters/drizzle/drizzle-uow-compiler.ts +546 -0
- package/src/adapters/drizzle/drizzle-uow-decoder.ts +165 -0
- package/src/adapters/drizzle/drizzle-uow-executor.ts +213 -0
- package/src/adapters/drizzle/generate.test.ts +643 -0
- package/src/adapters/drizzle/generate.ts +481 -0
- package/src/adapters/drizzle/join-column-utils.test.ts +79 -0
- package/src/adapters/drizzle/join-column-utils.ts +39 -0
- package/src/adapters/drizzle/migrate-drizzle.test.ts +226 -0
- package/src/adapters/drizzle/shared.ts +22 -0
- package/src/adapters/drizzle/test-utils.ts +56 -0
- package/src/adapters/kysely/kysely-adapter-pglite.test.ts +789 -0
- package/src/adapters/kysely/kysely-adapter.ts +196 -0
- package/src/adapters/kysely/kysely-query-builder.test.ts +1344 -0
- package/src/adapters/kysely/kysely-query-builder.ts +611 -0
- package/src/adapters/kysely/kysely-query-compiler.ts +124 -0
- package/src/adapters/kysely/kysely-query.ts +254 -0
- package/src/adapters/kysely/kysely-uow-compiler.test.ts +916 -0
- package/src/adapters/kysely/kysely-uow-compiler.ts +271 -0
- package/src/adapters/kysely/kysely-uow-executor.ts +149 -0
- package/src/adapters/kysely/kysely-uow-joins.test.ts +811 -0
- package/src/adapters/kysely/migration/execute-mysql.test.ts +1173 -0
- package/src/adapters/kysely/migration/execute-postgres.test.ts +2657 -0
- package/src/adapters/kysely/migration/execute.ts +382 -0
- package/src/adapters/kysely/migration/kysely-migrator.test.ts +197 -0
- package/src/fragment.test.ts +287 -0
- package/src/fragment.ts +198 -0
- package/src/migration-engine/auto-from-schema.test.ts +118 -58
- package/src/migration-engine/auto-from-schema.ts +103 -32
- package/src/migration-engine/create.test.ts +34 -46
- package/src/migration-engine/create.ts +41 -26
- package/src/migration-engine/shared.ts +26 -6
- package/src/mod.ts +197 -1
- package/src/query/condition-builder.test.ts +379 -0
- package/src/query/condition-builder.ts +294 -0
- package/src/query/cursor.test.ts +296 -0
- package/src/query/cursor.ts +147 -0
- package/src/query/orm/orm.ts +92 -0
- package/src/query/query-type.test.ts +429 -0
- package/src/query/query.ts +200 -0
- package/src/query/result-transform.test.ts +795 -0
- package/src/query/result-transform.ts +247 -0
- package/src/query/unit-of-work-types.test.ts +192 -0
- package/src/query/unit-of-work.test.ts +947 -0
- package/src/query/unit-of-work.ts +1199 -0
- package/src/schema/create.test.ts +653 -110
- package/src/schema/create.ts +708 -337
- package/src/schema/serialize.test.ts +559 -0
- package/src/schema/serialize.ts +359 -0
- package/src/schema-generator/schema-generator.ts +12 -0
- package/src/shared/config.ts +0 -8
- package/src/util/import-generator.ts +28 -0
- package/src/util/parse.ts +16 -0
- package/src/util/types.ts +4 -0
- package/tsconfig.json +1 -1
- package/tsdown.config.ts +11 -1
- package/vitest.config.ts +3 -0
- /package/dist/{cuid.js → id.js} +0 -0
- /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
|
+
}
|