@blackglory/pg-migrations 0.2.1 → 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.
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
3
  if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
5
9
  }) : (function(o, m, k, k2) {
6
10
  if (k2 === undefined) k2 = k;
7
11
  o[k2] = m[k];
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,4CAAyB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,4CAAyB"}
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
3
  if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
5
9
  }) : (function(o, m, k, k2) {
6
10
  if (k2 === undefined) k2 = k;
7
11
  o[k2] = m[k];
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,4CAAyB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,4CAAyB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blackglory/pg-migrations",
3
- "version": "0.2.1",
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",
@@ -46,26 +47,26 @@
46
47
  }
47
48
  },
48
49
  "devDependencies": {
49
- "@commitlint/cli": "^16.2.1",
50
- "@commitlint/config-conventional": "^16.2.1",
50
+ "@commitlint/cli": "^17.0.3",
51
+ "@commitlint/config-conventional": "^17.0.3",
51
52
  "@types/jest": "^27.4.0",
52
- "@types/pg": "^8.6.4",
53
- "@typescript-eslint/eslint-plugin": "^5.12.0",
54
- "@typescript-eslint/parser": "^5.12.0",
55
- "eslint": "^8.9.0",
53
+ "@types/pg": "^8.6.5",
54
+ "@typescript-eslint/eslint-plugin": "^5.32.0",
55
+ "@typescript-eslint/parser": "^5.32.0",
56
+ "eslint": "^8.21.0",
56
57
  "husky": "^4.3.0",
57
58
  "jest": "^27.5.1",
58
59
  "npm-run-all": "^4.1.5",
59
60
  "pg": "^8.7.3",
60
61
  "rimraf": "^3.0.2",
61
- "standard-version": "^9.3.0",
62
+ "standard-version": "^9.5.0",
62
63
  "ts-jest": "^27.1.3",
63
64
  "tscpaths": "^0.0.9",
64
- "typescript": "^4.5.5"
65
+ "typescript": "^4.7.4"
65
66
  },
66
67
  "dependencies": {
67
- "@blackglory/errors": "^2.2.0",
68
- "@blackglory/types": "^0.6.5"
68
+ "@blackglory/errors": "^2.2.2",
69
+ "@blackglory/types": "^1.2.1"
69
70
  },
70
71
  "peerDependencies": {
71
72
  "pg": "8"
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
+ }