velocious 1.0.612 → 1.0.614
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/README.md +2 -2
- package/build/background-jobs/pooled-runner-child.js +1 -0
- package/build/background-jobs/worker.js +65 -27
- package/build/database/drivers/base.js +19 -0
- package/build/database/drivers/mysql/index.js +12 -0
- package/build/database/drivers/mysql/sql/alter-table.js +14 -2
- package/build/database/drivers/pgsql/index.js +13 -0
- package/build/database/migration/change-table.js +324 -0
- package/build/database/migration/index.js +197 -13
- package/build/src/background-jobs/pooled-runner-child.js +3 -1
- package/build/src/background-jobs/worker.d.ts +23 -10
- package/build/src/background-jobs/worker.d.ts.map +1 -1
- package/build/src/background-jobs/worker.js +67 -29
- package/build/src/database/drivers/base.d.ts +13 -0
- package/build/src/database/drivers/base.d.ts.map +1 -1
- package/build/src/database/drivers/base.js +18 -1
- package/build/src/database/drivers/mysql/index.d.ts +10 -0
- package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
- package/build/src/database/drivers/mysql/index.js +11 -1
- package/build/src/database/drivers/mysql/sql/alter-table.d.ts.map +1 -1
- package/build/src/database/drivers/mysql/sql/alter-table.js +14 -3
- package/build/src/database/drivers/pgsql/index.d.ts +11 -0
- package/build/src/database/drivers/pgsql/index.d.ts.map +1 -1
- package/build/src/database/drivers/pgsql/index.js +12 -1
- package/build/src/database/migration/change-table.d.ts +403 -0
- package/build/src/database/migration/change-table.d.ts.map +1 -0
- package/build/src/database/migration/change-table.js +286 -0
- package/build/src/database/migration/index.d.ts +46 -38
- package/build/src/database/migration/index.d.ts.map +1 -1
- package/build/src/database/migration/index.js +179 -14
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/background-jobs/pooled-runner-child.js +1 -0
- package/src/background-jobs/worker.js +65 -27
- package/src/database/drivers/base.js +19 -0
- package/src/database/drivers/mysql/index.js +12 -0
- package/src/database/drivers/mysql/sql/alter-table.js +14 -2
- package/src/database/drivers/pgsql/index.js +13 -0
- package/src/database/migration/change-table.js +324 -0
- 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))
|
|
@@ -2396,7 +2396,7 @@ VELOCIOUS_BACKGROUND_JOBS_JOB_TIMEOUT_MS=5400000
|
|
|
2396
2396
|
|
|
2397
2397
|
`maxConcurrentInlineJobs` (default: `4`) caps how many `executionMode: "inline"` jobs a single `background-jobs-worker` process runs in parallel. Concurrency is at the JS event-loop level: every job in flight shares the worker's process and DB connection pool, so the cap should fit the pool, not the CPU count. Forking remains the right tool when you need memory isolation across long-running jobs or want to use more cores; select it with `executionMode: "forked"`.
|
|
2398
2398
|
|
|
2399
|
-
New jobs default to `executionMode: "pooled"`: a worker runs them in warm, reusable Node child runners. `pooledRunnerCount` (default: `4`) bounds this independent per-worker pool, and `pooledRunnerConcurrency` (default: `1`) sets how many jobs each child runs at once on its own event loop, so total pooled capacity is `pooledRunnerCount × pooledRunnerConcurrency` — raise concurrency for I/O-bound jobs to get high throughput from a bounded, isolated set of processes. Workers advertise that exact free-slot count and the main consumes one slot per durable handoff, allowing one readiness notification to fill the pool. `pooledRunnerCount`, `pooledRunnerConcurrency`, and `pooledRunnerMaxJobs` must be finite positive integers; the RSS and lifetime limits must be finite positive numbers. A child is recycled after an acknowledged job when it reaches `pooledRunnerMaxJobs` (default: `100`), `pooledRunnerMaxRssBytes` (default: `536870912`, or 512 MiB), or `pooledRunnerMaxLifetimeMs` (default: `3600000`, or one hour). `execution_mode` is the single source of truth for a job's runtime — pooled rows persist as `execution_mode = "pooled"` directly. See [execution modes and pooled runners](docs/background-jobs.md#execution-modes-and-pooled-runners).
|
|
2399
|
+
New jobs default to `executionMode: "pooled"`: a worker runs them in warm, reusable Node child runners. `pooledRunnerCount` (default: `4`) bounds this independent per-worker pool, and `pooledRunnerConcurrency` (default: `1`) sets how many jobs each child runs at once on its own event loop, so total pooled capacity is `pooledRunnerCount × pooledRunnerConcurrency` — raise concurrency for I/O-bound jobs to get high throughput from a bounded, isolated set of processes. Workers advertise that exact free-slot count and the main consumes one slot per durable handoff, allowing one readiness notification to fill the pool. If an initialized child exits unexpectedly, the worker immediately advertises the freed capacity while failure reports retry; the replacement is spawned lazily by the next dispatch, and a pre-startup crash does not trigger a respawn loop. `pooledRunnerCount`, `pooledRunnerConcurrency`, and `pooledRunnerMaxJobs` must be finite positive integers; the RSS and lifetime limits must be finite positive numbers. A child is recycled after an acknowledged job when it reaches `pooledRunnerMaxJobs` (default: `100`), `pooledRunnerMaxRssBytes` (default: `536870912`, or 512 MiB), or `pooledRunnerMaxLifetimeMs` (default: `3600000`, or one hour). `execution_mode` is the single source of truth for a job's runtime — pooled rows persist as `execution_mode = "pooled"` directly. See [execution modes and pooled runners](docs/background-jobs.md#execution-modes-and-pooled-runners).
|
|
2400
2400
|
|
|
2401
2401
|
Cold pooled jobs share one atomic model-bootstrap phase. If that phase fails, all
|
|
2402
2402
|
waiting jobs receive the failure; a later job in the surviving child cannot run
|
|
@@ -141,3 +141,4 @@ function handleMessage(message) {
|
|
|
141
141
|
process.on("message", (message) => handleMessage(message))
|
|
142
142
|
process.once("disconnect", () => void shutdownRunner(0))
|
|
143
143
|
for (const signal of ["SIGTERM", "SIGINT"]) process.once(signal, () => void shutdownRunner(1))
|
|
144
|
+
if (process.send) process.send({type: "ready"})
|
|
@@ -194,8 +194,10 @@ export default class BackgroundJobsWorker {
|
|
|
194
194
|
this.inflightPooledJobs = new Set()
|
|
195
195
|
/** @type {Set<import("node:child_process").ChildProcess>} */
|
|
196
196
|
this.pooledChildren = new Set()
|
|
197
|
-
/** @type {Map<import("node:child_process").ChildProcess, {createdAtMs: number, jobsRun: number, inflight: Map<string, {payload: import("./types.js").BackgroundJobPayload & {id: string}, resolve?: (value: void) => void, timeoutTimer?: ReturnType<typeof setTimeout> | null}>, lastDispatchSeq: number, retiring: boolean, settling?: boolean, timeoutSigkillTimer?: ReturnType<typeof setTimeout> | null}>} */
|
|
197
|
+
/** @type {Map<import("node:child_process").ChildProcess, {createdAtMs: number, jobsRun: number, inflight: Map<string, {payload: import("./types.js").BackgroundJobPayload & {id: string}, resolve?: (value: void) => void, pooledJob?: Promise<void>, timeoutTimer?: ReturnType<typeof setTimeout> | null}>, lastDispatchSeq: number, retiring: boolean, started?: boolean, settling?: boolean, timeoutSigkillTimer?: ReturnType<typeof setTimeout> | null}>} */
|
|
198
198
|
this.pooledChildStates = new Map()
|
|
199
|
+
/** @type {WeakSet<Promise<void>>} */
|
|
200
|
+
this._pooledStartupFailureJobs = new WeakSet()
|
|
199
201
|
// Monotonic dispatch counter for round-robin child selection: each dispatch stamps
|
|
200
202
|
// the chosen child, and selection prefers the child dispatched least recently.
|
|
201
203
|
this._pooledDispatchSeq = 0
|
|
@@ -600,16 +602,16 @@ export default class BackgroundJobsWorker {
|
|
|
600
602
|
}
|
|
601
603
|
|
|
602
604
|
/**
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
605
|
+
* Advertises current worker capacity unless the worker is draining.
|
|
606
|
+
* @param {object} [options] - Advertisement options.
|
|
607
|
+
* @param {boolean} [options.revokePooledAdmission] - Revoke pooled credits while preserving other execution modes.
|
|
606
608
|
* @returns {void}
|
|
607
609
|
*/
|
|
608
|
-
_sendReadyIfRunning() {
|
|
610
|
+
_sendReadyIfRunning({revokePooledAdmission = false} = {}) {
|
|
609
611
|
if (this.shouldStop) return
|
|
610
612
|
if (!this.jsonSocket) return
|
|
611
613
|
|
|
612
|
-
const readyMessage = this._readyMessage()
|
|
614
|
+
const readyMessage = this._readyMessage({revokePooledAdmission})
|
|
613
615
|
|
|
614
616
|
if (!readyMessage) return
|
|
615
617
|
this.jsonSocket.send(readyMessage)
|
|
@@ -617,21 +619,24 @@ export default class BackgroundJobsWorker {
|
|
|
617
619
|
|
|
618
620
|
/**
|
|
619
621
|
* Runs ready message.
|
|
622
|
+
* @param {object} [options] - Advertisement options.
|
|
623
|
+
* @param {boolean} [options.revokePooledAdmission] - Revoke pooled credits while preserving other execution modes.
|
|
620
624
|
* @returns {import("./types.js").BackgroundJobSocketMessage | null} - Ready message or null when the worker has no capacity.
|
|
621
625
|
*/
|
|
622
|
-
_readyMessage() {
|
|
626
|
+
_readyMessage({revokePooledAdmission = false} = {}) {
|
|
623
627
|
const acceptsProcessJob = this.inflightProcessJobs.size < this.maxConcurrentForkedJobs
|
|
624
628
|
const acceptsInline = this.inflightInlineJobs.size < this.maxConcurrentInlineJobs
|
|
625
|
-
const
|
|
629
|
+
const availablePooledSlots = revokePooledAdmission ? 0 : this._availablePooledSlots()
|
|
630
|
+
const acceptsPooled = availablePooledSlots > 0
|
|
626
631
|
|
|
627
|
-
if (!acceptsProcessJob && !acceptsInline && !acceptsPooled) return null
|
|
632
|
+
if (!revokePooledAdmission && !acceptsProcessJob && !acceptsInline && !acceptsPooled) return null
|
|
628
633
|
|
|
629
634
|
return {
|
|
630
635
|
type: "ready",
|
|
631
636
|
acceptsForked: acceptsProcessJob,
|
|
632
637
|
acceptsInline,
|
|
633
638
|
acceptsPooled,
|
|
634
|
-
availablePooledSlots
|
|
639
|
+
availablePooledSlots,
|
|
635
640
|
acceptsSpawned: acceptsProcessJob
|
|
636
641
|
}
|
|
637
642
|
}
|
|
@@ -646,7 +651,7 @@ export default class BackgroundJobsWorker {
|
|
|
646
651
|
let inflight
|
|
647
652
|
inflight = pooledJob.finally(() => {
|
|
648
653
|
this.inflightPooledJobs.delete(inflight)
|
|
649
|
-
if (!this.shouldStop) this._sendReadyIfRunning()
|
|
654
|
+
if (!this.shouldStop && !this._pooledStartupFailureJobs.has(pooledJob)) this._sendReadyIfRunning()
|
|
650
655
|
})
|
|
651
656
|
this.inflightPooledJobs.add(inflight)
|
|
652
657
|
}
|
|
@@ -689,16 +694,22 @@ export default class BackgroundJobsWorker {
|
|
|
689
694
|
// Stamp the round-robin cursor so the next dispatch prefers a different child.
|
|
690
695
|
state.lastDispatchSeq = ++this._pooledDispatchSeq
|
|
691
696
|
|
|
692
|
-
|
|
693
|
-
|
|
697
|
+
/**
|
|
698
|
+
* Resolves the pooled job promise.
|
|
699
|
+
* @type {(value: void) => void}
|
|
700
|
+
*/
|
|
701
|
+
let resolvePooledJob = () => {}
|
|
702
|
+
const pooledJob = new Promise((resolve) => { resolvePooledJob = resolve })
|
|
703
|
+
const timeoutTimer = this._armPooledJobTimeout({child, payload})
|
|
694
704
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
705
|
+
state.inflight.set(payload.id, {payload, resolve: resolvePooledJob, pooledJob, timeoutTimer})
|
|
706
|
+
try {
|
|
707
|
+
child.send({type: "job", payload, sharedTransactionBroker: this._pooledJobSharedTransactionBrokerConfig()})
|
|
708
|
+
} catch (error) {
|
|
709
|
+
void this._handlePooledChildFailure({child, error})
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
return pooledJob
|
|
702
713
|
}
|
|
703
714
|
|
|
704
715
|
/**
|
|
@@ -769,7 +780,8 @@ export default class BackgroundJobsWorker {
|
|
|
769
780
|
* (SIGTERM, then SIGKILL after the grace) — a hung JS job cannot be cancelled
|
|
770
781
|
* any other way. The non-clean exit flows through `_handlePooledChildFailure`,
|
|
771
782
|
* which reports every in-flight job on the child failed (so they requeue) and
|
|
772
|
-
* drops it from tracking;
|
|
783
|
+
* drops it from tracking; the failure path immediately re-advertises the
|
|
784
|
+
* resulting capacity once the runner has completed startup.
|
|
773
785
|
* @param {object} args - Options.
|
|
774
786
|
* @param {import("node:child_process").ChildProcess} args.child - Pooled child.
|
|
775
787
|
* @param {string} args.jobId - Job id that overran.
|
|
@@ -810,7 +822,7 @@ export default class BackgroundJobsWorker {
|
|
|
810
822
|
})
|
|
811
823
|
this.pooledChildren.add(child)
|
|
812
824
|
this.inflightProcessChildren.add(child)
|
|
813
|
-
this.pooledChildStates.set(child, {createdAtMs: Date.now(), jobsRun: 0, inflight: new Map(), lastDispatchSeq: 0, retiring: false})
|
|
825
|
+
this.pooledChildStates.set(child, {createdAtMs: Date.now(), jobsRun: 0, inflight: new Map(), lastDispatchSeq: 0, retiring: false, started: false})
|
|
814
826
|
child.on("message", (message) => this._handlePooledChildMessage({child, message}))
|
|
815
827
|
child.once("exit", (code, signal) => this._handlePooledChildFailure({child, error: new Error(`Pooled background job runner exited: code=${code} signal=${signal || "none"}`)}))
|
|
816
828
|
child.once("error", (error) => this._handlePooledChildFailure({child, error}))
|
|
@@ -829,7 +841,12 @@ export default class BackgroundJobsWorker {
|
|
|
829
841
|
if (!message || typeof message !== "object") return
|
|
830
842
|
const record = /** @type {{type?: ReturnType<typeof JSON.parse>, jobId?: ReturnType<typeof JSON.parse>, acknowledged?: ReturnType<typeof JSON.parse>, rssBytes?: ReturnType<typeof JSON.parse>, error?: ReturnType<typeof JSON.parse>}} */ (message)
|
|
831
843
|
const state = this.pooledChildStates.get(child)
|
|
844
|
+
if (record.type === "ready") {
|
|
845
|
+
if (state) state.started = true
|
|
846
|
+
return
|
|
847
|
+
}
|
|
832
848
|
if (record.type !== "job-outcome" || !state || state.settling || typeof record.jobId !== "string") return
|
|
849
|
+
state.started = true
|
|
833
850
|
const entry = state.inflight.get(record.jobId)
|
|
834
851
|
if (!entry) return
|
|
835
852
|
|
|
@@ -907,9 +924,11 @@ export default class BackgroundJobsWorker {
|
|
|
907
924
|
/**
|
|
908
925
|
* Removes an exited/unhealthy pooled child and reports every job that was
|
|
909
926
|
* in-flight on it as failed — a process-level crash's blast radius is the
|
|
910
|
-
* child's whole in-flight set.
|
|
911
|
-
*
|
|
912
|
-
*
|
|
927
|
+
* child's whole in-flight set. Once the child has completed startup, its
|
|
928
|
+
* freed capacity is advertised immediately; the replacement itself is still
|
|
929
|
+
* spawned lazily by the next dispatch. A child that exits before its startup
|
|
930
|
+
* handshake does not re-announce, avoiding a tight respawn loop on startup
|
|
931
|
+
* failure.
|
|
913
932
|
* @param {object} args - Failure details.
|
|
914
933
|
* @param {import("node:child_process").ChildProcess} args.child - Pooled child.
|
|
915
934
|
* @param {ReturnType<typeof JSON.parse>} args.error - Failure.
|
|
@@ -934,7 +953,7 @@ export default class BackgroundJobsWorker {
|
|
|
934
953
|
if (state) state.inflight.clear()
|
|
935
954
|
this.pooledChildStates.delete(child)
|
|
936
955
|
|
|
937
|
-
|
|
956
|
+
const failureReports = entries.map(async (entry) => {
|
|
938
957
|
await this._reportJobResult({
|
|
939
958
|
jobId: entry.payload.id,
|
|
940
959
|
status: "failed",
|
|
@@ -944,7 +963,26 @@ export default class BackgroundJobsWorker {
|
|
|
944
963
|
workerId: entry.payload.workerId || this.workerId
|
|
945
964
|
})
|
|
946
965
|
if (entry.resolve) entry.resolve(undefined)
|
|
947
|
-
})
|
|
966
|
+
})
|
|
967
|
+
|
|
968
|
+
// Start every fallback report before announcing capacity so the main cannot
|
|
969
|
+
// observe a replacement slot before the failed jobs' reports are in flight.
|
|
970
|
+
// The report promises remain tracked below; a slow retry must not hold the
|
|
971
|
+
// newly freed runner capacity hostage.
|
|
972
|
+
if (state && state.started !== false) {
|
|
973
|
+
this._sendReadyIfRunning()
|
|
974
|
+
} else if (state) {
|
|
975
|
+
for (const entry of entries) {
|
|
976
|
+
if (entry.pooledJob) this._pooledStartupFailureJobs.add(entry.pooledJob)
|
|
977
|
+
}
|
|
978
|
+
// A previous ready message may still have unconsumed pooled credits at the
|
|
979
|
+
// main. Revoke them authoritatively without suppressing valid inline or
|
|
980
|
+
// process-runner readiness; otherwise queued jobs can trigger a startup
|
|
981
|
+
// crash loop using the stale credits.
|
|
982
|
+
this._sendReadyIfRunning({revokePooledAdmission: true})
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
await Promise.allSettled(failureReports)
|
|
948
986
|
}
|
|
949
987
|
|
|
950
988
|
/**
|
|
@@ -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
|
|
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
|
-
|
|
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
|
+
}
|