@spinajs/orm 2.0.486 → 2.0.488
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/lib/cjs/builders.d.ts +57 -0
- package/lib/cjs/builders.d.ts.map +1 -1
- package/lib/cjs/builders.js +94 -1
- package/lib/cjs/builders.js.map +1 -1
- package/lib/cjs/config/orm.d.ts +24 -0
- package/lib/cjs/config/orm.d.ts.map +1 -0
- package/lib/cjs/config/orm.js +26 -0
- package/lib/cjs/config/orm.js.map +1 -0
- package/lib/cjs/decorators.d.ts +3 -2
- package/lib/cjs/decorators.d.ts.map +1 -1
- package/lib/cjs/decorators.js +19 -2
- package/lib/cjs/decorators.js.map +1 -1
- package/lib/cjs/descriptor.d.ts +29 -1
- package/lib/cjs/descriptor.d.ts.map +1 -1
- package/lib/cjs/descriptor.js +37 -0
- package/lib/cjs/descriptor.js.map +1 -1
- package/lib/cjs/index.d.ts +6 -0
- package/lib/cjs/index.d.ts.map +1 -1
- package/lib/cjs/index.js +6 -0
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/interfaces.d.ts +58 -2
- package/lib/cjs/interfaces.d.ts.map +1 -1
- package/lib/cjs/interfaces.js +18 -1
- package/lib/cjs/interfaces.js.map +1 -1
- package/lib/cjs/migration-environment.d.ts +70 -0
- package/lib/cjs/migration-environment.d.ts.map +1 -0
- package/lib/cjs/migration-environment.js +134 -0
- package/lib/cjs/migration-environment.js.map +1 -0
- package/lib/cjs/migration-runner.d.ts +108 -0
- package/lib/cjs/migration-runner.d.ts.map +1 -0
- package/lib/cjs/migration-runner.js +216 -0
- package/lib/cjs/migration-runner.js.map +1 -0
- package/lib/cjs/migration-service.d.ts +300 -0
- package/lib/cjs/migration-service.d.ts.map +1 -0
- package/lib/cjs/migration-service.js +829 -0
- package/lib/cjs/migration-service.js.map +1 -0
- package/lib/cjs/migration-sources.d.ts +85 -0
- package/lib/cjs/migration-sources.d.ts.map +1 -0
- package/lib/cjs/migration-sources.js +277 -0
- package/lib/cjs/migration-sources.js.map +1 -0
- package/lib/cjs/orm.d.ts +77 -15
- package/lib/cjs/orm.d.ts.map +1 -1
- package/lib/cjs/orm.js +156 -150
- package/lib/cjs/orm.js.map +1 -1
- package/lib/cjs/quoting.d.ts +33 -0
- package/lib/cjs/quoting.d.ts.map +1 -0
- package/lib/cjs/quoting.js +48 -0
- package/lib/cjs/quoting.js.map +1 -0
- package/lib/cjs/source-file.d.ts +10 -0
- package/lib/cjs/source-file.d.ts.map +1 -0
- package/lib/cjs/source-file.js +41 -0
- package/lib/cjs/source-file.js.map +1 -0
- package/lib/cjs/statements.d.ts +51 -1
- package/lib/cjs/statements.d.ts.map +1 -1
- package/lib/cjs/statements.js +73 -1
- package/lib/cjs/statements.js.map +1 -1
- package/lib/cjs/symbols.d.ts +11 -0
- package/lib/cjs/symbols.d.ts.map +1 -1
- package/lib/cjs/symbols.js +12 -1
- package/lib/cjs/symbols.js.map +1 -1
- package/lib/mjs/builders.d.ts +57 -0
- package/lib/mjs/builders.d.ts.map +1 -1
- package/lib/mjs/builders.js +94 -1
- package/lib/mjs/builders.js.map +1 -1
- package/lib/mjs/config/orm.d.ts +24 -0
- package/lib/mjs/config/orm.d.ts.map +1 -0
- package/lib/mjs/config/orm.js +24 -0
- package/lib/mjs/config/orm.js.map +1 -0
- package/lib/mjs/decorators.d.ts +3 -2
- package/lib/mjs/decorators.d.ts.map +1 -1
- package/lib/mjs/decorators.js +19 -2
- package/lib/mjs/decorators.js.map +1 -1
- package/lib/mjs/descriptor.d.ts +29 -1
- package/lib/mjs/descriptor.d.ts.map +1 -1
- package/lib/mjs/descriptor.js +36 -1
- package/lib/mjs/descriptor.js.map +1 -1
- package/lib/mjs/index.d.ts +6 -0
- package/lib/mjs/index.d.ts.map +1 -1
- package/lib/mjs/index.js +6 -0
- package/lib/mjs/index.js.map +1 -1
- package/lib/mjs/interfaces.d.ts +58 -2
- package/lib/mjs/interfaces.d.ts.map +1 -1
- package/lib/mjs/interfaces.js +17 -0
- package/lib/mjs/interfaces.js.map +1 -1
- package/lib/mjs/migration-environment.d.ts +70 -0
- package/lib/mjs/migration-environment.d.ts.map +1 -0
- package/lib/mjs/migration-environment.js +128 -0
- package/lib/mjs/migration-environment.js.map +1 -0
- package/lib/mjs/migration-runner.d.ts +108 -0
- package/lib/mjs/migration-runner.d.ts.map +1 -0
- package/lib/mjs/migration-runner.js +211 -0
- package/lib/mjs/migration-runner.js.map +1 -0
- package/lib/mjs/migration-service.d.ts +300 -0
- package/lib/mjs/migration-service.d.ts.map +1 -0
- package/lib/mjs/migration-service.js +824 -0
- package/lib/mjs/migration-service.js.map +1 -0
- package/lib/mjs/migration-sources.d.ts +85 -0
- package/lib/mjs/migration-sources.d.ts.map +1 -0
- package/lib/mjs/migration-sources.js +236 -0
- package/lib/mjs/migration-sources.js.map +1 -0
- package/lib/mjs/orm.d.ts +77 -15
- package/lib/mjs/orm.d.ts.map +1 -1
- package/lib/mjs/orm.js +158 -152
- package/lib/mjs/orm.js.map +1 -1
- package/lib/mjs/quoting.d.ts +33 -0
- package/lib/mjs/quoting.d.ts.map +1 -0
- package/lib/mjs/quoting.js +45 -0
- package/lib/mjs/quoting.js.map +1 -0
- package/lib/mjs/source-file.d.ts +10 -0
- package/lib/mjs/source-file.d.ts.map +1 -0
- package/lib/mjs/source-file.js +38 -0
- package/lib/mjs/source-file.js.map +1 -0
- package/lib/mjs/statements.d.ts +51 -1
- package/lib/mjs/statements.d.ts.map +1 -1
- package/lib/mjs/statements.js +71 -0
- package/lib/mjs/statements.js.map +1 -1
- package/lib/mjs/symbols.d.ts +11 -0
- package/lib/mjs/symbols.d.ts.map +1 -1
- package/lib/mjs/symbols.js +11 -0
- package/lib/mjs/symbols.js.map +1 -1
- package/lib/tsconfig.cjs.tsbuildinfo +1 -1
- package/lib/tsconfig.mjs.tsbuildinfo +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.MigrationRunner = exports.MIGRATION_FILE_REGEXP = void 0;
|
|
13
|
+
const log_common_1 = require("@spinajs/log-common");
|
|
14
|
+
const luxon_1 = require("luxon");
|
|
15
|
+
const symbols_js_1 = require("./symbols.js");
|
|
16
|
+
const descriptor_js_1 = require("./descriptor.js");
|
|
17
|
+
const exceptions_js_1 = require("./exceptions.js");
|
|
18
|
+
const migration_service_js_1 = require("./migration-service.js");
|
|
19
|
+
/**
|
|
20
|
+
* Re-exported from `./symbols.js` ( a leaf module ) rather than defined here, so this module can
|
|
21
|
+
* sit in `Orm`'s require cycle without closing it one hop earlier. This is still the import path
|
|
22
|
+
* the rest of the package - and `@spinajs/orm-cli` - use, and stays that way.
|
|
23
|
+
*/
|
|
24
|
+
var symbols_js_2 = require("./symbols.js");
|
|
25
|
+
Object.defineProperty(exports, "MIGRATION_FILE_REGEXP", { enumerable: true, get: function () { return symbols_js_2.MIGRATION_FILE_REGEXP; } });
|
|
26
|
+
/**
|
|
27
|
+
* Cross-connection orchestrator: validates and orders the migration registry, groups it by the
|
|
28
|
+
* connection each migration declared, and hands each group to that connection's
|
|
29
|
+
* `OrmMigrationService`. Everything that touches a database lives in the service; everything
|
|
30
|
+
* that spans connections lives here.
|
|
31
|
+
*/
|
|
32
|
+
class MigrationRunner {
|
|
33
|
+
constructor(orm) {
|
|
34
|
+
this.orm = orm;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Applies every pending migration on every configured connection, or only `name` when one is
|
|
38
|
+
* given, in `(created, name)` order.
|
|
39
|
+
*
|
|
40
|
+
* A `name` that matches nothing in the registry throws rather than returning `[]`: an empty
|
|
41
|
+
* result from a typo is indistinguishable from "already up to date", so the CLI would exit 0
|
|
42
|
+
* reporting "0 migrations applied" and the operator would believe the schema is current.
|
|
43
|
+
*/
|
|
44
|
+
async up(name, options) {
|
|
45
|
+
const executed = [];
|
|
46
|
+
for (const [driver, units] of this.plan(name, options?.force ?? true, options?.connection)) {
|
|
47
|
+
const service = await this.service(driver);
|
|
48
|
+
executed.push(...(await service.up(units, { fake: options?.fake })));
|
|
49
|
+
}
|
|
50
|
+
return executed;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Rolls the last applied batch back on every configured connection, or every batch with
|
|
54
|
+
* `{ all: true }`. `name` narrows the run to a single migration and - exactly like `up` - throws
|
|
55
|
+
* when the registry carries nothing by that name.
|
|
56
|
+
*
|
|
57
|
+
* KNOWN SHARP EDGE, `down(name)`: the service is handed a one-element unit list, and it treats
|
|
58
|
+
* every applied row in the target batch that has no matching unit as an orphan. So a named
|
|
59
|
+
* rollback warns that perfectly healthy, merely-unrequested migrations are "recorded as applied
|
|
60
|
+
* but no registered migration matches them (file deleted or renamed)" and advises restoring the
|
|
61
|
+
* file or removing the row by hand - guidance that is destructive if followed here, because
|
|
62
|
+
* nothing is actually wrong with those rows. The rollback itself is correct; only the warning
|
|
63
|
+
* lies. Fixing it means giving `IMigrationDownOptions` an "only these" notion, i.e. reshaping the
|
|
64
|
+
* service contract, so it is deliberately not done inside this facade.
|
|
65
|
+
*/
|
|
66
|
+
async down(name, options) {
|
|
67
|
+
const executed = [];
|
|
68
|
+
for (const [driver, units] of this.plan(name, options?.force ?? true, options?.connection)) {
|
|
69
|
+
const service = await this.service(driver);
|
|
70
|
+
executed.push(...(await service.down(units, { fake: options?.fake, all: options?.all })));
|
|
71
|
+
}
|
|
72
|
+
return executed;
|
|
73
|
+
}
|
|
74
|
+
async status() {
|
|
75
|
+
const entries = [];
|
|
76
|
+
// force: a status report that hid the connections with OnStartup off would answer "nothing
|
|
77
|
+
// to see" for exactly the connections somebody is most likely asking about
|
|
78
|
+
for (const [driver, units] of this.plan(undefined, true)) {
|
|
79
|
+
const service = await this.service(driver);
|
|
80
|
+
entries.push(...(await service.status(units)));
|
|
81
|
+
}
|
|
82
|
+
return entries;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Forces a migration's recorded state on whichever connection owns it - the escape hatch for a
|
|
86
|
+
* run that died halfway. The unit is handed down as well: the service cannot fingerprint a
|
|
87
|
+
* migration it was only given the name of, and a resolution without it leaves `Checksum` NULL
|
|
88
|
+
* forever, so drift is never detectable for that row again.
|
|
89
|
+
*/
|
|
90
|
+
async resolve(name, action) {
|
|
91
|
+
for (const [driver, units] of this.plan(name, true)) {
|
|
92
|
+
const unit = units.find((u) => u.name === name);
|
|
93
|
+
if (unit) {
|
|
94
|
+
return await (await this.service(driver)).resolve(name, action, unit);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// `plan()` already refused an unknown name, so reaching here means the class IS registered and
|
|
98
|
+
// `plan()` dropped it: the connection it declared is missing from this deployment's
|
|
99
|
+
// configuration, or it carries no `@Migration()` at all. Both left a warn naming it. Silently
|
|
100
|
+
// doing nothing would look like a successful resolve and leave the connection blocked
|
|
101
|
+
throw new exceptions_js_1.OrmException(`Migration ${name} is registered, but its connection is not configured ( or the class carries no @Migration('connection') decorator ) - nothing to resolve`);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The connection's configured `OrmMigrationService`, or the built-in one.
|
|
105
|
+
*/
|
|
106
|
+
async service(driver) {
|
|
107
|
+
const token = driver.Options.Migration?.Service;
|
|
108
|
+
return await driver.Container.resolve((token ?? migration_service_js_1.DefaultMigrationService), [driver]);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Validates every registered migration's name, orders the set, and groups it by the connection
|
|
112
|
+
* it declared - returning one ordered unit list per connection that is actually going to run.
|
|
113
|
+
*
|
|
114
|
+
* Keyed by driver rather than by connection name so aliases ( two names bound to the same
|
|
115
|
+
* `OrmDriver` ) collapse into one group instead of running the same migration twice.
|
|
116
|
+
*
|
|
117
|
+
* `name` narrows the set to one migration, and is the single place all three public entry
|
|
118
|
+
* points get their "that name is not registered" refusal from. `connection` narrows it to one
|
|
119
|
+
* connection - the two compose, and a `name` on a connection the filter excludes runs nothing.
|
|
120
|
+
*/
|
|
121
|
+
plan(name, force, connection) {
|
|
122
|
+
// `ClassInfo.name` is the migration's identity everywhere else in the system - it is what the
|
|
123
|
+
// filter below matches, what becomes `IMigrationUnit.name`, and what lands in the `Migration`
|
|
124
|
+
// column the service compares its rows against. So `m.name` ( never `m.type.name`, which
|
|
125
|
+
// `Orm.registerMigration` copies it from and which is therefore only *usually* the same ) is
|
|
126
|
+
// what gets validated, reported and recorded here - one value, so the three cannot disagree.
|
|
127
|
+
const source = name ? this.orm.Migrations.filter((m) => m.name === name) : this.orm.Migrations;
|
|
128
|
+
// an explicitly named migration that matches nothing is a typo, not an empty run. Returning
|
|
129
|
+
// [] would let `migrate-up --name Ceate_2021_01_01_00_00_00` exit 0 reporting "0 migrations
|
|
130
|
+
// applied", leaving the operator believing the schema is current - the same reason `resolve()`
|
|
131
|
+
// refuses to no-op below
|
|
132
|
+
if (name && source.length === 0) {
|
|
133
|
+
throw new exceptions_js_1.OrmException(`Migration ${name} is not registered - check the name for typos`);
|
|
134
|
+
}
|
|
135
|
+
const units = source
|
|
136
|
+
.map((m) => {
|
|
137
|
+
const match = m.name.match(symbols_js_1.MIGRATION_FILE_REGEXP);
|
|
138
|
+
const created = match && match.length === 3 ? luxon_1.DateTime.fromFormat(match[2], 'yyyy_MM_dd_HH_mm_ss') : null;
|
|
139
|
+
// a migration whose name carries no timestamp cannot be placed in the order, and a
|
|
140
|
+
// half-ordered run applies schema changes in an order nobody described - so the whole
|
|
141
|
+
// set is refused rather than the one entry skipped
|
|
142
|
+
if (!created || !created.isValid) {
|
|
143
|
+
throw new exceptions_js_1.OrmException(`Migration ${m.name} has invalid name format - expected some_name_yyyy_MM_dd_HH_mm_ss`);
|
|
144
|
+
}
|
|
145
|
+
return { name: m.name, created, type: m.type };
|
|
146
|
+
})
|
|
147
|
+
// timestamp first, then name: two migrations generated in the same second are otherwise
|
|
148
|
+
// ordered by whatever the registry happened to hold, which differs between a file-scan
|
|
149
|
+
// boot and a programmatic registration. Equal on both = 0, so the sort stays stable
|
|
150
|
+
.sort((a, b) => (a.created < b.created ? -1 : a.created > b.created ? 1 : a.name.localeCompare(b.name)));
|
|
151
|
+
// Resolved to a DRIVER rather than compared as a string, because that is what the groups
|
|
152
|
+
// below are keyed by: `db.Aliases` binds several names to one `OrmDriver`, so
|
|
153
|
+
// `--connection <alias>` and `--connection <the name it points at>` have to select the same
|
|
154
|
+
// group. Comparing the migration's declared `@Migration('...')` name instead would make those
|
|
155
|
+
// two filters disagree about a connection that is one connection.
|
|
156
|
+
let only;
|
|
157
|
+
if (connection !== undefined) {
|
|
158
|
+
only = this.orm.Connections.get(connection);
|
|
159
|
+
// refused for the same reason an unregistered migration name is: a filter that matches
|
|
160
|
+
// nothing would let `migrate-up --connection typo` exit 0 reporting "0 migrations applied",
|
|
161
|
+
// and the operator would believe the schema is current
|
|
162
|
+
if (!only) {
|
|
163
|
+
throw new exceptions_js_1.OrmException(`Connection ${connection} is not configured - check the name for typos ( configured: ${[...this.orm.Connections.keys()].join(', ') || 'none'} )`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const groups = new Map();
|
|
167
|
+
const gated = new Set();
|
|
168
|
+
for (const u of units) {
|
|
169
|
+
// chain-walking, deliberately: a subclass is still the same migration on the same
|
|
170
|
+
// connection whether or not it re-declares @Migration() - unlike Env ( own-only, see
|
|
171
|
+
// Orm.discoverMigrations() ), Connection is meant to be inherited.
|
|
172
|
+
const md = (0, descriptor_js_1.extractMigrationDescriptor)(u.type);
|
|
173
|
+
// none of the three skips below throws: a connection missing from this deployment's
|
|
174
|
+
// configuration, or switched off for startup, is a normal state - taking the whole boot
|
|
175
|
+
// down over it would be worse than running what can be run
|
|
176
|
+
if (!md?.Connection) {
|
|
177
|
+
this.Log.warn(`Migration ${u.name} has no connection assigned ( missing @Migration('connection') decorator ) and is skipped`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const driver = this.orm.Connections.get(md.Connection);
|
|
181
|
+
if (!driver) {
|
|
182
|
+
this.Log.warn(`Connection ${md.Connection} not exists for migration ${u.name} - migration is skipped`);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
// silently, and before the OnStartup gate below: the operator asked for one connection, so
|
|
186
|
+
// a line per migration on every OTHER connection is noise, and the gate warning in
|
|
187
|
+
// particular would name connections this run was never going to touch
|
|
188
|
+
if (only && driver !== only) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (!driver.Options.Migration?.OnStartup && !force) {
|
|
192
|
+
// the gate belongs to the connection, not to the migration, so it is reported once -
|
|
193
|
+
// repeating it per migration turns a boot log into noise nobody reads
|
|
194
|
+
if (!gated.has(driver)) {
|
|
195
|
+
gated.add(driver);
|
|
196
|
+
this.Log.warn(`Migration for connection ${md.Connection} is disabled on startup, please check conf file for db.[connection].Migration.OnStartup property`);
|
|
197
|
+
}
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const group = groups.get(driver);
|
|
201
|
+
if (group) {
|
|
202
|
+
group.push(u);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
groups.set(driver, [u]);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return [...groups.entries()];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
exports.MigrationRunner = MigrationRunner;
|
|
212
|
+
__decorate([
|
|
213
|
+
(0, log_common_1.Logger)('ORM'),
|
|
214
|
+
__metadata("design:type", log_common_1.Log)
|
|
215
|
+
], MigrationRunner.prototype, "Log", void 0);
|
|
216
|
+
//# sourceMappingURL=migration-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration-runner.js","sourceRoot":"","sources":["../../src/migration-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { Class } from '@spinajs/di';
|
|
2
|
+
import { Log } from '@spinajs/log-common';
|
|
3
|
+
import { DateTime } from 'luxon';
|
|
4
|
+
import type { TableQueryBuilder } from './builders.js';
|
|
5
|
+
import { OrmDriver } from './driver.js';
|
|
6
|
+
import { MigrationTransactionMode, OrmMigration } from './interfaces.js';
|
|
7
|
+
export declare const MIGRATION_TABLE_NAME = "spinajs_migration";
|
|
8
|
+
export declare const MIGRATION_LOCK_POLL_INTERVAL = 500;
|
|
9
|
+
export declare const MIGRATION_LOCK_TIMEOUT = 30000;
|
|
10
|
+
export declare const MIGRATION_LOCK_STALE_AFTER = 600000;
|
|
11
|
+
/**
|
|
12
|
+
* How many times one `acquireLock()` call may remove a lock row it judged stale. A steal is
|
|
13
|
+
* not proof the row is gone - a DELETE can succeed and remove nothing - so without a cap the
|
|
14
|
+
* stale branch is free to warn and retry forever.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MIGRATION_LOCK_MAX_STEALS = 3;
|
|
17
|
+
export type MigrationResolveAction = 'applied' | 'rolled-back';
|
|
18
|
+
/**
|
|
19
|
+
* One row of the migration tracking table.
|
|
20
|
+
*/
|
|
21
|
+
export interface IMigrationRecord {
|
|
22
|
+
Migration: string;
|
|
23
|
+
CreatedAt: Date;
|
|
24
|
+
StartedAt: Date;
|
|
25
|
+
FinishedAt: Date | null;
|
|
26
|
+
RolledBackAt: Date | null;
|
|
27
|
+
Logs: string | null;
|
|
28
|
+
Checksum: string | null;
|
|
29
|
+
Batch: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A migration class paired with the timestamp parsed out of its name.
|
|
33
|
+
*/
|
|
34
|
+
export interface IMigrationUnit {
|
|
35
|
+
name: string;
|
|
36
|
+
created: DateTime;
|
|
37
|
+
type: Class<OrmMigration>;
|
|
38
|
+
}
|
|
39
|
+
export interface IMigrationRunOptions {
|
|
40
|
+
/**
|
|
41
|
+
* Record the migration as applied without running its `up()`.
|
|
42
|
+
*/
|
|
43
|
+
fake?: boolean;
|
|
44
|
+
}
|
|
45
|
+
export interface IMigrationDownOptions extends IMigrationRunOptions {
|
|
46
|
+
/**
|
|
47
|
+
* Roll every applied migration back instead of only the last batch.
|
|
48
|
+
*/
|
|
49
|
+
all?: boolean;
|
|
50
|
+
}
|
|
51
|
+
export interface IMigrationStatusEntry {
|
|
52
|
+
name: string;
|
|
53
|
+
connection: string;
|
|
54
|
+
applied: boolean;
|
|
55
|
+
failed: boolean;
|
|
56
|
+
rolledBack: boolean;
|
|
57
|
+
pending: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* The row was opened by a run that never reached either outcome: `StartedAt` is set while both
|
|
60
|
+
* `FinishedAt` and `Logs` are NULL, and no run is currently holding the migration lock. A
|
|
61
|
+
* process killed between the start and the outcome ( OOM, SIGKILL, a lost connection that took
|
|
62
|
+
* the failure write down with it ) leaves exactly this.
|
|
63
|
+
*
|
|
64
|
+
* Orthogonal to `pending`, like `rolledBack`: such a migration IS pending and the next `up()`
|
|
65
|
+
* WILL re-run it from the top. What the flag adds is that nobody knows how much of it already
|
|
66
|
+
* reached the database. It never blocks a run - see `warnOnInterrupted` for why.
|
|
67
|
+
*/
|
|
68
|
+
interrupted: boolean;
|
|
69
|
+
batch: number | null;
|
|
70
|
+
startedAt: Date | null;
|
|
71
|
+
finishedAt: Date | null;
|
|
72
|
+
checksumMismatch: boolean;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Fingerprint of a migration's source, used to detect a migration that was edited
|
|
76
|
+
* after it had already been applied.
|
|
77
|
+
*/
|
|
78
|
+
export declare function migrationChecksum(type: Class<OrmMigration>): string;
|
|
79
|
+
/**
|
|
80
|
+
* Per-connection migration execution contract. Configure an alternative
|
|
81
|
+
* implementation with db.Connections[n].Migration.Service (DI token).
|
|
82
|
+
*/
|
|
83
|
+
export declare abstract class OrmMigrationService {
|
|
84
|
+
protected driver: OrmDriver;
|
|
85
|
+
constructor(driver: OrmDriver);
|
|
86
|
+
/**
|
|
87
|
+
* Creates or upgrades the tracking tables this connection needs.
|
|
88
|
+
*
|
|
89
|
+
* NOTE on what is NOT here: `applied()`. It was part of this contract and had no production
|
|
90
|
+
* caller - `status()` answers "what is applied", per unit and per connection, and is what the
|
|
91
|
+
* runner, the CLI and every deploy gate go through. An abstract method that every custom
|
|
92
|
+
* implementation must write and nothing ever calls is a tax with no payer, so it is a concrete
|
|
93
|
+
* helper on `DefaultMigrationService` instead.
|
|
94
|
+
*/
|
|
95
|
+
abstract ensureStorage(): Promise<void>;
|
|
96
|
+
abstract up(units: IMigrationUnit[], options?: IMigrationRunOptions): Promise<OrmMigration[]>;
|
|
97
|
+
abstract down(units: IMigrationUnit[], options?: IMigrationDownOptions): Promise<OrmMigration[]>;
|
|
98
|
+
abstract status(units: IMigrationUnit[]): Promise<IMigrationStatusEntry[]>;
|
|
99
|
+
/**
|
|
100
|
+
* Forces a migration's recorded state without running it - the escape hatch for a
|
|
101
|
+
* run that died halfway and left the table lying.
|
|
102
|
+
*
|
|
103
|
+
* `unit` is optional so callers that only know a name (the CLI, the runner facade) keep
|
|
104
|
+
* working; passing it lets an `'applied'` resolution stamp the checksum as a real run would.
|
|
105
|
+
*/
|
|
106
|
+
abstract resolve(name: string, action: MigrationResolveAction, unit?: IMigrationUnit): Promise<void>;
|
|
107
|
+
}
|
|
108
|
+
export declare class DefaultMigrationService extends OrmMigrationService {
|
|
109
|
+
protected Log: Log;
|
|
110
|
+
protected get table(): string;
|
|
111
|
+
protected get lockTable(): string;
|
|
112
|
+
/**
|
|
113
|
+
* Creates `name` unless it is already there, tolerating a second process that creates it in
|
|
114
|
+
* the window between the probe and the CREATE.
|
|
115
|
+
*
|
|
116
|
+
* That window cannot be closed with a lock: the lock table is one of the tables being created
|
|
117
|
+
* here, so it cannot guard its own creation. Two processes booting together therefore both see
|
|
118
|
+
* "absent" and both issue a CREATE, and the loser must not take the whole boot down with it.
|
|
119
|
+
* Only a table that really is present afterwards excuses the failure - anything else ( no
|
|
120
|
+
* permission, bad DDL, dead connection ) is a genuine error and is rethrown.
|
|
121
|
+
*
|
|
122
|
+
* Returns true when the table was *absent at probe time* - which is not the same as "this
|
|
123
|
+
* process created it", since the lost-race path returns true too. Callers use it to skip the
|
|
124
|
+
* legacy upgrade path: a table that appeared inside the race window was created by a peer
|
|
125
|
+
* running this same DDL, so it already carries the current shape.
|
|
126
|
+
*/
|
|
127
|
+
protected createTableIfAbsent(name: string, columns: (t: TableQueryBuilder) => void): Promise<boolean>;
|
|
128
|
+
ensureStorage(): Promise<void>;
|
|
129
|
+
protected records(): Promise<IMigrationRecord[]>;
|
|
130
|
+
/**
|
|
131
|
+
* Fills the columns the upgrade above has just added. A row written before they existed carries
|
|
132
|
+
* nothing but `CreatedAt`, and a NULL `FinishedAt` reads as "never applied" - so without this
|
|
133
|
+
* every migration the deployment ran years ago would run again over a schema that already has
|
|
134
|
+
* it. `CreatedAt` is the only timestamp such a row has, so it is treated as both start and
|
|
135
|
+
* finish.
|
|
136
|
+
*
|
|
137
|
+
* Row by row through the update builder rather than as three set-based `UPDATE`s, and that is
|
|
138
|
+
* the point of the method: a set-based statement has to name the table itself, and the only
|
|
139
|
+
* way to do that here is raw SQL. `Migration.Table` is configuration - a name that needs
|
|
140
|
+
* quoting ( a reserved word, a dot, a space ) would then break this path alone, and only on a
|
|
141
|
+
* deployment that already has rows, which is the least reachable corner in the file. The
|
|
142
|
+
* builder quotes it exactly as every other statement in this class does. The cost is one UPDATE
|
|
143
|
+
* per legacy row, on the single boot that performs the upgrade and never again.
|
|
144
|
+
*/
|
|
145
|
+
protected backfillLegacyRows(): Promise<void>;
|
|
146
|
+
/**
|
|
147
|
+
* Migrations that finished successfully and were not rolled back - the raw rows, unmerged with
|
|
148
|
+
* the registry.
|
|
149
|
+
*
|
|
150
|
+
* A convenience on this class rather than part of `OrmMigrationService`: nothing in the ORM,
|
|
151
|
+
* the runner or the CLI calls it, because they all need the registry merged in and go through
|
|
152
|
+
* `status()`. It is kept because a subclass, a script or a health check reaching for "what does
|
|
153
|
+
* this connection think it has applied?" should not have to reimplement the applied-gate, and
|
|
154
|
+
* getting that gate subtly wrong ( "a row exists" rather than the FinishedAt NOT NULL and
|
|
155
|
+
* RolledBackAt NULL pair ) is the classic way to re-run a migration.
|
|
156
|
+
*/
|
|
157
|
+
applied(): Promise<IMigrationRecord[]>;
|
|
158
|
+
/**
|
|
159
|
+
* Opens a migration's row: a fresh one, or a reset of whatever a previous failed or
|
|
160
|
+
* rolled-back attempt left behind.
|
|
161
|
+
*/
|
|
162
|
+
protected upsertStart(name: string, existing: IMigrationRecord | undefined): Promise<void>;
|
|
163
|
+
/**
|
|
164
|
+
* Closes a migration's row as applied. The batch number is stamped here rather than at
|
|
165
|
+
* insert time, so a row that never finishes carries no batch to be rolled back later.
|
|
166
|
+
*/
|
|
167
|
+
protected markFinished(name: string, batch: number, checksum: string): Promise<void>;
|
|
168
|
+
/**
|
|
169
|
+
* Records why a migration died. Failed state is `FinishedAt` NULL *and* `Logs` set - the pair
|
|
170
|
+
* `assertNoFailed` matches on - so this write establishes both rather than assuming the row
|
|
171
|
+
* already carries a NULL `FinishedAt`.
|
|
172
|
+
*
|
|
173
|
+
* It cannot assume it: a migration that was applied and later rolled back is pending again
|
|
174
|
+
* while still holding the old `FinishedAt`/`RolledBackAt` timestamps, and the reset
|
|
175
|
+
* `upsertStart` issued for the retry is inside the transaction that just unwound. Writing only
|
|
176
|
+
* `Logs` would leave `FinishedAt` set, and a half-applied migration would slip past the block.
|
|
177
|
+
*/
|
|
178
|
+
protected markFailed(name: string, err: Error): Promise<void>;
|
|
179
|
+
/**
|
|
180
|
+
* A half-applied migration means the database is in a state nobody described. Refuse to
|
|
181
|
+
* pile more schema changes on top of it.
|
|
182
|
+
*/
|
|
183
|
+
protected assertNoFailed(records: IMigrationRecord[]): void;
|
|
184
|
+
/**
|
|
185
|
+
* The shape of a row whose run never reached an outcome: `StartedAt`, written by `upsertStart`,
|
|
186
|
+
* and neither of the two writes that close it - `markFinished`'s `FinishedAt` or `markFailed`'s
|
|
187
|
+
* `Logs`. Nothing in this class produces it deliberately; a process killed between the start and
|
|
188
|
+
* the outcome does.
|
|
189
|
+
*
|
|
190
|
+
* `RolledBackAt` is excluded on purpose. `resolve('rolled-back')` also leaves `FinishedAt` and
|
|
191
|
+
* `Logs` NULL with `StartedAt` set, and that row is pending because somebody said so - not
|
|
192
|
+
* abandoned.
|
|
193
|
+
*
|
|
194
|
+
* The predicate says nothing about how much of the migration reached the database. It says only
|
|
195
|
+
* that nobody recorded the answer, which is exactly why it is worth surfacing.
|
|
196
|
+
*/
|
|
197
|
+
protected isInterrupted(rec: IMigrationRecord): boolean;
|
|
198
|
+
/**
|
|
199
|
+
* Is a migration run in flight on this connection right now? Read, never acquired: the caller is
|
|
200
|
+
* `status()`, which must not block behind the run it is reporting on.
|
|
201
|
+
*
|
|
202
|
+
* The lock row is the only honest signal available, and it is judged exactly as `acquireLock`
|
|
203
|
+
* judges it - a row younger than `StaleAfter` means somebody is inside a run, an older one means
|
|
204
|
+
* the holder is presumed dead. Freshness rather than mere presence is what makes this usable
|
|
205
|
+
* here: a process killed mid-migration leaves BOTH its open tracking row and its lock row
|
|
206
|
+
* behind, so "a lock row exists" would hide every crash this is meant to surface, permanently.
|
|
207
|
+
*
|
|
208
|
+
* Two deliberate consequences. For `StaleAfter` after a crash the answer is "running" and the
|
|
209
|
+
* open row is not yet reported as interrupted - the same window in which `acquireLock` still
|
|
210
|
+
* waits for the holder, and with the same client-clock caveat documented there. And
|
|
211
|
+
* `Lock.Enabled: false` removes the signal altogether, so the answer is "not running": an open
|
|
212
|
+
* row then always reads as interrupted, which is right for the crash and wrong only for a report
|
|
213
|
+
* taken while a run is genuinely in progress.
|
|
214
|
+
*/
|
|
215
|
+
protected runInProgress(): Promise<boolean>;
|
|
216
|
+
/**
|
|
217
|
+
* Warns about every migration this run is about to re-run whose row says a previous attempt was
|
|
218
|
+
* started and never closed. No lock check is needed here, unlike in `status()`: this runs inside
|
|
219
|
+
* `withLock`, so the only run in flight on this connection is this one.
|
|
220
|
+
*
|
|
221
|
+
* It warns rather than blocks, and that is a judgement call worth stating. The row records that
|
|
222
|
+
* a run STARTED, not that anything reached the database, so blocking would escalate "unknown" to
|
|
223
|
+
* "refuse to migrate" - and it would do so for the common, harmless shapes too: an idempotent
|
|
224
|
+
* `CREATE TABLE` that had not run yet, or any migration on a `PerMigration` / `PerRun`
|
|
225
|
+
* connection, whose transaction unwound the partial work when the process died. In those cases
|
|
226
|
+
* re-running from the top is exactly right, and a block would turn every OOM kill during a long
|
|
227
|
+
* migration into an operator ticket.
|
|
228
|
+
*
|
|
229
|
+
* The case that is genuinely dangerous is `Transaction.Mode: None` ( the default ) plus
|
|
230
|
+
* non-idempotent DML: half the INSERTs are already in, nothing recorded which half, and the
|
|
231
|
+
* re-run applies them again. Non-idempotent DDL is the recoverable version of the same thing -
|
|
232
|
+
* it fails, and the failed row then blocks properly. Neither is detectable from here, so the
|
|
233
|
+
* warning describes them and leaves the decision with the operator, who is also the only party
|
|
234
|
+
* that can look at the data.
|
|
235
|
+
*/
|
|
236
|
+
protected warnOnInterrupted(records: IMigrationRecord[], pending: IMigrationUnit[]): void;
|
|
237
|
+
protected transactionMode(): MigrationTransactionMode;
|
|
238
|
+
/**
|
|
239
|
+
* True when this migration must run outside any wrapping transaction ( TypeORM parity:
|
|
240
|
+
* `public transaction = false` on the migration class - needed for DDL that cannot be
|
|
241
|
+
* transacted, such as MySQL index rebuilds ).
|
|
242
|
+
*
|
|
243
|
+
* That declaration is an *instance* field, assigned in the constructor, so it never reaches
|
|
244
|
+
* the prototype - the resolved instance is the only place it can be read from. A prototype
|
|
245
|
+
* getter or a static property is honoured too, so a migration may also opt out without
|
|
246
|
+
* being constructed.
|
|
247
|
+
*/
|
|
248
|
+
protected optedOutOfTransaction(u: IMigrationUnit, instance?: OrmMigration): boolean;
|
|
249
|
+
/**
|
|
250
|
+
* Advisory only: transpilation differences move the checksum as readily as an edit does,
|
|
251
|
+
* so this warns and never blocks.
|
|
252
|
+
*/
|
|
253
|
+
protected warnOnChecksumDrift(u: IMigrationUnit, records: IMigrationRecord[]): void;
|
|
254
|
+
protected lockOptions(): {
|
|
255
|
+
enabled: boolean;
|
|
256
|
+
timeout: number;
|
|
257
|
+
staleAfter: number;
|
|
258
|
+
};
|
|
259
|
+
/**
|
|
260
|
+
* Identity written into the lock row. It exists to answer "who is holding this?" when a run
|
|
261
|
+
* blocks, so it has to survive being read on another machine.
|
|
262
|
+
*/
|
|
263
|
+
protected lockOwner(): string;
|
|
264
|
+
/**
|
|
265
|
+
* Takes the single row of the lock table, waiting for whoever has it.
|
|
266
|
+
*
|
|
267
|
+
* The row is claimed by INSERT rather than by "SELECT then INSERT": `Id` is unique, so the
|
|
268
|
+
* database decides the winner in one statement and two processes racing here cannot both
|
|
269
|
+
* succeed. A refused insert is therefore read as "somebody else holds it" - which is also why
|
|
270
|
+
* the holder is re-read afterwards rather than guessed at.
|
|
271
|
+
*
|
|
272
|
+
* Staleness is judged against the *client* clock: `AcquiredAt` is written here as
|
|
273
|
+
* `new Date()` and compared to this host's `Date.now()`. That is sound for the case this
|
|
274
|
+
* lock is built for - one process migrating, crashing, and restarting to find its own
|
|
275
|
+
* abandoned row - but on hosts whose clocks disagree the window is off by the skew, which
|
|
276
|
+
* shows up as stealing too early or waiting too long. Stamping `AcquiredAt` from the
|
|
277
|
+
* database ( a driver-level `CURRENT_TIMESTAMP` default and a server-side comparison ) would
|
|
278
|
+
* remove the assumption; it needs dialect support that does not exist here yet.
|
|
279
|
+
*/
|
|
280
|
+
protected acquireLock(): Promise<void>;
|
|
281
|
+
/**
|
|
282
|
+
* Drops the lock row unconditionally rather than only the row this process wrote. A run whose
|
|
283
|
+
* lock was stolen as stale would otherwise have nothing to release, and the alternative -
|
|
284
|
+
* deleting only `Owner = ours` - leaves the table holding a row nobody will clear if the owner
|
|
285
|
+
* string ever changes underneath a run. Losing a stolen lock is the lesser harm: the thief
|
|
286
|
+
* already assumed the run was dead.
|
|
287
|
+
*/
|
|
288
|
+
protected releaseLock(): Promise<void>;
|
|
289
|
+
/**
|
|
290
|
+
* Concurrency guard around a whole run: one migration run per connection at a time, across
|
|
291
|
+
* processes. Note the release is `finally` - a run that throws must not leave the connection
|
|
292
|
+
* locked until the staleness window expires.
|
|
293
|
+
*/
|
|
294
|
+
protected withLock<R>(fn: () => Promise<R>): Promise<R>;
|
|
295
|
+
up(units: IMigrationUnit[], options?: IMigrationRunOptions): Promise<OrmMigration[]>;
|
|
296
|
+
down(units: IMigrationUnit[], options?: IMigrationDownOptions): Promise<OrmMigration[]>;
|
|
297
|
+
status(units: IMigrationUnit[]): Promise<IMigrationStatusEntry[]>;
|
|
298
|
+
resolve(name: string, action: MigrationResolveAction, unit?: IMigrationUnit): Promise<void>;
|
|
299
|
+
}
|
|
300
|
+
//# sourceMappingURL=migration-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration-service.d.ts","sourceRoot":"","sources":["../../src/migration-service.ts"],"names":[],"mappings":"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"}
|