db-migrate 1.3.0 → 1.4.0
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/api.js +9 -0
- package/lib/commands/generated.js +2 -1
- package/lib/commands/work.js +33 -0
- package/lib/dml.js +458 -101
- package/lib/driver/index.js +2 -0
- package/lib/jobs.js +177 -0
- package/lib/walker.js +51 -3
- package/lib/work.js +260 -0
- package/package.json +2 -2
package/lib/driver/index.js
CHANGED
|
@@ -166,6 +166,8 @@ exports.connect = function (config, intern, callback) {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
trackSql(db);
|
|
169
|
+
// the dialect of parameters in all, $1 instead of ? for pg
|
|
170
|
+
db._dbmDriver = typeof config.driver === 'string' ? config.driver : null;
|
|
169
171
|
callback(null, db);
|
|
170
172
|
}
|
|
171
173
|
);
|
package/lib/jobs.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const log = require('db-migrate-shared').log;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Background migrations, dml migrations with _meta.background, are not run
|
|
8
|
+
* by up, but registered as jobs. Workers started by executeWork take the
|
|
9
|
+
* jobs one by one and run them. A migration counts as run once its job is
|
|
10
|
+
* done, until then it is running.
|
|
11
|
+
*
|
|
12
|
+
* All jobs live in one row of the state table, changed by compare and swap
|
|
13
|
+
* only, so any number of workers can work on them at once:
|
|
14
|
+
*
|
|
15
|
+
* s: queued, running or failed
|
|
16
|
+
* blocking: later jobs wait until this one is done
|
|
17
|
+
* ID: the worker running it, n: renewed by every write of the worker, a
|
|
18
|
+
* job whose n does not change for the timeout is taken over
|
|
19
|
+
* step, learned, done, rb: the progress of the run, like for migrations run
|
|
20
|
+
* in the foreground, see State
|
|
21
|
+
* h: hash of the migration file when it started
|
|
22
|
+
* err: why it failed
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const JOBS = '__dbmigrate_jobs__';
|
|
26
|
+
|
|
27
|
+
const nonce = () => crypto.randomBytes(8).toString('hex');
|
|
28
|
+
|
|
29
|
+
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
30
|
+
|
|
31
|
+
const read = async (pdriver, internals) => {
|
|
32
|
+
const row = await pdriver._getKV(internals.migrationState, JOBS);
|
|
33
|
+
return { row, jobs: row ? JSON.parse(row.value).jobs : {} };
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const list = async (pdriver, internals) => (await read(pdriver, internals)).jobs;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Changes the jobs by mutate, retried until no other worker wrote in
|
|
40
|
+
* between. mutate returns false to leave them as they are.
|
|
41
|
+
*/
|
|
42
|
+
const change = async (pdriver, internals, mutate) => {
|
|
43
|
+
for (let attempt = 0; ; attempt++) {
|
|
44
|
+
const { row, jobs } = await read(pdriver, internals);
|
|
45
|
+
|
|
46
|
+
if (mutate(jobs) === false) {
|
|
47
|
+
return jobs;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const value = JSON.stringify({ jobs, n: nonce() });
|
|
51
|
+
|
|
52
|
+
if (!row) {
|
|
53
|
+
try {
|
|
54
|
+
await pdriver._insertKV(internals.migrationState, JOBS, value);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
// inserted by another worker first, try again with theirs
|
|
57
|
+
log.verbose('[jobs] insert failed', err.message);
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
await pdriver._updateKVC(
|
|
61
|
+
internals.migrationState,
|
|
62
|
+
JOBS,
|
|
63
|
+
value,
|
|
64
|
+
'value',
|
|
65
|
+
row.value
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const after = await pdriver._getKV(internals.migrationState, JOBS);
|
|
70
|
+
if (after && after.value === value) {
|
|
71
|
+
return jobs;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (attempt > 50) {
|
|
75
|
+
throw new Error('[jobs] could not change the jobs, too much contention');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await delay(Math.floor(Math.random() * 20 * Math.min(attempt + 1, 10)));
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Registers a migration as job, or a failed one again. A job which failed
|
|
84
|
+
* with irreversible steps keeps its progress, so it continues after them.
|
|
85
|
+
*/
|
|
86
|
+
const register = async (pdriver, internals, file) => {
|
|
87
|
+
const blocking = (file.get()._meta || {}).blocking === true;
|
|
88
|
+
|
|
89
|
+
await change(pdriver, internals, jobs => {
|
|
90
|
+
const job = jobs[file.name];
|
|
91
|
+
|
|
92
|
+
if (job && job.s !== 'failed') {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
jobs[file.name] = Object.assign(
|
|
97
|
+
{ step: 0, learned: 0, done: 0, rb: 0 },
|
|
98
|
+
job,
|
|
99
|
+
{ s: 'queued', blocking, ID: 0, n: nonce(), err: undefined }
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const mig = await pdriver._getKV(internals.migrationState, file.name);
|
|
104
|
+
if (!mig) {
|
|
105
|
+
await pdriver._insertKV(internals.migrationState, file.name, '{}');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
log.info(`[jobs] ${file.name} runs in the background`);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Takes the first job to run, in the order of their names. A running job is
|
|
113
|
+
* taken over if it did not change for timeout ms, measured on our clock in
|
|
114
|
+
* seen. Jobs after a blocking job wait until it is done.
|
|
115
|
+
*
|
|
116
|
+
* Resolves to the name of the job taken, or null.
|
|
117
|
+
*/
|
|
118
|
+
const claim = async (pdriver, internals, id, seen, timeout) => {
|
|
119
|
+
const now = Date.now();
|
|
120
|
+
let taken = null;
|
|
121
|
+
|
|
122
|
+
await change(pdriver, internals, jobs => {
|
|
123
|
+
taken = null;
|
|
124
|
+
|
|
125
|
+
for (const name of Object.keys(jobs).sort()) {
|
|
126
|
+
const job = jobs[name];
|
|
127
|
+
let free = job.s === 'queued';
|
|
128
|
+
|
|
129
|
+
if (job.s === 'running' && job.ID !== id) {
|
|
130
|
+
const last = seen[name];
|
|
131
|
+
|
|
132
|
+
if (!last || last.n !== job.n) {
|
|
133
|
+
seen[name] = { n: job.n, since: now };
|
|
134
|
+
} else if (now - last.since >= timeout) {
|
|
135
|
+
log.warn(
|
|
136
|
+
`[jobs] taking over ${name}, its worker did not respond for ` +
|
|
137
|
+
`${timeout} ms`
|
|
138
|
+
);
|
|
139
|
+
free = true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (free) {
|
|
144
|
+
Object.assign(job, { s: 'running', ID: id, n: nonce() });
|
|
145
|
+
taken = name;
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (job.blocking) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return false;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
return taken;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Changes a job we run. If another worker took it over in between, lost is
|
|
162
|
+
* called and nothing is changed.
|
|
163
|
+
*/
|
|
164
|
+
const update = (pdriver, internals, name, id, mutate, lost = () => {}) =>
|
|
165
|
+
change(pdriver, internals, jobs => {
|
|
166
|
+
const job = jobs[name];
|
|
167
|
+
|
|
168
|
+
if (!job || job.ID !== id) {
|
|
169
|
+
lost();
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
mutate(job, jobs);
|
|
174
|
+
job.n = nonce();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
module.exports = { JOBS, list, register, claim, update };
|
package/lib/walker.js
CHANGED
|
@@ -5,6 +5,8 @@ const State = require('./state');
|
|
|
5
5
|
const log = require('db-migrate-shared').log;
|
|
6
6
|
const Promise = require('bluebird');
|
|
7
7
|
const File = require('./file.js');
|
|
8
|
+
const Dml = require('./dml');
|
|
9
|
+
const Jobs = require('./jobs');
|
|
8
10
|
|
|
9
11
|
const INTERFACES = {
|
|
10
12
|
migration: require('./interface/migratorInterface.js'),
|
|
@@ -133,6 +135,46 @@ Walker.prototype = {
|
|
|
133
135
|
}
|
|
134
136
|
},
|
|
135
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Migrations running in the background are left out, until their job is
|
|
140
|
+
* done. Failed ones are run, which registers them again.
|
|
141
|
+
*/
|
|
142
|
+
_withoutJobs: async function (toRun) {
|
|
143
|
+
if (this.prefix !== 'migration' || !this._pdriver || typeof this._pdriver._getKV !== 'function') {
|
|
144
|
+
return toRun;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const jobs = await Jobs.list(this._pdriver, this.internals);
|
|
148
|
+
|
|
149
|
+
return toRun.filter(file => {
|
|
150
|
+
const job = jobs[file.name];
|
|
151
|
+
|
|
152
|
+
if (job && job.s !== 'failed') {
|
|
153
|
+
log.info(`${this.title}${file.name} is running in the background`);
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return true;
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
_registerJob: async function (file) {
|
|
162
|
+
Dml.checkType(file);
|
|
163
|
+
|
|
164
|
+
if (this.internals.dryRun) {
|
|
165
|
+
log.info(`${this.title}${file.name} would run in the background`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!State.supportsLock(this._pdriver)) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
'Background migrations need a driver supporting the migration lock'
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return Jobs.register(this._pdriver, this.internals, file);
|
|
176
|
+
},
|
|
177
|
+
|
|
136
178
|
writeMigrationRecord: function (migration, callback) {
|
|
137
179
|
const onComplete = err => {
|
|
138
180
|
if (err) {
|
|
@@ -201,9 +243,11 @@ Walker.prototype = {
|
|
|
201
243
|
this._driver,
|
|
202
244
|
this.internals
|
|
203
245
|
)
|
|
204
|
-
])
|
|
205
|
-
|
|
206
|
-
|
|
246
|
+
])
|
|
247
|
+
.spread((allFiles, completedFiles) =>
|
|
248
|
+
dbmUtil.filterUp(allFiles, completedFiles, partialName, count)
|
|
249
|
+
)
|
|
250
|
+
.then(toRun => this._withoutJobs(toRun));
|
|
207
251
|
|
|
208
252
|
if (this.internals.check) {
|
|
209
253
|
return determine()
|
|
@@ -219,6 +263,10 @@ Walker.prototype = {
|
|
|
219
263
|
|
|
220
264
|
return Promise.each(toRun, file => {
|
|
221
265
|
log.verbose(this.title + 'preparing to run up:', file.name);
|
|
266
|
+
if (Dml.isBackground(file)) {
|
|
267
|
+
return this._registerJob(file).catch(failedIn(file));
|
|
268
|
+
}
|
|
269
|
+
|
|
222
270
|
const _meta = file.get()._meta || {};
|
|
223
271
|
const version = _meta.version || 1;
|
|
224
272
|
this.internals.modSchema = { i: {}, c: {}, f: {}, s: [] };
|
package/lib/work.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const Promise = require('bluebird');
|
|
6
|
+
const log = require('db-migrate-shared').log;
|
|
7
|
+
const State = require('./state');
|
|
8
|
+
const File = require('./file');
|
|
9
|
+
const Jobs = require('./jobs');
|
|
10
|
+
const Dml = require('./dml');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Runs the jobs of background migrations, see Jobs.
|
|
14
|
+
*
|
|
15
|
+
* parallel: jobs run at once by this worker, each on connections of its own
|
|
16
|
+
* pause: ms to wait between two batches of a job
|
|
17
|
+
* batch: rows per batch, unless the instruction sets its own
|
|
18
|
+
* interval: ms between looking for new jobs
|
|
19
|
+
* timeout: ms after which a job of a worker which stopped responding is
|
|
20
|
+
* taken over, the heartbeat renews it every third of it
|
|
21
|
+
* watch: keep looking for new jobs, instead of ending once none is left
|
|
22
|
+
*
|
|
23
|
+
* Returns { done, stop }. done resolves with the jobs done and failed, once
|
|
24
|
+
* no job is left, or after stop() was called. stop() lets the running jobs
|
|
25
|
+
* stop after their current batch, they are continued later.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const DEFAULTS = {
|
|
29
|
+
parallel: 1,
|
|
30
|
+
pause: 0,
|
|
31
|
+
batch: undefined,
|
|
32
|
+
interval: 5000,
|
|
33
|
+
timeout: 60000,
|
|
34
|
+
watch: false
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
38
|
+
|
|
39
|
+
const connect = async (internals, config) => {
|
|
40
|
+
const Migrator = require('./walker');
|
|
41
|
+
const index = require('../connect');
|
|
42
|
+
|
|
43
|
+
const walker = await index.connect(
|
|
44
|
+
{
|
|
45
|
+
config: config.getCurrent().settings,
|
|
46
|
+
internals,
|
|
47
|
+
prefix: 'migration'
|
|
48
|
+
},
|
|
49
|
+
Migrator
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
walker.directory = path.resolve(internals.argv['migrations-dir']);
|
|
53
|
+
await walker.createMigrationsTable();
|
|
54
|
+
|
|
55
|
+
if (!State.supportsLock(walker._pdriver)) {
|
|
56
|
+
walker.driver.close(() => {});
|
|
57
|
+
throw new Error(
|
|
58
|
+
'Background migrations need a driver supporting the migration lock'
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return walker;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const close = walker =>
|
|
66
|
+
new Promise(resolve => walker.driver.close(() => resolve()));
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The interrupted run of a job, like State.startMigration reports it.
|
|
70
|
+
*/
|
|
71
|
+
const interruption = (job, hash) =>
|
|
72
|
+
job.step > 0
|
|
73
|
+
? {
|
|
74
|
+
step: job.step,
|
|
75
|
+
learned: job.learned,
|
|
76
|
+
done: job.done,
|
|
77
|
+
rollback: job.rb === 1,
|
|
78
|
+
changed: !!job.h && !!hash && job.h !== hash
|
|
79
|
+
}
|
|
80
|
+
: null;
|
|
81
|
+
|
|
82
|
+
const hashFile = file =>
|
|
83
|
+
crypto
|
|
84
|
+
.createHash('sha256')
|
|
85
|
+
.update(require('fs').readFileSync(file.path))
|
|
86
|
+
.digest('hex');
|
|
87
|
+
|
|
88
|
+
const runJob = async (walker, name, id, options, stopping, result) => {
|
|
89
|
+
const internals = walker.internals;
|
|
90
|
+
const pdriver = walker._pdriver;
|
|
91
|
+
let lost = false;
|
|
92
|
+
const update = mutate =>
|
|
93
|
+
Jobs.update(pdriver, internals, name, id, mutate, () => {
|
|
94
|
+
lost = true;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const files = await File.loadFromFileystem(
|
|
98
|
+
walker.directory,
|
|
99
|
+
walker.prefix,
|
|
100
|
+
internals
|
|
101
|
+
);
|
|
102
|
+
const file = files.find(f => f.name === name);
|
|
103
|
+
|
|
104
|
+
if (!file) {
|
|
105
|
+
await update(job => {
|
|
106
|
+
job.s = 'failed';
|
|
107
|
+
job.ID = 0;
|
|
108
|
+
job.err = 'the migration file is missing';
|
|
109
|
+
});
|
|
110
|
+
throw new Error(`The migration file of the job ${name} is missing`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const heartbeat = setInterval(() => {
|
|
114
|
+
update(() => {}).catch(err => log.error(err.message));
|
|
115
|
+
}, Math.max(Math.floor(options.timeout / 3), 1));
|
|
116
|
+
heartbeat.unref();
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const jobs = await Jobs.list(pdriver, internals);
|
|
120
|
+
const hash = hashFile(file);
|
|
121
|
+
const interrupted = interruption(jobs[name], hash);
|
|
122
|
+
|
|
123
|
+
await State.reloadSchema(pdriver, internals);
|
|
124
|
+
const mig = await pdriver._getKV(internals.migrationState, name);
|
|
125
|
+
internals.modSchema = Object.assign(
|
|
126
|
+
{ i: {}, c: {}, f: {}, s: [] },
|
|
127
|
+
mig ? JSON.parse(mig.value) : {}
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (!interrupted) {
|
|
131
|
+
await update(job => {
|
|
132
|
+
job.h = hash;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
log.info(
|
|
137
|
+
`[jobs] ${interrupted ? 'continuing' : 'running'} ${name}`
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const progress = {
|
|
141
|
+
step: op => update(job => { job.step = op; }),
|
|
142
|
+
learned: op => update(job => { job.learned = op; }),
|
|
143
|
+
done: op => update(job => { job.done = op; })
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
await Dml.execute(
|
|
147
|
+
walker,
|
|
148
|
+
file,
|
|
149
|
+
internals,
|
|
150
|
+
pdriver,
|
|
151
|
+
interrupted,
|
|
152
|
+
progress,
|
|
153
|
+
{
|
|
154
|
+
batch: options.batch,
|
|
155
|
+
pause: options.pause,
|
|
156
|
+
stopping: () => lost || stopping()
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
rollingBack: () => update(job => { job.rb = 1; }),
|
|
160
|
+
restart: () =>
|
|
161
|
+
update(job => {
|
|
162
|
+
Object.assign(job, { step: 0, learned: 0, done: 0, rb: 0, h: hash });
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
await Promise.promisify(walker.writeMigrationRecord.bind(walker))(file);
|
|
168
|
+
await update((job, jobs) => {
|
|
169
|
+
delete jobs[name];
|
|
170
|
+
});
|
|
171
|
+
log.info(`[jobs] ${name} is done`);
|
|
172
|
+
result.done.push(name);
|
|
173
|
+
} catch (err) {
|
|
174
|
+
if (lost) {
|
|
175
|
+
log.warn(`[jobs] ${name} was taken over by another worker`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (err && err.stopped) {
|
|
180
|
+
await update(job => {
|
|
181
|
+
job.s = 'queued';
|
|
182
|
+
job.ID = 0;
|
|
183
|
+
});
|
|
184
|
+
log.info(`[jobs] ${name} stopped, it continues with the next run`);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// a job rolled back starts over, else it continues after the steps
|
|
189
|
+
// which can not be reverted
|
|
190
|
+
await update(job => {
|
|
191
|
+
job.s = 'failed';
|
|
192
|
+
job.ID = 0;
|
|
193
|
+
job.err = (err && err.message) || String(err);
|
|
194
|
+
if (err && err.rolledBack) {
|
|
195
|
+
Object.assign(job, { step: 0, learned: 0, done: 0, rb: 0 });
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
log.error(`[jobs] ${name} failed: ${(err && err.message) || err}`);
|
|
199
|
+
result.failed.push({ name, error: err });
|
|
200
|
+
} finally {
|
|
201
|
+
clearInterval(heartbeat);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
module.exports = function executeWork (internals, config, opts = {}) {
|
|
206
|
+
const options = Object.assign({}, DEFAULTS, opts);
|
|
207
|
+
const id = crypto.randomBytes(16).toString('base64');
|
|
208
|
+
const seen = {};
|
|
209
|
+
const result = { done: [], failed: [] };
|
|
210
|
+
let stopped = false;
|
|
211
|
+
const stopping = () => stopped;
|
|
212
|
+
|
|
213
|
+
const slot = async () => {
|
|
214
|
+
// internals of its own, each slot migrates on its own connections
|
|
215
|
+
const own = Object.assign({}, internals, {
|
|
216
|
+
argv: Object.assign({}, internals.argv)
|
|
217
|
+
});
|
|
218
|
+
const walker = await connect(own, config);
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
while (!stopping()) {
|
|
222
|
+
const name = await Jobs.claim(
|
|
223
|
+
walker._pdriver,
|
|
224
|
+
own,
|
|
225
|
+
id,
|
|
226
|
+
seen,
|
|
227
|
+
options.timeout
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
if (name) {
|
|
231
|
+
await runJob(walker, name, id, options, stopping, result);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (!options.watch) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
await delay(options.interval);
|
|
240
|
+
}
|
|
241
|
+
} finally {
|
|
242
|
+
await close(walker);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const slots = [];
|
|
247
|
+
for (let i = 0; i < Math.max(Number(options.parallel) || 1, 1); i++) {
|
|
248
|
+
slots.push(slot());
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const done = Promise.all(slots).then(() => result);
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
done,
|
|
255
|
+
stop: () => {
|
|
256
|
+
stopped = true;
|
|
257
|
+
return done;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
};
|
package/package.json
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"programmable",
|
|
32
32
|
"api"
|
|
33
33
|
],
|
|
34
|
-
"version": "1.
|
|
34
|
+
"version": "1.4.0",
|
|
35
35
|
"bugs": {
|
|
36
36
|
"url": "https://github.com/db-migrate/node-db-migrate/issues"
|
|
37
37
|
},
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"@hapi/lab": "25.x",
|
|
60
60
|
"db-migrate-base": "^2.5.0",
|
|
61
61
|
"db-migrate-mysql": "^3.2.0",
|
|
62
|
-
"db-migrate-sqlite3": "^1.2.
|
|
62
|
+
"db-migrate-sqlite3": "^1.2.1",
|
|
63
63
|
"eslint": "^7.32.0",
|
|
64
64
|
"eslint-config-standard": "^14.1.1",
|
|
65
65
|
"eslint-plugin-import": "^2.26.0",
|