@holo-js/db 0.3.1 → 0.3.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/dist/index.d.ts +10 -0
- package/dist/index.mjs +86 -5
- package/package.json +6 -6
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ interface DatabaseCapabilities {
|
|
|
19
19
|
jsonLength: boolean;
|
|
20
20
|
schemaQualifiedIdentifiers: boolean;
|
|
21
21
|
nativeUpsert: boolean;
|
|
22
|
+
ddlAddColumnSupport?: boolean;
|
|
22
23
|
ddlAlterSupport: boolean;
|
|
23
24
|
introspection: boolean;
|
|
24
25
|
}
|
|
@@ -355,6 +356,9 @@ interface DriverAdapter {
|
|
|
355
356
|
isDatabaseMissingError?(error: unknown): boolean;
|
|
356
357
|
ensureDatabaseExists?(): Promise<void>;
|
|
357
358
|
runWithTransactionScope?<T>(callback: () => Promise<T>): Promise<T>;
|
|
359
|
+
beforeMigrationTransaction?(): Promise<unknown>;
|
|
360
|
+
validateMigrationTransaction?(): Promise<void>;
|
|
361
|
+
afterMigrationTransaction?(state: unknown): Promise<void>;
|
|
358
362
|
introspect?<TRow extends Record<string, unknown> = Record<string, unknown>>(sql: string, bindings?: readonly unknown[], options?: DatabaseOperationOptions): Promise<DriverQueryResult<TRow>>;
|
|
359
363
|
query<TRow extends Record<string, unknown> = Record<string, unknown>>(sql: string, bindings?: readonly unknown[], options?: DatabaseOperationOptions): Promise<DriverQueryResult<TRow>>;
|
|
360
364
|
execute(sql: string, bindings?: readonly unknown[], options?: DatabaseOperationOptions): Promise<DriverExecutionResult>;
|
|
@@ -3053,11 +3057,13 @@ declare class SchemaService {
|
|
|
3053
3057
|
private parsePostgresIndex;
|
|
3054
3058
|
private unsupportedIntrospectionError;
|
|
3055
3059
|
private assertAlterCapability;
|
|
3060
|
+
private assertAddColumnCapability;
|
|
3056
3061
|
private assertForeignKeyCapability;
|
|
3057
3062
|
private resolveColumnDefinition;
|
|
3058
3063
|
private createDefinedTable;
|
|
3059
3064
|
private assertTableMutationIndexNames;
|
|
3060
3065
|
private executeTableMutation;
|
|
3066
|
+
private rebuildSqliteTableForAlteredColumn;
|
|
3061
3067
|
private buildTableDefinition;
|
|
3062
3068
|
private updateRegisteredTable;
|
|
3063
3069
|
private renameRegisteredTable;
|
|
@@ -3142,6 +3148,7 @@ declare class MigrationService {
|
|
|
3142
3148
|
private ensureTrackingTableForMigration;
|
|
3143
3149
|
rollback(options?: RollbackOptions): Promise<RegisteredMigrationDefinition[]>;
|
|
3144
3150
|
getExecutionPolicy(): MigrationExecutionPolicy;
|
|
3151
|
+
private runMigrationTransaction;
|
|
3145
3152
|
private ensureTrackingTable;
|
|
3146
3153
|
private getRanRecords;
|
|
3147
3154
|
private createContext;
|
|
@@ -3297,6 +3304,9 @@ declare class DeferredDatabaseDriverAdapter implements DriverAdapter {
|
|
|
3297
3304
|
ensureDatabaseExists(): Promise<void>;
|
|
3298
3305
|
isDatabaseMissingError(error: unknown): boolean;
|
|
3299
3306
|
runWithTransactionScope<T>(callback: () => Promise<T>): Promise<T>;
|
|
3307
|
+
beforeMigrationTransaction(): Promise<unknown>;
|
|
3308
|
+
validateMigrationTransaction(): Promise<void>;
|
|
3309
|
+
afterMigrationTransaction(state: unknown): Promise<void>;
|
|
3300
3310
|
introspect<TRow extends Record<string, unknown> = Record<string, unknown>>(sql: string, bindings?: readonly unknown[], options?: DatabaseOperationOptions): Promise<DriverQueryResult<TRow>>;
|
|
3301
3311
|
query<TRow extends Record<string, unknown> = Record<string, unknown>>(sql: string, bindings?: readonly unknown[], options?: DatabaseOperationOptions): Promise<DriverQueryResult<TRow>>;
|
|
3302
3312
|
execute(sql: string, bindings?: readonly unknown[], options?: DatabaseOperationOptions): Promise<DriverExecutionResult>;
|
package/dist/index.mjs
CHANGED
|
@@ -7344,6 +7344,14 @@ var SchemaService = class {
|
|
|
7344
7344
|
);
|
|
7345
7345
|
}
|
|
7346
7346
|
}
|
|
7347
|
+
assertAddColumnCapability() {
|
|
7348
|
+
const capabilities = this.connection.getCapabilities();
|
|
7349
|
+
if (!(capabilities.ddlAddColumnSupport ?? capabilities.ddlAlterSupport)) {
|
|
7350
|
+
throw new CapabilityError(
|
|
7351
|
+
`SchemaService does not support adding columns for dialect "${this.connection.getDialect().name}".`
|
|
7352
|
+
);
|
|
7353
|
+
}
|
|
7354
|
+
}
|
|
7347
7355
|
assertForeignKeyCapability(action) {
|
|
7348
7356
|
if (this.isSqlite() || !this.connection.getCapabilities().ddlAlterSupport) {
|
|
7349
7357
|
throw new CapabilityError(
|
|
@@ -7384,16 +7392,20 @@ var SchemaService = class {
|
|
|
7384
7392
|
async executeTableMutation(tableName, operation) {
|
|
7385
7393
|
switch (operation.kind) {
|
|
7386
7394
|
case "addColumn": {
|
|
7387
|
-
this.
|
|
7395
|
+
this.assertAddColumnCapability();
|
|
7388
7396
|
const definition = this.resolveColumnDefinition(operation.columnName, operation.column);
|
|
7389
7397
|
await this.execute(this.createCompiler().compile(addColumnOperation(tableName, definition)));
|
|
7390
7398
|
this.updateRegisteredTable(tableName, (table) => this.withColumn(table, definition));
|
|
7391
7399
|
return;
|
|
7392
7400
|
}
|
|
7393
7401
|
case "alterColumn": {
|
|
7394
|
-
this.assertAlterCapability("altering columns");
|
|
7395
7402
|
const definition = this.resolveColumnDefinition(operation.columnName, operation.column);
|
|
7396
|
-
|
|
7403
|
+
if (this.isSqlite()) {
|
|
7404
|
+
await this.rebuildSqliteTableForAlteredColumn(tableName, definition);
|
|
7405
|
+
} else {
|
|
7406
|
+
this.assertAlterCapability("altering columns");
|
|
7407
|
+
await this.execute(this.createCompiler().compile(alterColumnOperation(tableName, definition)));
|
|
7408
|
+
}
|
|
7397
7409
|
this.updateRegisteredTable(tableName, (table) => this.withAlteredColumn(table, definition));
|
|
7398
7410
|
return;
|
|
7399
7411
|
}
|
|
@@ -7457,6 +7469,48 @@ var SchemaService = class {
|
|
|
7457
7469
|
return;
|
|
7458
7470
|
}
|
|
7459
7471
|
}
|
|
7472
|
+
async rebuildSqliteTableForAlteredColumn(tableName, column2) {
|
|
7473
|
+
const registered = this.connection.getSchemaRegistry().get(tableName);
|
|
7474
|
+
if (!registered) {
|
|
7475
|
+
throw new SchemaError(
|
|
7476
|
+
`SQLite column changes require table "${tableName}" to be registered before calling change().`
|
|
7477
|
+
);
|
|
7478
|
+
}
|
|
7479
|
+
if (!registered.columns[column2.name]) {
|
|
7480
|
+
throw new SchemaError(
|
|
7481
|
+
`SQLite cannot change missing column "${column2.name}" on table "${tableName}". Add the column without change() first.`
|
|
7482
|
+
);
|
|
7483
|
+
}
|
|
7484
|
+
const temporaryTableName = `__holo_rebuild_${tableName.replaceAll(".", "_")}`;
|
|
7485
|
+
if (await this.hasTable(temporaryTableName)) {
|
|
7486
|
+
throw new SchemaError(
|
|
7487
|
+
`SQLite cannot rebuild table "${tableName}" because temporary table "${temporaryTableName}" already exists.`
|
|
7488
|
+
);
|
|
7489
|
+
}
|
|
7490
|
+
const rebuilt = this.withAlteredColumn(registered, column2);
|
|
7491
|
+
const schemaObjects = await this.connection.introspectCompiled({
|
|
7492
|
+
sql: "SELECT type, name, sql FROM sqlite_master WHERE tbl_name = ? AND type IN ('index', 'trigger') AND sql IS NOT NULL ORDER BY type, name",
|
|
7493
|
+
bindings: [tableName],
|
|
7494
|
+
source: `schema:sqliteRebuild:objects:${tableName}`
|
|
7495
|
+
});
|
|
7496
|
+
const temporaryDefinition = defineTable(temporaryTableName, rebuilt.columns);
|
|
7497
|
+
const compiler = this.createCompiler();
|
|
7498
|
+
const quote = (identifier) => this.connection.getDialect().quoteIdentifier(identifier);
|
|
7499
|
+
const columns = Object.keys(rebuilt.columns).map(quote).join(", ");
|
|
7500
|
+
await this.execute(compiler.compile(createTableOperation(temporaryDefinition)));
|
|
7501
|
+
await this.connection.executeCompiled({
|
|
7502
|
+
sql: `INSERT INTO ${quote(temporaryTableName)} (${columns}) SELECT ${columns} FROM ${quote(tableName)}`,
|
|
7503
|
+
source: `schema:sqliteRebuild:copy:${tableName}`
|
|
7504
|
+
});
|
|
7505
|
+
await this.execute(compiler.compile(dropTableOperation(tableName)));
|
|
7506
|
+
await this.execute(compiler.compile(renameTableOperation(temporaryTableName, tableName)));
|
|
7507
|
+
for (const object of schemaObjects.rows) {
|
|
7508
|
+
await this.connection.executeCompiled({
|
|
7509
|
+
sql: object.sql,
|
|
7510
|
+
source: `schema:sqliteRebuild:restore:${object.type}:${object.name}`
|
|
7511
|
+
});
|
|
7512
|
+
}
|
|
7513
|
+
}
|
|
7460
7514
|
async buildTableDefinition(tableName, callback) {
|
|
7461
7515
|
assertValidIdentifierPath(tableName, "Table name");
|
|
7462
7516
|
if (!callback) {
|
|
@@ -7915,7 +7969,7 @@ var MigrationService = class {
|
|
|
7915
7969
|
for (const migration of pending) {
|
|
7916
7970
|
const log = this.createMigrationLog(migration.name, "up", nextBatch);
|
|
7917
7971
|
await this.runMigrationLifecycle(log, async () => {
|
|
7918
|
-
await this.
|
|
7972
|
+
await this.runMigrationTransaction(async (tx) => {
|
|
7919
7973
|
const context = this.createContext(tx);
|
|
7920
7974
|
await migration.up(context);
|
|
7921
7975
|
await new TableQueryBuilder(migrationsTable, tx).insert({
|
|
@@ -7964,7 +8018,7 @@ var MigrationService = class {
|
|
|
7964
8018
|
}
|
|
7965
8019
|
const log = this.createMigrationLog(record.name, "down", record.batch);
|
|
7966
8020
|
await this.runMigrationLifecycle(log, async () => {
|
|
7967
|
-
await this.
|
|
8021
|
+
await this.runMigrationTransaction(async (tx) => {
|
|
7968
8022
|
const context = this.createContext(tx);
|
|
7969
8023
|
if (migration.down) {
|
|
7970
8024
|
await migration.down(context);
|
|
@@ -7984,6 +8038,22 @@ var MigrationService = class {
|
|
|
7984
8038
|
allowsConcurrentMigrations: false
|
|
7985
8039
|
};
|
|
7986
8040
|
}
|
|
8041
|
+
async runMigrationTransaction(callback) {
|
|
8042
|
+
const adapter = this.connection.getAdapter();
|
|
8043
|
+
const runWithinScope = adapter.runWithTransactionScope?.bind(adapter) ?? (async (runner) => runner());
|
|
8044
|
+
return await runWithinScope(async () => {
|
|
8045
|
+
const driverState = await adapter.beforeMigrationTransaction?.();
|
|
8046
|
+
try {
|
|
8047
|
+
return await this.connection.transaction(async (tx) => {
|
|
8048
|
+
const result = await callback(tx);
|
|
8049
|
+
await adapter.validateMigrationTransaction?.();
|
|
8050
|
+
return result;
|
|
8051
|
+
});
|
|
8052
|
+
} finally {
|
|
8053
|
+
await adapter.afterMigrationTransaction?.(driverState);
|
|
8054
|
+
}
|
|
8055
|
+
});
|
|
8056
|
+
}
|
|
7987
8057
|
async ensureTrackingTable() {
|
|
7988
8058
|
const schema = createSchemaService(this.connection);
|
|
7989
8059
|
if (await schema.hasTable(migrationsTable.tableName)) {
|
|
@@ -14284,6 +14354,7 @@ var DEFAULT_CAPABILITIES = {
|
|
|
14284
14354
|
jsonLength: false,
|
|
14285
14355
|
schemaQualifiedIdentifiers: false,
|
|
14286
14356
|
nativeUpsert: false,
|
|
14357
|
+
ddlAddColumnSupport: false,
|
|
14287
14358
|
ddlAlterSupport: false,
|
|
14288
14359
|
introspection: false
|
|
14289
14360
|
};
|
|
@@ -14940,6 +15011,15 @@ var DeferredDatabaseDriverAdapter = class {
|
|
|
14940
15011
|
async runWithTransactionScope(callback) {
|
|
14941
15012
|
return (await this.resolve()).runWithTransactionScope?.(callback) ?? callback();
|
|
14942
15013
|
}
|
|
15014
|
+
async beforeMigrationTransaction() {
|
|
15015
|
+
return await (await this.resolve()).beforeMigrationTransaction?.();
|
|
15016
|
+
}
|
|
15017
|
+
async validateMigrationTransaction() {
|
|
15018
|
+
await (await this.resolve()).validateMigrationTransaction?.();
|
|
15019
|
+
}
|
|
15020
|
+
async afterMigrationTransaction(state) {
|
|
15021
|
+
await (await this.resolve()).afterMigrationTransaction?.(state);
|
|
15022
|
+
}
|
|
14943
15023
|
async introspect(sql, bindings, options) {
|
|
14944
15024
|
const adapter = await this.resolve();
|
|
14945
15025
|
return adapter.introspect ? adapter.introspect(sql, bindings, options) : adapter.query(sql, bindings, options);
|
|
@@ -15150,6 +15230,7 @@ function createDialect(driver) {
|
|
|
15150
15230
|
jsonLength: true,
|
|
15151
15231
|
schemaQualifiedIdentifiers: false,
|
|
15152
15232
|
nativeUpsert: true,
|
|
15233
|
+
ddlAddColumnSupport: true,
|
|
15153
15234
|
ddlAlterSupport: false,
|
|
15154
15235
|
introspection: true
|
|
15155
15236
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holo-js/db",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Holo-JS Framework - portable database, ORM, migrations, factories, and seeders",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,16 +28,16 @@
|
|
|
28
28
|
"test": "vitest --run"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@holo-js/config": "^0.3.
|
|
32
|
-
"@holo-js/kernel": "^0.3.
|
|
31
|
+
"@holo-js/config": "^0.3.2",
|
|
32
|
+
"@holo-js/kernel": "^0.3.2",
|
|
33
33
|
"inflection": "^3.0.2",
|
|
34
34
|
"ulid": "^3.0.1",
|
|
35
35
|
"uuid": "^12.0.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@holo-js/db-mysql": "^0.3.
|
|
39
|
-
"@holo-js/db-postgres": "^0.3.
|
|
40
|
-
"@holo-js/db-sqlite": "^0.3.
|
|
38
|
+
"@holo-js/db-mysql": "^0.3.2",
|
|
39
|
+
"@holo-js/db-postgres": "^0.3.2",
|
|
40
|
+
"@holo-js/db-sqlite": "^0.3.2",
|
|
41
41
|
"tsup": "^8.3.5",
|
|
42
42
|
"typescript": "^5.7.2"
|
|
43
43
|
}
|