This repository was archived by the owner on Oct 1, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathindex.js
271 lines (215 loc) · 9.07 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
/* eslint complexity: ["error", 27] */
'use strict'
const defaultMigrations = require('../migrations')
const repoVersion = require('./repo/version')
const errors = require('./errors')
const { wrapBackends } = require('./utils')
const log = require('debug')('ipfs:repo:migrator')
/**
* @typedef {import('./types').Migration} Migration
* @typedef {import('./types').MigrationOptions} MigrationOptions
* @typedef {import('./types').ProgressCallback} ProgressCallback
* @typedef {import('./types').MigrationProgressCallback} MigrationProgressCallback
*/
/**
* Returns the version of latest migration.
* If no migrations are present returns 0.
*
* @param {Migration[]} [migrations] - Array of migrations to consider. If undefined, the bundled migrations are used. Mainly for testing purpose.
*/
function getLatestMigrationVersion (migrations) {
migrations = migrations || defaultMigrations
if (!Array.isArray(migrations) || migrations.length === 0) {
return 0
}
return migrations[migrations.length - 1].version
}
/**
* Main function to execute forward migrations.
* It acquire lock on the provided path before doing any migrations.
*
* Signature of the progress callback is: function(migrationObject: object, currentMigrationNumber: int, totalMigrationsCount: int)
*
* @param {string} path - Path to initialized (!) JS-IPFS repo
* @param {import('./types').Backends} backends
* @param {import('./types').RepoOptions} repoOptions - Options that are passed to migrations, that can use them to correctly construct datastore. Options are same like for IPFSRepo.
* @param {number} toVersion - Version to which the repo should be migrated.
* @param {MigrationOptions} [options] - Options for migration
*/
async function migrate (path, backends, repoOptions, toVersion, { ignoreLock = false, onProgress, isDryRun = false, migrations }) {
migrations = migrations || defaultMigrations
if (!path) {
throw new errors.RequiredParameterError('Path argument is required!')
}
if (!repoOptions) {
throw new errors.RequiredParameterError('repoOptions argument is required!')
}
if (!toVersion) {
throw new errors.RequiredParameterError('toVersion argument is required!')
}
if (!Number.isInteger(toVersion) || toVersion <= 0) {
throw new errors.InvalidValueError('Version has to be positive integer!')
}
// make sure we can read pre-level@5 datastores
backends = wrapBackends(backends)
const currentVersion = await repoVersion.getVersion(backends)
if (currentVersion === toVersion) {
log('Nothing to migrate.')
return
}
if (currentVersion > toVersion) {
throw new errors.InvalidValueError(`Current repo's version (${currentVersion}) is higher then toVersion (${toVersion}), you probably wanted to revert it?`)
}
verifyAvailableMigrations(migrations, currentVersion, toVersion)
let lock
if (!isDryRun && !ignoreLock) {
lock = await repoOptions.repoLock.lock(path)
}
try {
for (const migration of migrations) {
if (toVersion !== undefined && migration.version > toVersion) {
break
}
if (migration.version <= currentVersion) {
continue
}
log(`Migrating version ${migration.version}`)
try {
if (!isDryRun) {
/** @type {MigrationProgressCallback} */
let progressCallback = () => {}
if (onProgress) { // eslint-disable-line max-depth
progressCallback = (percent, message) => onProgress(migration.version, percent.toFixed(2), message)
}
await migration.migrate(backends, progressCallback)
}
} catch (e) {
const lastSuccessfullyMigratedVersion = migration.version - 1
log(`An exception was raised during execution of migration. Setting the repo's version to last successfully migrated version: ${lastSuccessfullyMigratedVersion}`)
await repoVersion.setVersion(lastSuccessfullyMigratedVersion, backends)
throw new Error(`During migration to version ${migration.version} exception was raised: ${e.stack || e.message || e}`)
}
log(`Migrating to version ${migration.version} finished`)
}
if (!isDryRun) {
await repoVersion.setVersion(toVersion || getLatestMigrationVersion(migrations), backends)
}
log('Repo successfully migrated', toVersion !== undefined ? `to version ${toVersion}!` : 'to latest version!')
} finally {
if (!isDryRun && !ignoreLock && lock) {
await lock.close()
}
}
}
/**
* Main function to execute backward migration (reversion).
* It acquire lock on the provided path before doing any migrations.
*
* Signature of the progress callback is: function(migrationObject: object, currentMigrationNumber: int, totalMigrationsCount: int)
*
* @param {string} path - Path to initialized (!) JS-IPFS repo
* @param {import('./types').Backends} backends
* @param {import('./types').RepoOptions} repoOptions - Options that are passed to migrations, that can use them to correctly construct datastore. Options are same like for IPFSRepo.
* @param {number} toVersion - Version to which the repo will be reverted.
* @param {MigrationOptions} [options] - Options for the reversion
*/
async function revert (path, backends, repoOptions, toVersion, { ignoreLock = false, onProgress, isDryRun = false, migrations }) {
migrations = migrations || defaultMigrations
if (!path) {
throw new errors.RequiredParameterError('Path argument is required!')
}
if (!repoOptions) {
throw new errors.RequiredParameterError('repoOptions argument is required!')
}
if (!toVersion) {
throw new errors.RequiredParameterError('When reverting migrations, you have to specify to which version to revert!')
}
if (!Number.isInteger(toVersion) || toVersion <= 0) {
throw new errors.InvalidValueError('Version has to be positive integer!')
}
// make sure we can read pre-level@5 datastores
backends = wrapBackends(backends)
const currentVersion = await repoVersion.getVersion(backends)
if (currentVersion === toVersion) {
log('Nothing to revert.')
return
}
if (currentVersion < toVersion) {
throw new errors.InvalidValueError(`Current repo's version (${currentVersion}) is lower then toVersion (${toVersion}), you probably wanted to migrate it?`)
}
verifyAvailableMigrations(migrations, toVersion, currentVersion, true)
let lock
if (!isDryRun && !ignoreLock) {
lock = await repoOptions.repoLock.lock(path)
}
log(`Reverting from version ${currentVersion} to ${toVersion}`)
try {
const reversedMigrationArray = migrations.slice().reverse()
for (const migration of reversedMigrationArray) {
if (migration.version <= toVersion) {
break
}
if (migration.version > currentVersion) {
continue
}
log(`Reverting migration version ${migration.version}`)
try {
if (!isDryRun) {
/** @type {MigrationProgressCallback} */
let progressCallback = () => {}
if (onProgress) { // eslint-disable-line max-depth
progressCallback = (percent, message) => onProgress(migration.version, percent.toFixed(2), message)
}
await migration.revert(backends, progressCallback)
}
} catch (e) {
const lastSuccessfullyRevertedVersion = migration.version
log(`An exception was raised during execution of migration. Setting the repo's version to last successfully reverted version: ${lastSuccessfullyRevertedVersion}`)
await repoVersion.setVersion(lastSuccessfullyRevertedVersion, backends)
e.message = `During reversion to version ${migration.version} exception was raised: ${e.message}`
throw e
}
log(`Reverting to version ${migration.version} finished`)
}
if (!isDryRun) {
await repoVersion.setVersion(toVersion, backends)
}
log(`All migrations successfully reverted to version ${toVersion}!`)
} finally {
if (!isDryRun && !ignoreLock && lock) {
await lock.close()
}
}
}
/**
* Function checks if all migrations in given range are available.
*
* @param {Migration[]} migrations
* @param {number} fromVersion
* @param {number} toVersion
* @param {boolean} checkReversibility - Will additionally checks if all the migrations in the range are reversible
*/
function verifyAvailableMigrations (migrations, fromVersion, toVersion, checkReversibility = false) {
let migrationCounter = 0
for (const migration of migrations) {
if (migration.version > toVersion) {
break
}
if (migration.version > fromVersion) {
if (checkReversibility && !migration.revert) {
throw new errors.NonReversibleMigrationError(`It is not possible to revert to version ${fromVersion} because migration version ${migration.version} is not reversible. Cancelling reversion.`)
}
migrationCounter++
}
}
if (migrationCounter !== (toVersion - fromVersion)) {
throw new errors.InvalidValueError(`The ipfs-repo-migrations package does not have all migration to migrate from version ${fromVersion} to ${toVersion}`)
}
}
module.exports = {
getCurrentRepoVersion: repoVersion.getVersion,
getLatestMigrationVersion,
errors,
migrate,
revert
}