@blackglory/pg-migrations 0.2.2 → 0.2.3
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/package.json +3 -2
- package/src/index.ts +1 -0
- package/src/migrate.ts +135 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blackglory/pg-migrations",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "A utility for database migrations with pg",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"migration",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"sql"
|
|
12
12
|
],
|
|
13
13
|
"files": [
|
|
14
|
-
"lib"
|
|
14
|
+
"lib",
|
|
15
|
+
"src"
|
|
15
16
|
],
|
|
16
17
|
"main": "lib/es2018/index.js",
|
|
17
18
|
"types": "lib/es2018/index.d.ts",
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './migrate'
|
package/src/migrate.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { Client } from 'pg'
|
|
2
|
+
import { isFunction } from '@blackglory/types'
|
|
3
|
+
import { assert } from '@blackglory/errors'
|
|
4
|
+
|
|
5
|
+
export interface IMigration {
|
|
6
|
+
version: number
|
|
7
|
+
up: string | ((client: Client) => PromiseLike<void>)
|
|
8
|
+
down: string | ((client: Client) => PromiseLike<void>)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function migrate(
|
|
12
|
+
client: Client
|
|
13
|
+
, migrations: IMigration[]
|
|
14
|
+
, targetVersion = getMaximumVersion(migrations)
|
|
15
|
+
, migrationsTable: string = 'migrations'
|
|
16
|
+
, advisoryLockKey: bigint = BigInt('-9223372036854775808') // The smallest bigint for postgres
|
|
17
|
+
): Promise<void> {
|
|
18
|
+
const maxVersion = getMaximumVersion(migrations)
|
|
19
|
+
await lock(client, advisoryLockKey)
|
|
20
|
+
try {
|
|
21
|
+
while (true) {
|
|
22
|
+
const currentVersion = await getDatabaseVersion(client, migrationsTable)
|
|
23
|
+
if (maxVersion < currentVersion) {
|
|
24
|
+
break
|
|
25
|
+
} else {
|
|
26
|
+
if (currentVersion === targetVersion) {
|
|
27
|
+
break
|
|
28
|
+
} else if (currentVersion < targetVersion) {
|
|
29
|
+
await upgrade()
|
|
30
|
+
} else {
|
|
31
|
+
await downgrade()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} finally {
|
|
36
|
+
await unlock(client, advisoryLockKey)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function upgrade() {
|
|
40
|
+
await client.query('BEGIN')
|
|
41
|
+
const currentVersion: number = await getDatabaseVersion(client, migrationsTable)
|
|
42
|
+
const targetVersion = currentVersion + 1
|
|
43
|
+
try {
|
|
44
|
+
const migration = migrations.find(x => x.version === targetVersion)
|
|
45
|
+
assert(migration, `Cannot find migration for version ${targetVersion}`)
|
|
46
|
+
|
|
47
|
+
if (isFunction(migration.up)) {
|
|
48
|
+
await migration.up(client)
|
|
49
|
+
} else {
|
|
50
|
+
await client.query(migration.up)
|
|
51
|
+
}
|
|
52
|
+
await setDatabaseVersion(client, migrationsTable, targetVersion)
|
|
53
|
+
await client.query('COMMIT')
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.error(`Upgrade from version ${currentVersion} to version ${targetVersion} failed.`)
|
|
56
|
+
await client.query('ROLLBACK')
|
|
57
|
+
throw e
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function downgrade() {
|
|
62
|
+
await client.query('BEGIN')
|
|
63
|
+
const currentVersion = await getDatabaseVersion(client, migrationsTable)
|
|
64
|
+
const targetVersion = currentVersion - 1
|
|
65
|
+
try {
|
|
66
|
+
const migration = migrations.find(x => x.version === currentVersion)
|
|
67
|
+
assert(migration, `Cannot find migration for version ${targetVersion}`)
|
|
68
|
+
|
|
69
|
+
if (isFunction(migration.down)) {
|
|
70
|
+
await migration.down(client)
|
|
71
|
+
} else {
|
|
72
|
+
await client.query(migration.down)
|
|
73
|
+
}
|
|
74
|
+
await setDatabaseVersion(client, migrationsTable, targetVersion)
|
|
75
|
+
await client.query('COMMIT')
|
|
76
|
+
} catch (e) {
|
|
77
|
+
console.error(`Downgrade from version ${currentVersion} to version ${targetVersion} failed.`)
|
|
78
|
+
await client.query('ROLLBACK')
|
|
79
|
+
throw e
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getMaximumVersion(migrations: IMigration[]): number {
|
|
85
|
+
return migrations.reduce((max, cur) => Math.max(cur.version, max), 0)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function getDatabaseVersion(client: Client, migrationTable: string): Promise<number> {
|
|
89
|
+
await ensureMigrationsTable(client, migrationTable)
|
|
90
|
+
|
|
91
|
+
const result = await client.query<{ schema_version: number }>(`
|
|
92
|
+
SELECT schema_version
|
|
93
|
+
FROM "${migrationTable}";
|
|
94
|
+
`)
|
|
95
|
+
if (result.rows.length) {
|
|
96
|
+
return result.rows[0].schema_version
|
|
97
|
+
} else {
|
|
98
|
+
await client.query(`
|
|
99
|
+
INSERT INTO "${migrationTable}" (schema_version)
|
|
100
|
+
VALUES (0);
|
|
101
|
+
`)
|
|
102
|
+
return 0
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function ensureMigrationsTable(client: Client, migrationTable: string): Promise<void> {
|
|
107
|
+
await client.query(`
|
|
108
|
+
CREATE TABLE IF NOT EXISTS "${migrationTable}" (
|
|
109
|
+
schema_version INTEGER NOT NULL
|
|
110
|
+
);
|
|
111
|
+
`)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function setDatabaseVersion(
|
|
115
|
+
client: Client
|
|
116
|
+
, migrationTable: string
|
|
117
|
+
, version: number
|
|
118
|
+
): Promise<void> {
|
|
119
|
+
await client.query(`
|
|
120
|
+
UPDATE ${migrationTable}
|
|
121
|
+
SET schema_version = ${version};
|
|
122
|
+
`)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function lock(client: Client, key: bigint): Promise<void> {
|
|
126
|
+
await client.query(`
|
|
127
|
+
SELECT pg_advisory_lock(${key});
|
|
128
|
+
`)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function unlock(client: Client, key: bigint): Promise<void> {
|
|
132
|
+
await client.query(`
|
|
133
|
+
SELECT pg_advisory_unlock(${key});
|
|
134
|
+
`)
|
|
135
|
+
}
|