velocious 1.0.613 → 1.0.615

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 (37) hide show
  1. package/README.md +2 -2
  2. package/build/background-jobs/store.js +32 -5
  3. package/build/database/drivers/base.js +19 -0
  4. package/build/database/drivers/mysql/index.js +12 -0
  5. package/build/database/drivers/mysql/sql/alter-table.js +14 -2
  6. package/build/database/drivers/pgsql/index.js +13 -0
  7. package/build/database/migration/change-table.js +324 -0
  8. package/build/database/migration/index.js +197 -13
  9. package/build/src/background-jobs/store.d.ts +1 -1
  10. package/build/src/background-jobs/store.d.ts.map +1 -1
  11. package/build/src/background-jobs/store.js +28 -6
  12. package/build/src/database/drivers/base.d.ts +13 -0
  13. package/build/src/database/drivers/base.d.ts.map +1 -1
  14. package/build/src/database/drivers/base.js +18 -1
  15. package/build/src/database/drivers/mysql/index.d.ts +10 -0
  16. package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
  17. package/build/src/database/drivers/mysql/index.js +11 -1
  18. package/build/src/database/drivers/mysql/sql/alter-table.d.ts.map +1 -1
  19. package/build/src/database/drivers/mysql/sql/alter-table.js +14 -3
  20. package/build/src/database/drivers/pgsql/index.d.ts +11 -0
  21. package/build/src/database/drivers/pgsql/index.d.ts.map +1 -1
  22. package/build/src/database/drivers/pgsql/index.js +12 -1
  23. package/build/src/database/migration/change-table.d.ts +403 -0
  24. package/build/src/database/migration/change-table.d.ts.map +1 -0
  25. package/build/src/database/migration/change-table.js +286 -0
  26. package/build/src/database/migration/index.d.ts +46 -38
  27. package/build/src/database/migration/index.d.ts.map +1 -1
  28. package/build/src/database/migration/index.js +179 -14
  29. package/build/tsconfig.tsbuildinfo +1 -1
  30. package/package.json +1 -1
  31. package/src/background-jobs/store.js +32 -5
  32. package/src/database/drivers/base.js +19 -0
  33. package/src/database/drivers/mysql/index.js +12 -0
  34. package/src/database/drivers/mysql/sql/alter-table.js +14 -2
  35. package/src/database/drivers/pgsql/index.js +13 -0
  36. package/src/database/migration/change-table.js +324 -0
  37. package/src/database/migration/index.js +197 -13
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  * Connection-scoped advisory locks with automatic cleanup before pooled connections are reused or closed (see [docs/advisory-locks.md](docs/advisory-locks.md))
8
8
  * Built-in record auditing for model lifecycle changes (see [docs/auditing.md](docs/auditing.md))
9
9
  * Declarative state machines for models, with typed event methods generated into the base model (see [docs/state-machine.md](docs/state-machine.md))
10
- * Migrations for schema changes and UTC datetime storage (see [docs/database-migrations.md](docs/database-migrations.md))
10
+ * Migrations for schema changes and UTC datetime storage, including recorded `changeTable` batches that combine operations into one `ALTER` on bulk-capable drivers (see [docs/database-migrations.md](docs/database-migrations.md) and [docs/change-table.md](docs/change-table.md))
11
11
  * Tenant-selected base-model and structure generation with one immutable, fail-closed physical database context; tenant-only model metadata initializes only after that context is active (see [docs/tenant-selected-database-generation.md](docs/tenant-selected-database-generation.md))
12
12
  * Read-only tenant migration deploy preflight with stable JSON output and fail-closed ledger reads (see [docs/tenant-migration-deploy-preflight.md](docs/tenant-migration-deploy-preflight.md))
13
13
  * External packages (engines) that contribute data models, frontend-model resources and migrations to a consuming app (see [docs/packages.md](docs/packages.md))
@@ -2625,7 +2625,7 @@ backgroundJobs: {
2625
2625
  }
2626
2626
  ```
2627
2627
 
2628
- A job with no queue runs on `"default"`; a queue with no cap is unlimited. Caps are enforced through the durable per-key concurrency mechanism (the reserved `queue:<name>` key) and hold regardless of how many worker processes run. Changing a cap is reconciled against the existing backlog only when `background-jobs-main` starts (serialized across processes with a database advisory lock); `db:migrate`, `db:tenants:*`, and routine store/application initialization never reconcile the backlog and stay read-only regarding queued jobs. Scheduled jobs honor a job's `static queue` too.
2628
+ A job with no queue runs on `"default"`; a queue with no cap is unlimited. Caps are enforced through the durable per-key concurrency mechanism (the reserved `queue:<name>` key) and hold regardless of how many worker processes run. Changing a cap and rebuilding durable active counts happen only when `background-jobs-main` starts (serialized across processes with a database advisory lock and logged with database identifier/duration); `db:migrate`, `db:tenants:*`, and routine store/application initialization with an intact jobs table never adopt queued jobs or rebuild global concurrency counts. If schema repair must recreate a missing `background_jobs` table while the migration marker and concurrency table survive, it resets the now-orphaned active counts against that newly empty table. Scheduled jobs honor a job's `static queue` too.
2629
2629
 
2630
2630
  Set `priority` (default `0`) to dispatch a queue ahead of lower-priority ones regardless of enqueue order, so a small time-critical queue is never starved by a flood of low-priority work sharing a worker pool. Unlike Sidekiq's strict queue ordering, priority composes with the caps: a higher-priority queue already at its `maxConcurrent` is skipped and dispatch falls through to the next eligible job. See [docs/background-jobs.md](docs/background-jobs.md#queues-per-queue-concurrency-caps).
2631
2631
 
@@ -46,6 +46,7 @@ import {
46
46
  const MIGRATIONS_TABLE = "velocious_internal_migrations"
47
47
  const MIGRATION_SCOPE = "background_jobs"
48
48
  const MIGRATION_VERSION = "20250215000000"
49
+ const SCHEMA_RECOVERY_PENDING_VERSION = "schema-recovery-pending"
49
50
  const EXECUTION_MODE_BACKFILL_MIGRATION_VERSION = "20260607131010"
50
51
  // Drops the redundant legacy `forked` boolean column and rewrites pooled rows to
51
52
  // persist `execution_mode = "pooled"` directly (retiring the pooled-as-forked
@@ -186,6 +187,13 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
186
187
  async reconcileQueueConcurrency() {
187
188
  if (this._queueConcurrencyReconciled) return
188
189
 
190
+ const databaseIdentifier = this.getDatabaseIdentifier()
191
+ const startedAtMs = Date.now()
192
+
193
+ await this.logger.info(() => [
194
+ "Starting background jobs queue-concurrency startup reconciliation",
195
+ {databaseIdentifier}
196
+ ])
189
197
  await this.ensureReady()
190
198
 
191
199
  await this._withDb(async (db) => {
@@ -206,6 +214,11 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
206
214
  await db.releaseAdvisoryLock(lockName)
207
215
  }
208
216
  })
217
+
218
+ await this.logger.info(() => [
219
+ "Completed background jobs queue-concurrency startup reconciliation",
220
+ {databaseIdentifier, durationMs: Date.now() - startedAtMs}
221
+ ])
209
222
  }
210
223
 
211
224
  /**
@@ -1514,24 +1527,29 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
1514
1527
  await this._ensureMigrationsTable(db)
1515
1528
 
1516
1529
  const alreadyApplied = await this._hasMigration(db)
1530
+ const schemaRecoveryPending = await this._hasMigration(db, SCHEMA_RECOVERY_PENDING_VERSION)
1531
+ const jobsTableExists = await db.tableExists(JOBS_TABLE)
1517
1532
 
1518
1533
  // Even when the migration row is present, the jobs table itself can have
1519
1534
  // been dropped underneath us by a transaction rollback in another caller
1520
1535
  // (DDL is transactional on SQLite/MSSQL). Verify the table physically
1521
1536
  // exists and recreate it when missing rather than trusting the migration
1522
1537
  // row alone, otherwise later callers fail with "no such table".
1523
- if (alreadyApplied && await db.tableExists(JOBS_TABLE)) {
1538
+ if (alreadyApplied && jobsTableExists && !schemaRecoveryPending) {
1524
1539
  await this._ensureJobsTableColumns(db)
1525
1540
  await this._ensureIdempotencyKeysTable(db)
1526
1541
  await this._ensureMailDeliveryOperationsTable(db)
1527
1542
  await this._ensureScheduleKeysTable(db)
1528
1543
  await this._ensureConcurrencyTable(db)
1529
1544
  await this._ensureCountRevisionTable(db)
1530
- await this._reconcileConcurrency(db)
1531
1545
 
1532
1546
  return
1533
1547
  }
1534
1548
 
1549
+ if (alreadyApplied && !schemaRecoveryPending) {
1550
+ await this._recordMigration(db, SCHEMA_RECOVERY_PENDING_VERSION)
1551
+ }
1552
+
1535
1553
  await this._applyMigrations(db)
1536
1554
  await this._ensureJobsTableColumns(db)
1537
1555
  await this._ensureIdempotencyKeysTable(db)
@@ -1539,9 +1557,18 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
1539
1557
  await this._ensureScheduleKeysTable(db)
1540
1558
  await this._ensureConcurrencyTable(db)
1541
1559
  await this._ensureCountRevisionTable(db)
1542
- await this._reconcileConcurrency(db)
1543
1560
 
1544
- if (alreadyApplied) return
1561
+ if (alreadyApplied) {
1562
+ // The recreated jobs table is empty, but the surviving concurrency table
1563
+ // can still count handoffs that disappeared with the dropped jobs table.
1564
+ await this._reconcileConcurrency(db)
1565
+ await db.delete({
1566
+ tableName: MIGRATIONS_TABLE,
1567
+ conditions: {key: this._migrationKey(SCHEMA_RECOVERY_PENDING_VERSION)}
1568
+ })
1569
+
1570
+ return
1571
+ }
1545
1572
 
1546
1573
  await this._recordMigration(db, MIGRATION_VERSION)
1547
1574
  }
@@ -2578,7 +2605,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
2578
2605
  }
2579
2606
 
2580
2607
  /**
2581
- * Rebuilds durable counts from active handoffs after startup.
2608
+ * Rebuilds durable counts from active handoffs.
2582
2609
  * @param {import("../database/drivers/base.js").default} db - Database connection.
2583
2610
  * @returns {Promise<void>} - Resolves when reconciled.
2584
2611
  */
@@ -908,6 +908,25 @@ export default class VelociousDatabaseDriversBase {
908
908
  throw new Error("'type' not implemented")
909
909
  }
910
910
 
911
+ /**
912
+ * Whether this driver can combine unrelated alter-table operations into a
913
+ * single `ALTER TABLE` statement (Rails' `supports_bulk_alter`).
914
+ * @returns {boolean} - Whether bulk alter is supported.
915
+ */
916
+ supportsBulkAlter() {
917
+ return false
918
+ }
919
+
920
+ /**
921
+ * Whether a bulk `ALTER TABLE` statement can also carry `ADD INDEX` clauses.
922
+ * Only drivers that support this keep index adds inside the combined batch;
923
+ * the rest execute each index as its own statement.
924
+ * @returns {boolean} - Whether indexes can be added inside a bulk alter.
925
+ */
926
+ supportsBulkAlterIndexes() {
927
+ return false
928
+ }
929
+
911
930
  /**
912
931
  * Runs insert.
913
932
  * @param {InsertSqlArgsType} args - Options object.
@@ -345,6 +345,18 @@ export default class VelociousDatabaseDriversMysql extends Base{
345
345
  */
346
346
  getType() { return "mysql" }
347
347
 
348
+ /**
349
+ * Whether this driver supports combining operations into one bulk `ALTER`.
350
+ * @returns {boolean} - Whether bulk alter is supported.
351
+ */
352
+ supportsBulkAlter() { return true }
353
+
354
+ /**
355
+ * Whether the bulk `ALTER` can also carry `ADD INDEX` clauses.
356
+ * @returns {boolean} - Whether indexes can be added inside a bulk alter.
357
+ */
358
+ supportsBulkAlterIndexes() { return true }
359
+
348
360
  /**
349
361
  * Runs retryable database error.
350
362
  * @param {Error} error - Error instance.
@@ -26,13 +26,25 @@ export default class VelociousDatabaseConnectionDriversMysqlSqlAlterTable extend
26
26
  const indexes = this.tableData.getIndexes()
27
27
 
28
28
  if (indexes.length === 0) return sqls
29
- if (sqls.length !== 1) throw new Error("Expected one MySQL ALTER TABLE statement when adding indexes")
29
+ if (sqls.length > 1) throw new Error("Expected one MySQL ALTER TABLE statement when adding indexes")
30
30
 
31
31
  const options = this.getOptions()
32
32
  let sql = sqls[0]
33
+ let needsIndexSeparator = true
34
+
35
+ if (sql === undefined) {
36
+ sql = `ALTER TABLE ${options.quoteTableName(this.tableData.getName())} `
37
+ needsIndexSeparator = false
38
+ }
33
39
 
34
40
  for (const index of indexes) {
35
- sql += ", ADD"
41
+ if (needsIndexSeparator) {
42
+ sql += ", "
43
+ } else {
44
+ needsIndexSeparator = true
45
+ }
46
+
47
+ sql += "ADD"
36
48
 
37
49
  if (index.getUnique()) sql += " UNIQUE"
38
50
 
@@ -203,6 +203,19 @@ export default class VelociousDatabaseDriversPgsql extends Base{
203
203
 
204
204
  getType() { return "pgsql" }
205
205
 
206
+ /**
207
+ * Whether this driver supports combining operations into one bulk `ALTER`.
208
+ * @returns {boolean} - Whether bulk alter is supported.
209
+ */
210
+ supportsBulkAlter() { return true }
211
+
212
+ /**
213
+ * Whether the bulk `ALTER` can also carry `ADD INDEX` clauses. PostgreSQL's
214
+ * `ALTER TABLE` cannot express index creation, so indexes stay standalone.
215
+ * @returns {boolean} - Whether indexes can be added inside a bulk alter.
216
+ */
217
+ supportsBulkAlterIndexes() { return false }
218
+
206
219
  /**
207
220
  * Runs query actual.
208
221
  * @param {string} sql - SQL string.
@@ -0,0 +1,324 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * ChangeTableAddIndexArgsType type.
5
+ * @typedef {object} ChangeTableAddIndexArgsType
6
+ * @property {boolean} [ifNotExists] - Skip creation if the index already exists.
7
+ * @property {string} [name] - Explicit index name to use.
8
+ * @property {boolean} [unique] - Whether the index should be unique.
9
+ */
10
+
11
+ /**
12
+ * ChangeTableRemoveIndexArgsType type.
13
+ * @typedef {object} ChangeTableRemoveIndexArgsType
14
+ * @property {string} [name] - Explicit index name to remove.
15
+ */
16
+
17
+ /**
18
+ * ChangeTableRemoveReferenceArgsType type.
19
+ * @typedef {object} ChangeTableRemoveReferenceArgsType
20
+ * @property {string} [columnName] - Override the derived reference column name.
21
+ * @property {string} [indexName] - Explicit generated index name to remove.
22
+ */
23
+
24
+ /**
25
+ * ChangeTableAddColumnOperationType type.
26
+ * @typedef {object} ChangeTableAddColumnOperationType
27
+ * @property {"addColumn"} type - Operation type.
28
+ * @property {string} columnName - Column name.
29
+ * @property {string} columnType - Column type.
30
+ * @property {import("../table-data/table-column.js").TableColumnArgsType | undefined} args - Column args.
31
+ */
32
+
33
+ /**
34
+ * ChangeTableRemoveColumnOperationType type.
35
+ * @typedef {object} ChangeTableRemoveColumnOperationType
36
+ * @property {"removeColumn"} type - Operation type.
37
+ * @property {string} columnName - Column name.
38
+ */
39
+
40
+ /**
41
+ * ChangeTableAddIndexOperationType type.
42
+ * @typedef {object} ChangeTableAddIndexOperationType
43
+ * @property {"addIndex"} type - Operation type.
44
+ * @property {Array<string | import("../table-data/table-column.js").default>} columns - Columns to index.
45
+ * @property {ChangeTableAddIndexArgsType | undefined} args - Index args.
46
+ */
47
+
48
+ /**
49
+ * ChangeTableRemoveIndexOperationType type.
50
+ * @typedef {object} ChangeTableRemoveIndexOperationType
51
+ * @property {"removeIndex"} type - Operation type.
52
+ * @property {string | Array<string | import("../table-data/table-column.js").default>} nameOrColumns - Index name or columns.
53
+ * @property {ChangeTableRemoveIndexArgsType | undefined} args - Index args.
54
+ */
55
+
56
+ /**
57
+ * ChangeTableAddReferenceOperationType type.
58
+ * @typedef {object} ChangeTableAddReferenceOperationType
59
+ * @property {"addReference"} type - Operation type.
60
+ * @property {string} referenceName - Reference name.
61
+ * @property {object | undefined} args - Reference args.
62
+ */
63
+
64
+ /**
65
+ * ChangeTableRemoveReferenceOperationType type.
66
+ * @typedef {object} ChangeTableRemoveReferenceOperationType
67
+ * @property {"removeReference"} type - Operation type.
68
+ * @property {string} referenceName - Reference name.
69
+ * @property {ChangeTableRemoveReferenceArgsType | undefined} args - Reference args.
70
+ */
71
+
72
+ /**
73
+ * ChangeTableRenameColumnOperationType type.
74
+ * @typedef {object} ChangeTableRenameColumnOperationType
75
+ * @property {"renameColumn"} type - Operation type.
76
+ * @property {string} oldColumnName - Previous column name.
77
+ * @property {string} newColumnName - New column name.
78
+ */
79
+
80
+ /**
81
+ * ChangeTableChangeColumnNullOperationType type.
82
+ * @typedef {object} ChangeTableChangeColumnNullOperationType
83
+ * @property {"changeColumnNull"} type - Operation type.
84
+ * @property {string} columnName - Column name.
85
+ * @property {boolean} nullable - Whether the column becomes nullable.
86
+ */
87
+
88
+ /**
89
+ * ChangeTableOperationType type.
90
+ * @typedef {ChangeTableAddColumnOperationType | ChangeTableRemoveColumnOperationType | ChangeTableAddIndexOperationType | ChangeTableRemoveIndexOperationType | ChangeTableAddReferenceOperationType | ChangeTableRemoveReferenceOperationType | ChangeTableRenameColumnOperationType | ChangeTableChangeColumnNullOperationType} ChangeTableOperationType
91
+ */
92
+
93
+ /**
94
+ * Table-scoped recorder used by `migration.changeTable`. Each call records a
95
+ * single DDL operation synchronously; `changeTable` replays them after the
96
+ * callback completes so a failed callback executes zero recorded DDL.
97
+ */
98
+ export default class VelociousDatabaseMigrationChangeTable {
99
+ /**
100
+ * Operations.
101
+ * @type {ChangeTableOperationType[]} */
102
+ _operations = []
103
+
104
+ /**
105
+ * Runs constructor.
106
+ * @param {object} args - Options object.
107
+ * @param {string} args.tableName - Table name.
108
+ */
109
+ constructor({tableName}) {
110
+ if (!tableName) throw new Error(`Invalid table name: ${tableName}`)
111
+
112
+ this._tableName = tableName
113
+ }
114
+
115
+ /**
116
+ * Runs get table name.
117
+ * @returns {string} - The table name.
118
+ */
119
+ getTableName() { return this._tableName }
120
+
121
+ /**
122
+ * Runs get operations.
123
+ * @returns {ChangeTableOperationType[]} - The recorded operations.
124
+ */
125
+ getOperations() { return this._operations }
126
+
127
+ /**
128
+ * Records a new column.
129
+ * @param {string} name - Column name.
130
+ * @param {string} type - Column type.
131
+ * @param {import("../table-data/table-column.js").TableColumnArgsType} [args] - Options object.
132
+ * @returns {void} - No return value.
133
+ */
134
+ column(name, type, args) {
135
+ this._operations.push({type: "addColumn", columnName: name, columnType: type, args})
136
+ }
137
+
138
+ /**
139
+ * Records a bigint column.
140
+ * @param {string} name - Column name.
141
+ * @param {import("../table-data/table-column.js").TableColumnArgsType} [args] - Options object.
142
+ * @returns {void} - No return value.
143
+ */
144
+ bigint(name, args) { this.column(name, "bigint", args) }
145
+
146
+ /**
147
+ * Records a blob column.
148
+ * @param {string} name - Column name.
149
+ * @param {import("../table-data/table-column.js").TableColumnArgsType} [args] - Options object.
150
+ * @returns {void} - No return value.
151
+ */
152
+ blob(name, args) { this.column(name, "blob", args) }
153
+
154
+ /**
155
+ * Records a boolean column.
156
+ * @param {string} name - Column name.
157
+ * @param {import("../table-data/table-column.js").TableColumnArgsType} [args] - Options object.
158
+ * @returns {void} - No return value.
159
+ */
160
+ boolean(name, args) { this.column(name, "boolean", args) }
161
+
162
+ /**
163
+ * Records a datetime column.
164
+ * @param {string} name - Column name.
165
+ * @param {import("../table-data/table-column.js").TableColumnArgsType} [args] - Options object.
166
+ * @returns {void} - No return value.
167
+ */
168
+ datetime(name, args) { this.column(name, "datetime", args) }
169
+
170
+ /**
171
+ * Records a decimal column.
172
+ * @param {string} name - Column name.
173
+ * @param {import("../table-data/table-column.js").TableColumnArgsType} [args] - Options object.
174
+ * @returns {void} - No return value.
175
+ */
176
+ decimal(name, args) { this.column(name, "decimal", args) }
177
+
178
+ /**
179
+ * Records an integer column.
180
+ * @param {string} name - Column name.
181
+ * @param {import("../table-data/table-column.js").TableColumnArgsType} [args] - Options object.
182
+ * @returns {void} - No return value.
183
+ */
184
+ integer(name, args) { this.column(name, "integer", args) }
185
+
186
+ /**
187
+ * Records a json column.
188
+ * @param {string} name - Column name.
189
+ * @param {import("../table-data/table-column.js").TableColumnArgsType} [args] - Options object.
190
+ * @returns {void} - No return value.
191
+ */
192
+ json(name, args) { this.column(name, "json", args) }
193
+
194
+ /**
195
+ * Records a string column.
196
+ * @param {string} name - Column name.
197
+ * @param {import("../table-data/table-column.js").TableColumnArgsType} [args] - Options object.
198
+ * @returns {void} - No return value.
199
+ */
200
+ string(name, args) { this.column(name, "string", args) }
201
+
202
+ /**
203
+ * Records a text column.
204
+ * @param {string} name - Column name.
205
+ * @param {import("../table-data/table-column.js").TableColumnArgsType} [args] - Options object.
206
+ * @returns {void} - No return value.
207
+ */
208
+ text(name, args) { this.column(name, "text", args) }
209
+
210
+ /**
211
+ * Records a tinyint column.
212
+ * @param {string} name - Column name.
213
+ * @param {import("../table-data/table-column.js").TableColumnArgsType} [args] - Options object.
214
+ * @returns {void} - No return value.
215
+ */
216
+ tinyint(name, args) { this.column(name, "tinyint", args) }
217
+
218
+ /**
219
+ * Records a uuid column.
220
+ * @param {string} name - Column name.
221
+ * @param {import("../table-data/table-column.js").TableColumnArgsType} [args] - Options object.
222
+ * @returns {void} - No return value.
223
+ */
224
+ uuid(name, args) { this.column(name, "uuid", args) }
225
+
226
+ /**
227
+ * Records created_at and updated_at datetime columns.
228
+ * @param {import("../table-data/table-column.js").TableColumnArgsType} [args] - Options object.
229
+ * @returns {void} - No return value.
230
+ */
231
+ timestamps(args) {
232
+ this.datetime("created_at", args)
233
+ this.datetime("updated_at", args)
234
+ }
235
+
236
+ /**
237
+ * Records a new index.
238
+ * @param {string | Array<string | import("../table-data/table-column.js").default>} columns - Column name or array of column names.
239
+ * @param {ChangeTableAddIndexArgsType} [args] - Options object.
240
+ * @returns {void} - No return value.
241
+ */
242
+ index(columns, args) {
243
+ const normalizedColumns = typeof columns == "string" ? [columns] : columns
244
+
245
+ this._operations.push({type: "addIndex", columns: normalizedColumns, args})
246
+ }
247
+
248
+ /**
249
+ * Records a reference column, index, and optional foreign key.
250
+ * @param {string} name - Reference name.
251
+ * @param {object} [args] - Options object.
252
+ * @returns {void} - No return value.
253
+ */
254
+ references(name, args) {
255
+ this._operations.push({type: "addReference", referenceName: name, args})
256
+ }
257
+
258
+ /**
259
+ * Alias for {@link references}.
260
+ * @param {string} name - Reference name.
261
+ * @param {object} [args] - Options object.
262
+ * @returns {void} - No return value.
263
+ */
264
+ belongsTo(name, args) { this.references(name, args) }
265
+
266
+ /**
267
+ * Records removal of one or more columns.
268
+ * @param {string[]} columnNames - Column names to remove.
269
+ * @returns {void} - No return value.
270
+ */
271
+ remove(...columnNames) {
272
+ for (const columnName of columnNames) {
273
+ this._operations.push({type: "removeColumn", columnName})
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Records removal of an index.
279
+ * @param {string | Array<string | import("../table-data/table-column.js").default>} nameOrColumns - Index name or columns.
280
+ * @param {ChangeTableRemoveIndexArgsType} [args] - Options object.
281
+ * @returns {void} - No return value.
282
+ */
283
+ removeIndex(nameOrColumns, args) {
284
+ this._operations.push({type: "removeIndex", nameOrColumns, args})
285
+ }
286
+
287
+ /**
288
+ * Records removal of a reference column and its generated index and foreign keys.
289
+ * @param {string} name - Reference name.
290
+ * @param {ChangeTableRemoveReferenceArgsType} [args] - Options object.
291
+ * @returns {void} - No return value.
292
+ */
293
+ removeReferences(name, args) {
294
+ this._operations.push({type: "removeReference", referenceName: name, args})
295
+ }
296
+
297
+ /**
298
+ * Records removal of the created_at and updated_at columns.
299
+ * @returns {void} - No return value.
300
+ */
301
+ removeTimestamps() {
302
+ this.remove("created_at", "updated_at")
303
+ }
304
+
305
+ /**
306
+ * Records a column rename.
307
+ * @param {string} oldColumnName - Previous column name.
308
+ * @param {string} newColumnName - New column name.
309
+ * @returns {void} - No return value.
310
+ */
311
+ rename(oldColumnName, newColumnName) {
312
+ this._operations.push({type: "renameColumn", oldColumnName, newColumnName})
313
+ }
314
+
315
+ /**
316
+ * Records a change to a column's nullability.
317
+ * @param {string} columnName - Column name.
318
+ * @param {boolean} nullable - Whether the column becomes nullable.
319
+ * @returns {void} - No return value.
320
+ */
321
+ changeNull(columnName, nullable) {
322
+ this._operations.push({type: "changeColumnNull", columnName, nullable})
323
+ }
324
+ }