@fragno-dev/db 0.1.1 → 0.1.2
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 +61 -53
- package/CHANGELOG.md +12 -0
- package/dist/adapters/adapters.d.ts +11 -1
- package/dist/adapters/adapters.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.d.ts +9 -2
- package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.js +21 -39
- package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-query.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-query.js +3 -2
- package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.js +8 -6
- package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-executor.js.map +1 -1
- package/dist/adapters/drizzle/generate.js +107 -34
- package/dist/adapters/drizzle/generate.js.map +1 -1
- package/dist/adapters/drizzle/shared.js +14 -1
- package/dist/adapters/drizzle/shared.js.map +1 -1
- package/dist/adapters/kysely/kysely-adapter.d.ts +2 -1
- package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
- package/dist/adapters/kysely/kysely-adapter.js +25 -30
- package/dist/adapters/kysely/kysely-adapter.js.map +1 -1
- package/dist/adapters/kysely/kysely-query-builder.js +48 -44
- package/dist/adapters/kysely/kysely-query-builder.js.map +1 -1
- package/dist/adapters/kysely/kysely-query-compiler.js +2 -2
- package/dist/adapters/kysely/kysely-query-compiler.js.map +1 -1
- package/dist/adapters/kysely/kysely-query.js +3 -2
- package/dist/adapters/kysely/kysely-query.js.map +1 -1
- package/dist/adapters/kysely/kysely-shared.js +18 -0
- package/dist/adapters/kysely/kysely-shared.js.map +1 -0
- package/dist/adapters/kysely/kysely-uow-compiler.js +4 -3
- package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -1
- package/dist/adapters/kysely/migration/execute.js +15 -12
- package/dist/adapters/kysely/migration/execute.js.map +1 -1
- package/dist/migration-engine/auto-from-schema.js +2 -8
- package/dist/migration-engine/auto-from-schema.js.map +1 -1
- package/dist/migration-engine/create.d.ts +1 -5
- package/dist/migration-engine/create.js +1 -1
- package/dist/migration-engine/create.js.map +1 -1
- package/dist/migration-engine/generation-engine.d.ts +51 -0
- package/dist/migration-engine/generation-engine.d.ts.map +1 -0
- package/dist/migration-engine/generation-engine.js +165 -0
- package/dist/migration-engine/generation-engine.js.map +1 -0
- package/dist/migration-engine/shared.d.ts +5 -2
- package/dist/migration-engine/shared.d.ts.map +1 -1
- package/dist/migration-engine/shared.js.map +1 -1
- package/dist/mod.d.ts +0 -8
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +0 -32
- package/dist/mod.js.map +1 -1
- package/dist/query/condition-builder.js.map +1 -1
- package/dist/query/result-transform.js +2 -1
- package/dist/query/result-transform.js.map +1 -1
- package/dist/schema/create.d.ts +74 -16
- package/dist/schema/create.d.ts.map +1 -1
- package/dist/schema/create.js +76 -11
- package/dist/schema/create.js.map +1 -1
- package/dist/schema/serialize.js.map +1 -1
- package/dist/shared/settings-schema.js +36 -0
- package/dist/shared/settings-schema.js.map +1 -0
- package/dist/util/import-generator.js.map +1 -1
- package/dist/util/parse.js.map +1 -1
- package/package.json +8 -2
- package/src/adapters/adapters.ts +10 -3
- package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +11 -7
- package/src/adapters/drizzle/drizzle-adapter.test.ts +77 -29
- package/src/adapters/drizzle/drizzle-adapter.ts +31 -78
- package/src/adapters/drizzle/drizzle-query.ts +4 -7
- package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +9 -3
- package/src/adapters/drizzle/drizzle-uow-compiler.ts +12 -6
- package/src/adapters/drizzle/drizzle-uow-decoder.ts +1 -1
- package/src/adapters/drizzle/drizzle-uow-executor.ts +1 -1
- package/src/adapters/drizzle/generate.test.ts +573 -150
- package/src/adapters/drizzle/generate.ts +187 -36
- package/src/adapters/drizzle/migrate-drizzle.test.ts +30 -6
- package/src/adapters/drizzle/shared.ts +31 -1
- package/src/adapters/drizzle/test-utils.ts +3 -1
- package/src/adapters/kysely/kysely-adapter-pglite.test.ts +25 -27
- package/src/adapters/kysely/kysely-adapter.ts +35 -58
- package/src/adapters/kysely/kysely-query-builder.ts +75 -44
- package/src/adapters/kysely/kysely-query-compiler.ts +3 -1
- package/src/adapters/kysely/kysely-query.ts +8 -2
- package/src/adapters/kysely/kysely-shared.ts +23 -0
- package/src/adapters/kysely/kysely-uow-compiler.ts +5 -2
- package/src/adapters/kysely/migration/execute-mysql.test.ts +2 -2
- package/src/adapters/kysely/migration/execute-postgres.test.ts +19 -19
- package/src/adapters/kysely/migration/execute.ts +48 -17
- package/src/adapters/kysely/migration/kysely-migrator.test.ts +19 -37
- package/src/fragment.test.ts +1 -0
- package/src/migration-engine/auto-from-schema.ts +14 -18
- package/src/migration-engine/create.ts +1 -6
- package/src/migration-engine/generation-engine.test.ts +597 -0
- package/src/migration-engine/generation-engine.ts +356 -0
- package/src/migration-engine/shared.ts +1 -4
- package/src/mod.ts +0 -66
- package/src/query/condition-builder.ts +24 -8
- package/src/query/result-transform.ts +7 -1
- package/src/schema/create.test.ts +4 -1
- package/src/schema/create.ts +132 -24
- package/src/schema/serialize.ts +21 -7
- package/src/shared/settings-schema.ts +61 -0
- package/src/util/deep-equal.ts +21 -7
- package/src/util/import-generator.ts +3 -1
- package/src/util/parse.ts +3 -1
- package/tsdown.config.ts +1 -0
- package/.turbo/turbo-test.log +0 -37
- package/.turbo/turbo-types$colon$check.log +0 -1
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import type { FragnoDatabase } from "../mod";
|
|
2
|
+
import type { AnySchema } from "../schema/create";
|
|
3
|
+
import type { PreparedMigration } from "./create";
|
|
4
|
+
import {
|
|
5
|
+
settingsSchema,
|
|
6
|
+
SETTINGS_NAMESPACE,
|
|
7
|
+
createSettingsManager,
|
|
8
|
+
} from "../shared/settings-schema";
|
|
9
|
+
|
|
10
|
+
export interface GenerationEngineResult {
|
|
11
|
+
schema: string;
|
|
12
|
+
path: string;
|
|
13
|
+
namespace: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface GenerationInternalResult {
|
|
17
|
+
schema: string;
|
|
18
|
+
path: string;
|
|
19
|
+
namespace: string;
|
|
20
|
+
fromVersion: number;
|
|
21
|
+
toVersion: number;
|
|
22
|
+
preparedMigration?: PreparedMigration;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ExecuteMigrationResult {
|
|
26
|
+
namespace: string;
|
|
27
|
+
didMigrate: boolean;
|
|
28
|
+
fromVersion: number;
|
|
29
|
+
toVersion: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function generateMigrationsOrSchema<
|
|
33
|
+
const TDatabases extends FragnoDatabase<AnySchema>[],
|
|
34
|
+
>(
|
|
35
|
+
databases: TDatabases,
|
|
36
|
+
options?: {
|
|
37
|
+
path?: string;
|
|
38
|
+
toVersion?: number;
|
|
39
|
+
fromVersion?: number;
|
|
40
|
+
},
|
|
41
|
+
): Promise<GenerationEngineResult[]> {
|
|
42
|
+
if (databases.length === 0) {
|
|
43
|
+
throw new Error("No databases provided for schema generation");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const firstDb = databases[0];
|
|
47
|
+
const adapter = firstDb.adapter;
|
|
48
|
+
|
|
49
|
+
// If adapter has createSchemaGenerator, use it for combined generation (e.g., Drizzle)
|
|
50
|
+
if (adapter.createSchemaGenerator) {
|
|
51
|
+
if (options?.toVersion !== undefined || options?.fromVersion !== undefined) {
|
|
52
|
+
console.warn(
|
|
53
|
+
"⚠️ Warning: --from and --to version options are not supported when generating schemas for multiple fragments and will be ignored.",
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const fragments = databases.map((db) => ({
|
|
58
|
+
schema: db.schema,
|
|
59
|
+
namespace: db.namespace,
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
const generator = adapter.createSchemaGenerator(fragments, {
|
|
63
|
+
path: options?.path,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return [
|
|
67
|
+
{
|
|
68
|
+
...generator.generateSchema(),
|
|
69
|
+
namespace: firstDb.namespace,
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Otherwise, use migration engine for individual generation (e.g., Kysely)
|
|
75
|
+
if (!adapter.createMigrationEngine) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
"Adapter does not support migration-based schema generation. Ensure your adapter implements createMigrationEngine.",
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!(await adapter.isConnectionHealthy())) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
"Database connection is not healthy. Please check your database connection and try again.",
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const settingsQueryEngine = adapter.createQueryEngine(settingsSchema, "");
|
|
88
|
+
const settingsManager = createSettingsManager(settingsQueryEngine, SETTINGS_NAMESPACE);
|
|
89
|
+
|
|
90
|
+
let settingsSourceVersion: number;
|
|
91
|
+
try {
|
|
92
|
+
const result = await settingsManager.get("version");
|
|
93
|
+
|
|
94
|
+
if (!result) {
|
|
95
|
+
settingsSourceVersion = 0;
|
|
96
|
+
} else {
|
|
97
|
+
settingsSourceVersion = parseInt(result.value);
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
// We don't really have a way to verify this error happens because the key doesn't exist in the database
|
|
101
|
+
settingsSourceVersion = 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const generatedFiles: GenerationInternalResult[] = [];
|
|
105
|
+
|
|
106
|
+
const settingsMigrator = adapter.createMigrationEngine(settingsSchema, SETTINGS_NAMESPACE);
|
|
107
|
+
const settingsTargetVersion = settingsSchema.version;
|
|
108
|
+
|
|
109
|
+
// Generate settings table migration
|
|
110
|
+
const settingsMigration = await settingsMigrator.prepareMigrationTo(settingsTargetVersion, {
|
|
111
|
+
fromVersion: settingsSourceVersion,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (!settingsMigration.getSQL) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
"Migration engine does not support SQL generation. Ensure your adapter's migration engine provides getSQL().",
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const settingsSql = settingsMigration.getSQL();
|
|
121
|
+
|
|
122
|
+
if (settingsSql.trim()) {
|
|
123
|
+
generatedFiles.push({
|
|
124
|
+
schema: settingsSql,
|
|
125
|
+
path: "settings-migration.sql", // Placeholder, will be renamed in post-processing
|
|
126
|
+
namespace: SETTINGS_NAMESPACE,
|
|
127
|
+
fromVersion: settingsSourceVersion,
|
|
128
|
+
toVersion: settingsTargetVersion,
|
|
129
|
+
preparedMigration: settingsMigration,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Generate migration for each fragment
|
|
134
|
+
for (const db of databases) {
|
|
135
|
+
const dbAdapter = db.adapter;
|
|
136
|
+
|
|
137
|
+
// Use migration engine
|
|
138
|
+
if (!dbAdapter.createMigrationEngine) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`Adapter for ${db.namespace} does not support schema generation. ` +
|
|
141
|
+
`Ensure your adapter implements either createSchemaGenerator or createMigrationEngine.`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const migrator = dbAdapter.createMigrationEngine(db.schema, db.namespace);
|
|
146
|
+
const targetVersion = options?.toVersion ?? db.schema.version;
|
|
147
|
+
const sourceVersion = options?.fromVersion ?? 0;
|
|
148
|
+
|
|
149
|
+
// Generate migration from source to target version
|
|
150
|
+
const preparedMigration = await migrator.prepareMigrationTo(targetVersion, {
|
|
151
|
+
fromVersion: sourceVersion,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (!preparedMigration.getSQL) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
"Migration engine does not support SQL generation. Ensure your adapter's migration engine provides getSQL().",
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const sql = preparedMigration.getSQL();
|
|
161
|
+
|
|
162
|
+
// If no migrations needed, skip this fragment
|
|
163
|
+
if (sql.trim()) {
|
|
164
|
+
generatedFiles.push({
|
|
165
|
+
schema: sql,
|
|
166
|
+
path: "schema.sql", // Placeholder, will be renamed in post-processing
|
|
167
|
+
namespace: db.namespace,
|
|
168
|
+
fromVersion: sourceVersion,
|
|
169
|
+
toVersion: targetVersion,
|
|
170
|
+
preparedMigration: preparedMigration,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Post-process filenames with ordering
|
|
176
|
+
return postProcessMigrationFilenames(generatedFiles);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Execute migrations for all fragments in the correct order.
|
|
181
|
+
* Migrates settings table first, then fragments alphabetically.
|
|
182
|
+
*
|
|
183
|
+
* @param databases - Array of FragnoDatabase instances to migrate
|
|
184
|
+
* @returns Array of execution results for each migration
|
|
185
|
+
*/
|
|
186
|
+
export async function executeMigrations<const TDatabases extends FragnoDatabase<AnySchema>[]>(
|
|
187
|
+
databases: TDatabases,
|
|
188
|
+
): Promise<ExecuteMigrationResult[]> {
|
|
189
|
+
if (databases.length === 0) {
|
|
190
|
+
throw new Error("No databases provided for migration");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const firstDb = databases[0];
|
|
194
|
+
const adapter = firstDb.adapter;
|
|
195
|
+
|
|
196
|
+
// Validate adapter supports migrations
|
|
197
|
+
if (!adapter.createMigrationEngine) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
"Adapter does not support running migrations. The adapter only supports schema generation.\n" +
|
|
200
|
+
"Try using 'generateMigrationsOrSchema' instead to generate schema files.",
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Validate all use same adapter
|
|
205
|
+
const allSameAdapter = databases.every((db) => db.adapter === adapter);
|
|
206
|
+
if (!allSameAdapter) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
"All fragments must use the same database adapter instance. Mixed adapters are not supported.",
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!(await adapter.isConnectionHealthy())) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
"Database connection is not healthy. Please check your database connection and try again.",
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const results: ExecuteMigrationResult[] = [];
|
|
219
|
+
const migrationsToExecute: Array<{
|
|
220
|
+
namespace: string;
|
|
221
|
+
fromVersion: number;
|
|
222
|
+
toVersion: number;
|
|
223
|
+
preparedMigration: PreparedMigration;
|
|
224
|
+
}> = [];
|
|
225
|
+
|
|
226
|
+
// 1. Prepare settings table migration
|
|
227
|
+
const settingsQueryEngine = adapter.createQueryEngine(settingsSchema, "");
|
|
228
|
+
const settingsManager = createSettingsManager(settingsQueryEngine, SETTINGS_NAMESPACE);
|
|
229
|
+
|
|
230
|
+
let settingsSourceVersion: number;
|
|
231
|
+
try {
|
|
232
|
+
const result = await settingsManager.get("version");
|
|
233
|
+
settingsSourceVersion = result ? parseInt(result.value) : 0;
|
|
234
|
+
} catch {
|
|
235
|
+
settingsSourceVersion = 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const settingsMigrator = adapter.createMigrationEngine(settingsSchema, SETTINGS_NAMESPACE);
|
|
239
|
+
const settingsTargetVersion = settingsSchema.version;
|
|
240
|
+
|
|
241
|
+
if (settingsSourceVersion < settingsTargetVersion) {
|
|
242
|
+
const settingsMigration = await settingsMigrator.prepareMigrationTo(settingsTargetVersion, {
|
|
243
|
+
fromVersion: settingsSourceVersion,
|
|
244
|
+
updateSettings: true,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
if (settingsMigration.operations.length > 0) {
|
|
248
|
+
migrationsToExecute.push({
|
|
249
|
+
namespace: SETTINGS_NAMESPACE,
|
|
250
|
+
fromVersion: settingsSourceVersion,
|
|
251
|
+
toVersion: settingsTargetVersion,
|
|
252
|
+
preparedMigration: settingsMigration,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 2. Prepare fragment migrations (sorted alphabetically)
|
|
258
|
+
const sortedDatabases = [...databases].sort((a, b) => a.namespace.localeCompare(b.namespace));
|
|
259
|
+
|
|
260
|
+
for (const fragnoDb of sortedDatabases) {
|
|
261
|
+
const migrator = adapter.createMigrationEngine(fragnoDb.schema, fragnoDb.namespace);
|
|
262
|
+
const currentVersion = await migrator.getVersion();
|
|
263
|
+
const targetVersion = fragnoDb.schema.version;
|
|
264
|
+
|
|
265
|
+
if (currentVersion < targetVersion) {
|
|
266
|
+
const preparedMigration = await migrator.prepareMigrationTo(targetVersion, {
|
|
267
|
+
updateSettings: true,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
if (preparedMigration.operations.length > 0) {
|
|
271
|
+
migrationsToExecute.push({
|
|
272
|
+
namespace: fragnoDb.namespace,
|
|
273
|
+
fromVersion: currentVersion,
|
|
274
|
+
toVersion: targetVersion,
|
|
275
|
+
preparedMigration: preparedMigration,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 3. Execute all migrations in order
|
|
282
|
+
for (const migration of migrationsToExecute) {
|
|
283
|
+
await migration.preparedMigration.execute();
|
|
284
|
+
results.push({
|
|
285
|
+
namespace: migration.namespace,
|
|
286
|
+
didMigrate: true,
|
|
287
|
+
fromVersion: migration.fromVersion,
|
|
288
|
+
toVersion: migration.toVersion,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// 4. Add skipped migrations (already up-to-date)
|
|
293
|
+
for (const fragnoDb of databases) {
|
|
294
|
+
if (!results.find((r) => r.namespace === fragnoDb.namespace)) {
|
|
295
|
+
results.push({
|
|
296
|
+
namespace: fragnoDb.namespace,
|
|
297
|
+
didMigrate: false,
|
|
298
|
+
fromVersion: fragnoDb.schema.version,
|
|
299
|
+
toVersion: fragnoDb.schema.version,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return results;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Post-processes migration files to add ordering and standardize naming.
|
|
309
|
+
*
|
|
310
|
+
* Sorts files with settings namespace first, then alphabetically by namespace,
|
|
311
|
+
* and assigns ordering numbers. Transforms filenames to format:
|
|
312
|
+
* `<date>_<n>_f<from>_t<to>_<namespace>.sql`
|
|
313
|
+
*
|
|
314
|
+
* @param files - Array of generated migration files with version information
|
|
315
|
+
* @returns Array of files with standardized paths and ordering
|
|
316
|
+
*/
|
|
317
|
+
export function postProcessMigrationFilenames(
|
|
318
|
+
files: GenerationInternalResult[],
|
|
319
|
+
): GenerationEngineResult[] {
|
|
320
|
+
if (files.length === 0) {
|
|
321
|
+
return [];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Sort files: settings namespace first, then alphabetically by namespace
|
|
325
|
+
const sortedFiles = [...files].sort((a, b) => {
|
|
326
|
+
if (a.namespace === SETTINGS_NAMESPACE) {
|
|
327
|
+
return -1;
|
|
328
|
+
}
|
|
329
|
+
if (b.namespace === SETTINGS_NAMESPACE) {
|
|
330
|
+
return 1;
|
|
331
|
+
}
|
|
332
|
+
return a.namespace.localeCompare(b.namespace);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Generate date prefix for filenames
|
|
336
|
+
const date = new Date().toISOString().split("T")[0].replace(/-/g, "");
|
|
337
|
+
|
|
338
|
+
// Rename files with ordering
|
|
339
|
+
return sortedFiles.map((file, index) => {
|
|
340
|
+
const fromVersion = file.fromVersion ?? 0;
|
|
341
|
+
const toVersion = file.toVersion ?? 0;
|
|
342
|
+
|
|
343
|
+
// Create new filename with ordering
|
|
344
|
+
const orderNum = (index + 1).toString().padStart(3, "0");
|
|
345
|
+
const fromPadded = fromVersion.toString().padStart(3, "0");
|
|
346
|
+
const toPadded = toVersion.toString().padStart(3, "0");
|
|
347
|
+
const safeName = file.namespace.replace(/[^a-z0-9-]/gi, "_");
|
|
348
|
+
const newPath = `${date}_${orderNum}_f${fromPadded}_t${toPadded}_${safeName}.sql`;
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
schema: file.schema,
|
|
352
|
+
path: newPath,
|
|
353
|
+
namespace: file.namespace,
|
|
354
|
+
};
|
|
355
|
+
});
|
|
356
|
+
}
|
|
@@ -20,10 +20,7 @@ export interface ColumnInfo {
|
|
|
20
20
|
| `varchar(${number})`;
|
|
21
21
|
isNullable: boolean;
|
|
22
22
|
role: "external-id" | "internal-id" | "version" | "reference" | "regular";
|
|
23
|
-
default?: {
|
|
24
|
-
value?: unknown;
|
|
25
|
-
runtime?: "now" | "auto";
|
|
26
|
-
};
|
|
23
|
+
default?: { value: unknown } | { dbSpecial: "now" } | { runtime: "cuid" | "now" };
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
export type MigrationOperation =
|
package/src/mod.ts
CHANGED
|
@@ -115,72 +115,6 @@ export class FragnoDatabase<const T extends AnySchema> {
|
|
|
115
115
|
get adapter() {
|
|
116
116
|
return this.#adapter;
|
|
117
117
|
}
|
|
118
|
-
|
|
119
|
-
async generateSchema(options?: {
|
|
120
|
-
path?: string;
|
|
121
|
-
toVersion?: number;
|
|
122
|
-
fromVersion?: number;
|
|
123
|
-
}): Promise<{ schema: string; path: string }> {
|
|
124
|
-
const adapter = this.#adapter;
|
|
125
|
-
|
|
126
|
-
if (adapter.createSchemaGenerator) {
|
|
127
|
-
if (options?.toVersion !== undefined || options?.fromVersion !== undefined) {
|
|
128
|
-
console.warn("⚠️ toVersion and fromVersion are not supported for schema generation.");
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const generator = adapter.createSchemaGenerator(this.#schema, this.#namespace);
|
|
132
|
-
const defaultPath = options?.path ?? "schema.ts";
|
|
133
|
-
return generator.generateSchema({
|
|
134
|
-
path: defaultPath,
|
|
135
|
-
toVersion: options?.toVersion,
|
|
136
|
-
fromVersion: options?.fromVersion,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (adapter.createMigrationEngine) {
|
|
141
|
-
const migrator = adapter.createMigrationEngine(this.#schema, this.#namespace);
|
|
142
|
-
const targetVersion = options?.toVersion ?? this.#schema.version;
|
|
143
|
-
const sourceVersion = options?.fromVersion;
|
|
144
|
-
|
|
145
|
-
// Get current version for file naming if not provided
|
|
146
|
-
const currentVersion = sourceVersion ?? (await migrator.getVersion());
|
|
147
|
-
|
|
148
|
-
// Determine the default path using the migrator's getDefaultFileName if available
|
|
149
|
-
const defaultPath =
|
|
150
|
-
options?.path ??
|
|
151
|
-
(migrator.getDefaultFileName
|
|
152
|
-
? migrator.getDefaultFileName(this.#namespace, currentVersion, targetVersion)
|
|
153
|
-
: "schema.sql");
|
|
154
|
-
|
|
155
|
-
// Generate migration from source to target version
|
|
156
|
-
const preparedMigration = await migrator.prepareMigrationTo(targetVersion, {
|
|
157
|
-
updateSettings: true,
|
|
158
|
-
fromVersion: sourceVersion,
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
if (!preparedMigration.getSQL) {
|
|
162
|
-
throw new Error(
|
|
163
|
-
"Migration engine does not support SQL generation. Ensure your adapter's migration engine provides getSQL().",
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const sql = preparedMigration.getSQL();
|
|
168
|
-
|
|
169
|
-
// If no migrations needed, return informative message
|
|
170
|
-
if (!sql.trim()) {
|
|
171
|
-
throw new Error("No migrations needed. Database is already at the target version.");
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return {
|
|
175
|
-
schema: sql,
|
|
176
|
-
path: defaultPath,
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
throw new Error(
|
|
181
|
-
"Adapter does not support schema generation. Ensure your adapter implements either createSchemaGenerator or createMigrationEngine.",
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
118
|
}
|
|
185
119
|
|
|
186
120
|
export function defineFragnoDatabase<const TSchema extends AnySchema>(
|
|
@@ -95,7 +95,9 @@ export function createBuilder<Columns extends Record<string, AnyColumn>>(
|
|
|
95
95
|
if (args.length === 3) {
|
|
96
96
|
const [a, operator, b] = args;
|
|
97
97
|
|
|
98
|
-
if (!operators.includes(operator))
|
|
98
|
+
if (!operators.includes(operator)) {
|
|
99
|
+
throw new Error(`Unsupported operator: ${operator}`);
|
|
100
|
+
}
|
|
99
101
|
|
|
100
102
|
return {
|
|
101
103
|
type: "compare",
|
|
@@ -116,7 +118,9 @@ export function createBuilder<Columns extends Record<string, AnyColumn>>(
|
|
|
116
118
|
builder.isNull = (a) => builder(a, "is", null);
|
|
117
119
|
builder.isNotNull = (a) => builder(a, "is not", null);
|
|
118
120
|
builder.not = (condition) => {
|
|
119
|
-
if (typeof condition === "boolean")
|
|
121
|
+
if (typeof condition === "boolean") {
|
|
122
|
+
return !condition;
|
|
123
|
+
}
|
|
120
124
|
|
|
121
125
|
return {
|
|
122
126
|
type: "not",
|
|
@@ -131,13 +135,19 @@ export function createBuilder<Columns extends Record<string, AnyColumn>>(
|
|
|
131
135
|
} as const;
|
|
132
136
|
|
|
133
137
|
for (const item of conditions) {
|
|
134
|
-
if (item === true)
|
|
135
|
-
|
|
138
|
+
if (item === true) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
if (item === false) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
136
144
|
|
|
137
145
|
out.items.push(item);
|
|
138
146
|
}
|
|
139
147
|
|
|
140
|
-
if (out.items.length === 0)
|
|
148
|
+
if (out.items.length === 0) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
141
151
|
return out;
|
|
142
152
|
};
|
|
143
153
|
|
|
@@ -148,13 +158,19 @@ export function createBuilder<Columns extends Record<string, AnyColumn>>(
|
|
|
148
158
|
} as const;
|
|
149
159
|
|
|
150
160
|
for (const item of conditions) {
|
|
151
|
-
if (item === true)
|
|
152
|
-
|
|
161
|
+
if (item === true) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (item === false) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
153
167
|
|
|
154
168
|
out.items.push(item);
|
|
155
169
|
}
|
|
156
170
|
|
|
157
|
-
if (out.items.length === 0)
|
|
171
|
+
if (out.items.length === 0) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
158
174
|
return out;
|
|
159
175
|
};
|
|
160
176
|
|
|
@@ -51,10 +51,16 @@ export function generateRuntimeDefault(column: AnyColumn): unknown {
|
|
|
51
51
|
return undefined;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
// If it's a database-level special function (defaultTo(b => b.now())), return undefined
|
|
55
|
+
// as the database should handle this via DEFAULT NOW() or equivalent
|
|
56
|
+
if ("dbSpecial" in column.default) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
54
60
|
// Handle runtime defaults (defaultTo$)
|
|
55
61
|
const runtime = column.default.runtime;
|
|
56
62
|
|
|
57
|
-
if (runtime === "
|
|
63
|
+
if (runtime === "cuid") {
|
|
58
64
|
return createId();
|
|
59
65
|
}
|
|
60
66
|
|
|
@@ -66,7 +66,10 @@ describe("create", () => {
|
|
|
66
66
|
return s.addTable("test", (t) => {
|
|
67
67
|
return t
|
|
68
68
|
.addColumn("id", idColumn())
|
|
69
|
-
.addColumn(
|
|
69
|
+
.addColumn(
|
|
70
|
+
"createdAt",
|
|
71
|
+
column("timestamp").defaultTo$((b) => b.now()),
|
|
72
|
+
)
|
|
70
73
|
.addColumn("status", column("string").defaultTo("active"));
|
|
71
74
|
});
|
|
72
75
|
});
|