@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 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.assertAlterCapability("adding columns");
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
- await this.execute(this.createCompiler().compile(alterColumnOperation(tableName, definition)));
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.connection.transaction(async (tx) => {
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.connection.transaction(async (tx) => {
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.1",
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.1",
32
- "@holo-js/kernel": "^0.3.1",
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.1",
39
- "@holo-js/db-postgres": "^0.3.1",
40
- "@holo-js/db-sqlite": "^0.3.1",
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
  }