velocious 1.0.533 → 1.0.535

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 (34) hide show
  1. package/README.md +2 -0
  2. package/build/background-jobs/store.js +67 -22
  3. package/build/database/drivers/mysql/index.js +24 -11
  4. package/build/database/migration/index.js +20 -0
  5. package/build/database/migrator.js +5 -0
  6. package/build/database/tenants/data-copier.js +40 -5
  7. package/build/environment-handlers/base.js +14 -0
  8. package/build/environment-handlers/node.js +23 -0
  9. package/build/src/background-jobs/store.d.ts +30 -1
  10. package/build/src/background-jobs/store.d.ts.map +1 -1
  11. package/build/src/background-jobs/store.js +62 -22
  12. package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
  13. package/build/src/database/drivers/mysql/index.js +19 -10
  14. package/build/src/database/migration/index.d.ts +7 -0
  15. package/build/src/database/migration/index.d.ts.map +1 -1
  16. package/build/src/database/migration/index.js +18 -1
  17. package/build/src/database/migrator.d.ts.map +1 -1
  18. package/build/src/database/migrator.js +6 -1
  19. package/build/src/database/tenants/data-copier.d.ts +31 -3
  20. package/build/src/database/tenants/data-copier.d.ts.map +1 -1
  21. package/build/src/database/tenants/data-copier.js +39 -6
  22. package/build/src/environment-handlers/base.d.ts +13 -0
  23. package/build/src/environment-handlers/base.d.ts.map +1 -1
  24. package/build/src/environment-handlers/base.js +14 -1
  25. package/build/src/environment-handlers/node.d.ts.map +1 -1
  26. package/build/src/environment-handlers/node.js +22 -1
  27. package/package.json +1 -1
  28. package/src/background-jobs/store.js +67 -22
  29. package/src/database/drivers/mysql/index.js +24 -11
  30. package/src/database/migration/index.js +20 -0
  31. package/src/database/migrator.js +5 -0
  32. package/src/database/tenants/data-copier.js +40 -5
  33. package/src/environment-handlers/base.js +14 -0
  34. package/src/environment-handlers/node.js +23 -0
@@ -468,6 +468,26 @@ export default class VelociousDatabaseMigration {
468
468
  return Boolean(false)
469
469
  }
470
470
 
471
+ /**
472
+ * Checks whether an index with the given name exists on a table.
473
+ * @param {string} tableName - Table name.
474
+ * @param {string} indexName - Index name to look for.
475
+ * @returns {Promise<boolean>} - Whether the index exists on the table.
476
+ */
477
+ async indexExists(tableName, indexName) {
478
+ const table = await this.getDriver().getTableByName(tableName, {throwError: false})
479
+
480
+ if (table) {
481
+ for (const index of await table.getIndexes()) {
482
+ if (index.getName() == indexName) {
483
+ return true
484
+ }
485
+ }
486
+ }
487
+
488
+ return false
489
+ }
490
+
471
491
  /**
472
492
  * Sets up the database schema for a gap-less positional list. Adds the
473
493
  * position column (INT NOT NULL) if absent and creates a UNIQUE index on
@@ -204,6 +204,11 @@ export default class VelociousDatabaseMigrator {
204
204
 
205
205
  if (!environmentHandler || Object.keys(filteredDbs).length == 0) return
206
206
 
207
+ // Ensure velocious' own framework schema (background jobs) before the structure
208
+ // dump, and unconditionally — the dump is gated to enabled environments but the
209
+ // framework schema must exist after every migrate so `db:migrate` (and thus
210
+ // schema:load of the dumped SQL) produces a complete DB in every environment.
211
+ await environmentHandler.ensureFrameworkSchema({dbs: filteredDbs})
207
212
  await environmentHandler.afterMigrations({dbs: filteredDbs})
208
213
  }
209
214
 
@@ -130,6 +130,35 @@ export default class DataCopier {
130
130
  * @returns {Promise<Map<string, Record<string, unknown>[]>>} - Loaded rows grouped by table name.
131
131
  */
132
132
  async loadRows(db, keyValue) {
133
+ return (await this.traversePlan(db, keyValue)).rowsByTableName
134
+ }
135
+
136
+ /**
137
+ * Loads only the ids for `keyValue` for every table in the plan from `db`, using the same
138
+ * parent/child traversal as {@link DataCopier#loadRows} but selecting just the id column.
139
+ * Callers that only need to compare row membership — for example verifying a tenant already
140
+ * holds every default row before a cleanup delete — should use this instead of loadRows so
141
+ * they never materialise full rows; for large tenants that is the difference between a few
142
+ * kilobytes of ids and gigabytes of row data.
143
+ * @param {import("../drivers/base.js").default} db - Source or target database to traverse.
144
+ * @param {string} keyValue - Tenant key selecting the root plan rows.
145
+ * @returns {Promise<Map<string, string[]>>} - Loaded ids grouped by table name.
146
+ */
147
+ async loadRowIds(db, keyValue) {
148
+ return (await this.traversePlan(db, keyValue, [this.idColumn])).idsByTableName
149
+ }
150
+
151
+ /**
152
+ * Traverses the table plan for `keyValue`, querying each table by its tenant key column or
153
+ * (for child tables) by the ids already selected for its parent, and returns both the ids
154
+ * and the loaded rows grouped by table name. `selectColumns` bounds the columns each query
155
+ * selects; pass `[idColumn]` for an id-only traversal, or omit it to load full rows.
156
+ * @param {import("../drivers/base.js").default} db - Source or target database to traverse.
157
+ * @param {string} keyValue - Tenant key selecting the root plan rows.
158
+ * @param {string[]} [selectColumns] - Columns to select; defaults to every column.
159
+ * @returns {Promise<{idsByTableName: Map<string, string[]>, rowsByTableName: Map<string, Record<string, unknown>[]>}>} - Ids and rows grouped by table name.
160
+ */
161
+ async traversePlan(db, keyValue, selectColumns) {
133
162
  /** @type {Map<string, string[]>} */
134
163
  const idsByTableName = new Map()
135
164
  /** @type {Map<string, Record<string, unknown>[]>} */
@@ -142,6 +171,7 @@ export default class DataCopier {
142
171
  rows = await this.queryRowsByColumn({
143
172
  columnName: tableConfig.keyColumn,
144
173
  db,
174
+ selectColumns,
145
175
  tableName: tableConfig.tableName,
146
176
  values: [keyValue]
147
177
  })
@@ -157,6 +187,7 @@ export default class DataCopier {
157
187
  rows = await this.queryRowsByColumn({
158
188
  columnName: tableConfig.parentColumn,
159
189
  db,
190
+ selectColumns,
160
191
  tableName: tableConfig.tableName,
161
192
  values: idsByTableName.get(tableConfig.parentTableName) || []
162
193
  })
@@ -170,15 +201,16 @@ export default class DataCopier {
170
201
  rowsByTableName.set(tableConfig.tableName, rows)
171
202
  }
172
203
 
173
- return rowsByTableName
204
+ return {idsByTableName, rowsByTableName}
174
205
  }
175
206
 
176
207
  /**
177
- * Selects all rows of `tableName` in `db` whose `columnName` is in `values`, chunked.
178
- * @param {{columnName: string, db: import("../drivers/base.js").default, tableName: string, values: string[]}} args - Table, column, database, and values for the chunked lookup.
208
+ * Selects `tableName` rows in `db` whose `columnName` is in `values`, chunked. `selectColumns`
209
+ * bounds the projection and defaults to every column.
210
+ * @param {{columnName: string, db: import("../drivers/base.js").default, selectColumns?: string[], tableName: string, values: string[]}} args - Table, column, projection, database, and values for the chunked lookup.
179
211
  * @returns {Promise<Record<string, unknown>[]>} - Rows matching the supplied column values.
180
212
  */
181
- async queryRowsByColumn({columnName, db, tableName, values}) {
213
+ async queryRowsByColumn({columnName, db, selectColumns, tableName, values}) {
182
214
  const normalizedValues = uniqueStrings(values)
183
215
 
184
216
  if (normalizedValues.length <= 0) {
@@ -188,9 +220,12 @@ export default class DataCopier {
188
220
  const rows = []
189
221
  const quotedTable = db.quoteTable(tableName)
190
222
  const quotedColumn = db.quoteColumn(columnName)
223
+ const selectList = selectColumns
224
+ ? selectColumns.map((column) => `${quotedTable}.${db.quoteColumn(column)}`).join(", ")
225
+ : `${quotedTable}.*`
191
226
 
192
227
  for (const valuesChunk of chunks(normalizedValues, this.queryChunkSize)) {
193
- const sql = `SELECT ${quotedTable}.* FROM ${quotedTable} WHERE ${quotedColumn} IN (${this.quotedValuesSql(db, valuesChunk)})`
228
+ const sql = `SELECT ${selectList} FROM ${quotedTable} WHERE ${quotedColumn} IN (${this.quotedValuesSql(db, valuesChunk)})`
194
229
 
195
230
  rows.push(...await this.executeQuietQuery(db, sql))
196
231
  }
@@ -508,6 +508,20 @@ export default class VelociousEnvironmentHandlerBase {
508
508
  return
509
509
  }
510
510
 
511
+ /**
512
+ * Ensures velocious' own framework-owned schema (e.g. the background-jobs
513
+ * tables) exists after app migrations run, so `db:migrate` produces a complete
514
+ * schema deterministically instead of it only appearing once a runtime store
515
+ * boots. Runs before the structure dump. No-op by default; the node handler
516
+ * overrides it.
517
+ * @param {object} args - Options object.
518
+ * @param {Record<string, import("../database/drivers/base.js").default>} args.dbs - Dbs being migrated.
519
+ * @returns {Promise<void>} - Resolves when complete.
520
+ */
521
+ async ensureFrameworkSchema(args) { // eslint-disable-line no-unused-vars
522
+ return
523
+ }
524
+
511
525
  /**
512
526
  * Runs require command.
513
527
  * @abstract
@@ -1005,6 +1005,29 @@ export default class VelociousEnvironmentHandlerNode extends Base{
1005
1005
  return basePath
1006
1006
  }
1007
1007
 
1008
+ /**
1009
+ * Ensures velocious' background-jobs schema exists on its configured database
1010
+ * as part of `db:migrate`, so the framework's own tables (background_jobs +
1011
+ * background_job_concurrency, execution_mode etc.) are created deterministically
1012
+ * alongside app migrations and captured in the dumped structure SQL — rather than
1013
+ * only appearing once a runtime store boots. Reuses the store's idempotent
1014
+ * `_ensureSchema`, so it's safe to re-run against DBs that already have it.
1015
+ * @param {object} args - Options object.
1016
+ * @param {Record<string, import("../database/drivers/base.js").default>} args.dbs - Dbs being migrated.
1017
+ * @returns {Promise<void>} - Resolves when complete.
1018
+ */
1019
+ async ensureFrameworkSchema({dbs}) {
1020
+ const {default: BackgroundJobsStore} = await import("../background-jobs/store.js")
1021
+ const store = new BackgroundJobsStore({configuration: this.getConfiguration()})
1022
+ const databaseIdentifier = store.getDatabaseIdentifier() ?? "default"
1023
+
1024
+ // Reuse the connection db:migrate already holds for this database; opening a
1025
+ // nested checkout would deadlock a database whose pool is capped at one
1026
+ // connection. When the background-jobs database isn't among the migrated set,
1027
+ // this passes undefined and the store checks out its own (that pool is free).
1028
+ await store.ensureSchema(dbs[databaseIdentifier])
1029
+ }
1030
+
1008
1031
  /**
1009
1032
  * Runs after migrations.
1010
1033
  * @param {object} args - Options object.