pgroll 0.0.7 → 0.0.9
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/README.md +162 -22
- package/dist/src/cli.d.ts +2 -0
- package/dist/{esm/src → src}/cli.js +19 -15
- package/dist/{esm/src → src}/index.d.ts +2 -2
- package/dist/src/index.js +141 -0
- package/dist/{esm/src → src}/utils.js +6 -6
- package/package.json +44 -45
- package/dist/cjs/src/cli.d.ts +0 -1
- package/dist/cjs/src/cli.js +0 -79
- package/dist/cjs/src/index.d.ts +0 -30
- package/dist/cjs/src/index.js +0 -181
- package/dist/cjs/src/utils.js +0 -37
- package/dist/esm/src/cli.d.ts +0 -1
- package/dist/esm/src/index.js +0 -177
- package/dist/esm/src/utils.d.ts +0 -4
- /package/dist/{cjs/src → src}/utils.d.ts +0 -0
package/README.md
CHANGED
|
@@ -1,60 +1,200 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
1
|
# pgroll
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/pgroll)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
A thread-safe, lightweight, and flexible database migration tool for **PostgreSQL**.
|
|
8
8
|
|
|
9
|
-
- [
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
`pgroll` runs your plain-SQL migrations through the [`postgres`](https://github.com/porsager/postgres)
|
|
10
|
+
(PostgresJS) client. Migrations are applied inside a transaction and guarded by a session-level
|
|
11
|
+
advisory lock, so it is safe to run concurrently from multiple processes — only one migration runs
|
|
12
|
+
at a time.
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
> **Supported PostgreSQL clients**
|
|
15
|
+
>
|
|
16
|
+
> - [x] PostgresJS
|
|
17
|
+
> - [ ] node-postgres (`pg`)
|
|
14
18
|
|
|
15
19
|
## Features
|
|
16
20
|
|
|
17
|
-
- **up
|
|
18
|
-
- **down
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
21
|
+
- **up** — Apply all pending migrations.
|
|
22
|
+
- **down** — Roll back **all** applied migrations (down to version `0`).
|
|
23
|
+
- **go** — Migrate forward or backward to a specific version (`go 0` reverts everything).
|
|
24
|
+
- **create** — Generate a matching up/down migration file pair.
|
|
25
|
+
|
|
26
|
+
## Requirements
|
|
27
|
+
|
|
28
|
+
- **Node.js ≥ 24.16.0** (the package ships as ESM)
|
|
29
|
+
- A reachable **PostgreSQL** database
|
|
21
30
|
|
|
22
31
|
## Installation
|
|
23
32
|
|
|
24
|
-
|
|
33
|
+
Install globally to use the CLI anywhere:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install -g pgroll
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
…or add it to a project and run it with `npx`:
|
|
25
40
|
|
|
26
41
|
```bash
|
|
27
42
|
npm install pgroll
|
|
28
43
|
```
|
|
29
44
|
|
|
30
|
-
##
|
|
45
|
+
## Configuration
|
|
31
46
|
|
|
32
|
-
###
|
|
47
|
+
### Connecting to PostgreSQL
|
|
33
48
|
|
|
34
|
-
|
|
49
|
+
The CLI connects using a connection URL or the standard libpq environment variables (read by the
|
|
50
|
+
underlying `postgres` client).
|
|
35
51
|
|
|
36
|
-
|
|
52
|
+
**Option 1 — connection URL** via the `-u, --url` flag:
|
|
37
53
|
|
|
38
|
-
|
|
54
|
+
```bash
|
|
55
|
+
npx pgroll --url "postgres://user:password@localhost:5432/mydb" up
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Option 2 — environment variables:**
|
|
59
|
+
|
|
60
|
+
| Variable | Description | Default |
|
|
61
|
+
| ------------ | ------------- | ------------ |
|
|
62
|
+
| `PGHOST` | Server host | `localhost` |
|
|
63
|
+
| `PGPORT` | Server port | `5432` |
|
|
64
|
+
| `PGDATABASE` | Database name | — |
|
|
65
|
+
| `PGUSER` | User name | OS user name |
|
|
66
|
+
| `PGPASSWORD` | Password | — |
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
PGHOST=localhost PGDATABASE=mydb PGUSER=me PGPASSWORD=secret npx pgroll up
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
When `--url` is provided, it takes precedence over the `PG*` variables.
|
|
73
|
+
|
|
74
|
+
### Migration files
|
|
75
|
+
|
|
76
|
+
Migrations live in a directory (default `./migrations`, override with `-d, --migrationDir <path>`).
|
|
77
|
+
Each migration is a **pair** of plain `.sql` files distinguished by suffix:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
20240619121610402_init_up.sql # applied on "up"
|
|
81
|
+
20240619121610402_init_down.sql # applied on "down"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- The leading number is a timestamp generated by `create`; it determines order.
|
|
85
|
+
- `up` migrations are applied in **ascending** filename order; `down` migrations in **descending**
|
|
86
|
+
order, so rollbacks unwind in the reverse of how they were applied.
|
|
87
|
+
- A migration's _version_ is its position in that ordered list (the first up migration is version `1`).
|
|
88
|
+
|
|
89
|
+
## CLI
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
pgroll [global options] <command>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Global options
|
|
96
|
+
|
|
97
|
+
| Option | Description |
|
|
98
|
+
| --------------------------- | --------------------------------------------------------------- |
|
|
99
|
+
| `-d, --migrationDir <path>` | Directory holding the migration files (default `./migrations`). |
|
|
100
|
+
| `-u, --url <url>` | PostgreSQL connection URL (overrides `PG*` env vars). |
|
|
101
|
+
| `-V, --version` | Print the `pgroll` version. |
|
|
102
|
+
| `-h, --help` | Show help. |
|
|
103
|
+
|
|
104
|
+
### Commands
|
|
105
|
+
|
|
106
|
+
**Apply all pending migrations:**
|
|
39
107
|
|
|
40
108
|
```bash
|
|
41
109
|
npx pgroll up
|
|
42
110
|
```
|
|
43
111
|
|
|
44
|
-
|
|
112
|
+
**Roll back every applied migration:**
|
|
45
113
|
|
|
46
114
|
```bash
|
|
47
115
|
npx pgroll down
|
|
48
116
|
```
|
|
49
117
|
|
|
50
|
-
|
|
118
|
+
**Migrate to a specific version** (moves up or down as needed; `0` rolls everything back):
|
|
51
119
|
|
|
52
120
|
```bash
|
|
53
121
|
npx pgroll go <version>
|
|
54
122
|
```
|
|
55
123
|
|
|
56
|
-
|
|
124
|
+
**Create a new migration pair** (writes `<timestamp>_<name>_up.sql` and `..._down.sql` with
|
|
125
|
+
placeholder contents for you to fill in):
|
|
57
126
|
|
|
58
127
|
```bash
|
|
59
|
-
npx
|
|
128
|
+
npx pgroll create <name>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
A typical workflow:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npx pgroll create add_users_table # creates the up/down file pair
|
|
135
|
+
# …edit the generated *_up.sql / *_down.sql files…
|
|
136
|
+
npx pgroll up # apply it
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Programmatic API
|
|
140
|
+
|
|
141
|
+
`pgroll` can also be used as a library. Pass it a `postgres` client instance and drive migrations
|
|
142
|
+
directly:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import postgres from 'postgres';
|
|
146
|
+
import { Migrator } from 'pgroll';
|
|
147
|
+
|
|
148
|
+
const sql = postgres('postgres://user:password@localhost:5432/mydb');
|
|
149
|
+
const migrator = new Migrator(sql, './migrations');
|
|
150
|
+
|
|
151
|
+
// Apply all pending migrations
|
|
152
|
+
await migrator.up();
|
|
153
|
+
|
|
154
|
+
console.log('Current version:', await migrator.getCurrentVersion());
|
|
155
|
+
|
|
156
|
+
// Migrate to a specific version, logging progress as it goes
|
|
157
|
+
await migrator.go(0, { eventHandler: info => console.log(info) });
|
|
158
|
+
|
|
159
|
+
await sql.end();
|
|
60
160
|
```
|
|
161
|
+
|
|
162
|
+
### `new Migrator(dbClient, migrationsDir?)`
|
|
163
|
+
|
|
164
|
+
| Parameter | Type | Description |
|
|
165
|
+
| --------------- | ------------------- | ------------------------------------------------------------- |
|
|
166
|
+
| `dbClient` | `Sql` (PostgresJS) | A `postgres` client instance. |
|
|
167
|
+
| `migrationsDir` | `string` (optional) | Directory of migration files. Defaults to `<cwd>/migrations`. |
|
|
168
|
+
|
|
169
|
+
### Methods
|
|
170
|
+
|
|
171
|
+
| Method | Description |
|
|
172
|
+
| --------------------- | ----------------------------------------------------------------------- |
|
|
173
|
+
| `up(opts?)` | Apply all pending up migrations. |
|
|
174
|
+
| `down(opts?)` | Roll back all applied migrations (to version `0`). |
|
|
175
|
+
| `go(version, opts?)` | Migrate forward or backward to `version` (`0` reverts everything). |
|
|
176
|
+
| `getCurrentVersion()` | Resolve to the highest applied version (`0` if none have been applied). |
|
|
177
|
+
|
|
178
|
+
`opts` is `{ eventHandler: (info: string) => void }` — an optional callback invoked with a
|
|
179
|
+
human-readable message as each migration is applied.
|
|
180
|
+
|
|
181
|
+
## How it works
|
|
182
|
+
|
|
183
|
+
On the first run, `pgroll` creates a bookkeeping table:
|
|
184
|
+
|
|
185
|
+
```sql
|
|
186
|
+
CREATE TABLE IF NOT EXISTS migrations (
|
|
187
|
+
name varchar(500) PRIMARY KEY,
|
|
188
|
+
version smallint NOT NULL,
|
|
189
|
+
applied_at timestamp DEFAULT CURRENT_TIMESTAMP
|
|
190
|
+
);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Each `up`/`down`/`go` run reserves a single connection, takes a session-level
|
|
194
|
+
`pg_advisory_lock`, and applies the relevant migration files within a transaction — recording or
|
|
195
|
+
removing the corresponding rows in `migrations` as it goes. The advisory lock serializes concurrent
|
|
196
|
+
runs across processes, and the surrounding transaction means a failed migration rolls back cleanly.
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
[ISC](./LICENSE)
|
|
@@ -1,28 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console */
|
|
2
3
|
import * as process from 'node:process';
|
|
3
4
|
import { Command } from 'commander';
|
|
4
5
|
import postgres from 'postgres';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
6
|
+
import { Migrator } from "./index.js";
|
|
7
|
+
import { createFile } from "./utils.js";
|
|
7
8
|
const program = new Command();
|
|
8
9
|
let migrator;
|
|
9
10
|
program
|
|
10
|
-
.version('0.0.
|
|
11
|
+
.version('0.0.9')
|
|
11
12
|
.description('Database migration tool')
|
|
12
13
|
.option('-d, --migrationDir <filepath>', 'Specify migration directory(Default: ./migrations)')
|
|
14
|
+
.option('-u, --url <url>', 'PostgreSQL connection URL (overrides PG* env vars)')
|
|
13
15
|
.hook('preAction', cmd => {
|
|
14
16
|
const opts = cmd.opts();
|
|
15
|
-
|
|
17
|
+
const pgOptions = {
|
|
16
18
|
onnotice: () => {
|
|
19
|
+
// do nothing
|
|
17
20
|
}
|
|
18
|
-
}
|
|
21
|
+
};
|
|
22
|
+
migrator = new Migrator(opts.url ? postgres(opts.url, pgOptions) : postgres(pgOptions), opts.migrationDir);
|
|
19
23
|
});
|
|
20
24
|
program
|
|
21
25
|
.command('up')
|
|
22
26
|
.description('Run all up migrations')
|
|
23
|
-
.action(() =>
|
|
27
|
+
.action(async () => {
|
|
24
28
|
try {
|
|
25
|
-
|
|
29
|
+
await migrator.up({ eventHandler: console.log });
|
|
26
30
|
console.log('Migrations up completed successfully.');
|
|
27
31
|
process.exit(0);
|
|
28
32
|
}
|
|
@@ -30,13 +34,13 @@ program
|
|
|
30
34
|
console.error('Error during migrations up:', error);
|
|
31
35
|
process.exit(1);
|
|
32
36
|
}
|
|
33
|
-
})
|
|
37
|
+
});
|
|
34
38
|
program
|
|
35
39
|
.command('down')
|
|
36
40
|
.description('Run all down migrations')
|
|
37
|
-
.action(() =>
|
|
41
|
+
.action(async () => {
|
|
38
42
|
try {
|
|
39
|
-
|
|
43
|
+
await migrator.down({ eventHandler: console.log });
|
|
40
44
|
console.log('Migrations down completed successfully.');
|
|
41
45
|
process.exit(0);
|
|
42
46
|
}
|
|
@@ -44,7 +48,7 @@ program
|
|
|
44
48
|
console.error('Error during migrations down:', error);
|
|
45
49
|
process.exit(1);
|
|
46
50
|
}
|
|
47
|
-
})
|
|
51
|
+
});
|
|
48
52
|
program
|
|
49
53
|
.command('create')
|
|
50
54
|
.description('Generate migration files in the migrations folder: one for applying changes (up), and one for reverting them (down).')
|
|
@@ -59,19 +63,19 @@ program
|
|
|
59
63
|
.command('go')
|
|
60
64
|
.description('Navigate to a specific version; version 0 performs a rollback, reverting all migrations.')
|
|
61
65
|
.argument('<version>', 'version to migrate to')
|
|
62
|
-
.action((version) =>
|
|
66
|
+
.action(async (version) => {
|
|
63
67
|
const parsedVersion = Number.parseInt(version, 10);
|
|
64
68
|
if (Number.isNaN(parsedVersion)) {
|
|
65
69
|
console.error('Invalid version number.');
|
|
66
70
|
process.exit(1);
|
|
67
71
|
}
|
|
68
72
|
try {
|
|
69
|
-
|
|
73
|
+
await migrator.go(parsedVersion, { eventHandler: console.log });
|
|
70
74
|
process.exit(0);
|
|
71
75
|
}
|
|
72
76
|
catch (error) {
|
|
73
77
|
console.error('Error during migrations:', error);
|
|
74
78
|
process.exit(1);
|
|
75
79
|
}
|
|
76
|
-
})
|
|
80
|
+
});
|
|
77
81
|
program.parse(process.argv);
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import {} from 'postgres';
|
|
3
|
+
import { getMigrationFiles } from "./utils.js";
|
|
4
|
+
export class Migrator {
|
|
5
|
+
dbClient;
|
|
6
|
+
migrationsDir;
|
|
7
|
+
constructor(dbClient, migrationsDir = '') {
|
|
8
|
+
this.dbClient = dbClient;
|
|
9
|
+
this.migrationsDir = migrationsDir || `${process.cwd()}/migrations`;
|
|
10
|
+
}
|
|
11
|
+
async ensureMigrationTable(tx) {
|
|
12
|
+
await tx `CREATE TABLE IF NOT EXISTS migrations(
|
|
13
|
+
name varchar(500) PRIMARY KEY,
|
|
14
|
+
version smallint NOT NULL,
|
|
15
|
+
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);`;
|
|
16
|
+
}
|
|
17
|
+
async up() {
|
|
18
|
+
return this.migrate('up');
|
|
19
|
+
}
|
|
20
|
+
async down() {
|
|
21
|
+
return this.migrate('down');
|
|
22
|
+
}
|
|
23
|
+
async go(version, opts) {
|
|
24
|
+
const tx = await this.dbClient.reserve();
|
|
25
|
+
try {
|
|
26
|
+
await this.acquireLock(tx);
|
|
27
|
+
await this.begin(tx);
|
|
28
|
+
await this.ensureMigrationTable(tx);
|
|
29
|
+
const currentVersion = await this.getCurrentVersionWithTx(tx);
|
|
30
|
+
if (currentVersion === version) {
|
|
31
|
+
opts?.eventHandler(`Already at version ${version}`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const direction = version > currentVersion ? 'up' : 'down';
|
|
35
|
+
const fileNames = getMigrationFiles(this.migrationsDir, direction);
|
|
36
|
+
if (direction === 'up') {
|
|
37
|
+
const fileVersion = Math.min(fileNames.length, version);
|
|
38
|
+
for (let i = currentVersion; i < fileVersion; i++) {
|
|
39
|
+
const file = fileNames[i] ?? '';
|
|
40
|
+
await Promise.all([
|
|
41
|
+
tx.file(path.join(this.migrationsDir, file)).execute(),
|
|
42
|
+
tx `INSERT INTO migrations(name, version) VALUES (${file}, ${i} + 1)`
|
|
43
|
+
]);
|
|
44
|
+
opts?.eventHandler(`Successfully migrated: ${file}`);
|
|
45
|
+
}
|
|
46
|
+
if (version > fileNames.length) {
|
|
47
|
+
opts?.eventHandler(`Currently at latest version: ${currentVersion}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// get index of the current file
|
|
52
|
+
const start = fileNames.length - currentVersion;
|
|
53
|
+
// current index + number of file to down
|
|
54
|
+
const end = start + (currentVersion - version);
|
|
55
|
+
for (let i = start; i < end; i++) {
|
|
56
|
+
const file = fileNames[i] ?? '';
|
|
57
|
+
await Promise.all([
|
|
58
|
+
tx.file(path.join(this.migrationsDir, file)).execute(),
|
|
59
|
+
tx `DELETE FROM migrations WHERE version = ${fileNames.length - i}`
|
|
60
|
+
]);
|
|
61
|
+
opts?.eventHandler(`Successfully migrated: ${file}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
await this.commit(tx);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
await this.rollback(tx);
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
await this.releaseLock(tx);
|
|
72
|
+
tx.release();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async migrate(direction, opts) {
|
|
76
|
+
const tx = await this.dbClient.reserve();
|
|
77
|
+
try {
|
|
78
|
+
await this.acquireLock(tx);
|
|
79
|
+
await this.begin(tx);
|
|
80
|
+
await this.ensureMigrationTable(tx);
|
|
81
|
+
const currentVersion = await this.getCurrentVersionWithTx(tx);
|
|
82
|
+
const fileNames = getMigrationFiles(this.migrationsDir, direction);
|
|
83
|
+
if (direction === 'up') {
|
|
84
|
+
for (const fileName of fileNames) {
|
|
85
|
+
const id = fileNames.indexOf(fileName);
|
|
86
|
+
if (id >= currentVersion) {
|
|
87
|
+
await Promise.all([
|
|
88
|
+
tx.file(path.join(this.migrationsDir, fileName)).execute(),
|
|
89
|
+
tx `INSERT INTO migrations(name, version) VALUES (${fileName}, ${id} + 1)`
|
|
90
|
+
]);
|
|
91
|
+
opts?.eventHandler(`Successfully migrated: ${fileName}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// calculate start to know where the down migration starts
|
|
97
|
+
const start = fileNames.length - currentVersion;
|
|
98
|
+
for (let i = start; i < fileNames.length; i++) {
|
|
99
|
+
const file = fileNames[i] ?? '';
|
|
100
|
+
await Promise.all([
|
|
101
|
+
tx.file(path.join(this.migrationsDir, file)).execute(),
|
|
102
|
+
tx `DELETE FROM migrations WHERE version = ${fileNames.length - i}`
|
|
103
|
+
]);
|
|
104
|
+
opts?.eventHandler(`Successfully migrated: ${file}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
await this.commit(tx);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
await this.rollback(tx);
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
await this.releaseLock(tx);
|
|
115
|
+
tx.release();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async getCurrentVersion() {
|
|
119
|
+
const result = await this.dbClient `SELECT version FROM migrations ORDER BY version DESC LIMIT 1`;
|
|
120
|
+
return result.length > 0 ? result[0]?.['version'] : 0;
|
|
121
|
+
}
|
|
122
|
+
async getCurrentVersionWithTx(tx) {
|
|
123
|
+
const result = await tx `SELECT version FROM migrations ORDER BY version DESC LIMIT 1`;
|
|
124
|
+
return result.length > 0 ? result[0]?.['version'] : 0;
|
|
125
|
+
}
|
|
126
|
+
async acquireLock(tx) {
|
|
127
|
+
await tx `SELECT pg_advisory_lock(21421431414441411)`;
|
|
128
|
+
}
|
|
129
|
+
async releaseLock(tx) {
|
|
130
|
+
await tx `SELECT pg_advisory_unlock(21421431414441411)`;
|
|
131
|
+
}
|
|
132
|
+
async begin(tx) {
|
|
133
|
+
await tx `BEGIN`;
|
|
134
|
+
}
|
|
135
|
+
async commit(tx) {
|
|
136
|
+
await tx `COMMIT`;
|
|
137
|
+
}
|
|
138
|
+
async rollback(tx) {
|
|
139
|
+
await tx `ROLLBACK`;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
export const getMigrationFiles = (dir, direction) => {
|
|
4
|
-
const files = fs
|
|
5
|
-
|
|
6
|
-
.
|
|
7
|
-
if (direction == 'up') {
|
|
8
|
-
return files.sort();
|
|
4
|
+
const files = fs.readdirSync(dir).filter(file => file.endsWith(`_${direction}.sql`));
|
|
5
|
+
if (direction === 'up') {
|
|
6
|
+
return files.toSorted((a, b) => a.localeCompare(b));
|
|
9
7
|
}
|
|
10
|
-
return files.
|
|
8
|
+
return files.toSorted((a, b) => b.localeCompare(a));
|
|
11
9
|
};
|
|
12
10
|
export const createFolderIfNotExists = (filePath) => {
|
|
13
11
|
if (!fs.existsSync(filePath)) {
|
|
@@ -18,10 +16,12 @@ export const createFile = (migrationDir, name) => {
|
|
|
18
16
|
createFolderIfNotExists(migrationDir);
|
|
19
17
|
const timestamp = new Date().toISOString().replaceAll(/[.:TZ-]/g, '');
|
|
20
18
|
const ressult = [];
|
|
19
|
+
// make up file
|
|
21
20
|
let fileName = `${timestamp}_${name}_up.sql`;
|
|
22
21
|
let filePath = path.join(migrationDir, fileName);
|
|
23
22
|
fs.writeFileSync(filePath, '-- up SQL here');
|
|
24
23
|
ressult.push(filePath);
|
|
24
|
+
// make down file
|
|
25
25
|
fileName = `${timestamp}_${name}_down.sql`;
|
|
26
26
|
filePath = path.join(migrationDir, fileName);
|
|
27
27
|
fs.writeFileSync(filePath, '-- down SQL here');
|
package/package.json
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pgroll",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "Postgres migration tool",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"types": "dist/
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/src/index.js",
|
|
7
|
+
"types": "dist/src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/src/index.d.ts",
|
|
11
|
+
"import": "./dist/src/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
8
14
|
"repository": {
|
|
9
15
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/tnht95/pgroll"
|
|
16
|
+
"url": "git+https://github.com/tnht95/pgroll.git"
|
|
11
17
|
},
|
|
12
18
|
"files": [
|
|
13
|
-
"dist/
|
|
14
|
-
"dist/esm/src"
|
|
19
|
+
"dist/src"
|
|
15
20
|
],
|
|
16
21
|
"bin": {
|
|
17
|
-
"pgroll": "
|
|
22
|
+
"pgroll": "dist/src/cli.js"
|
|
18
23
|
},
|
|
19
24
|
"keywords": [
|
|
20
25
|
"postgres",
|
|
@@ -25,50 +30,44 @@
|
|
|
25
30
|
"typescript"
|
|
26
31
|
],
|
|
27
32
|
"scripts": {
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"build
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"fmtc": "prettier --check .",
|
|
33
|
+
"preinstall": "npx only-allow pnpm",
|
|
34
|
+
"dev": "node --watch ./src/cli.ts",
|
|
35
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
36
|
+
"start": "node ./dist/src/cli.js",
|
|
37
|
+
"format": "prettier --write .",
|
|
38
|
+
"format:check": "prettier --check .",
|
|
35
39
|
"lint": "eslint . --fix",
|
|
36
|
-
"
|
|
37
|
-
"test": "
|
|
38
|
-
"
|
|
40
|
+
"lint:check": "eslint .",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:watch": "vitest",
|
|
43
|
+
"test:coverage": "vitest run --coverage",
|
|
44
|
+
"pp": "pnpm build && node update-version.ts && npm publish --access=public"
|
|
39
45
|
},
|
|
40
|
-
"packageManager": "pnpm@
|
|
46
|
+
"packageManager": "pnpm@10.28.1+sha512.7d7dbbca9e99447b7c3bf7a73286afaaf6be99251eb9498baefa7d406892f67b879adb3a1d7e687fc4ccc1a388c7175fbaae567a26ab44d1067b54fcb0d6a316",
|
|
41
47
|
"engines": {
|
|
42
|
-
"node": ">=
|
|
43
|
-
"npm": "please-use-pnpm",
|
|
44
|
-
"yarn": "please-use-pnpm",
|
|
45
|
-
"pnpm": ">=9.4.0"
|
|
48
|
+
"node": ">=v24.16.0"
|
|
46
49
|
},
|
|
47
50
|
"license": "ISC",
|
|
48
51
|
"devDependencies": {
|
|
49
|
-
"@eslint/
|
|
50
|
-
"@
|
|
51
|
-
"@
|
|
52
|
-
"@
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"eslint": "^
|
|
56
|
-
"eslint-
|
|
57
|
-
"eslint-
|
|
58
|
-
"eslint-plugin-
|
|
59
|
-
"eslint-plugin-
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"ts-jest": "^29.1.5",
|
|
66
|
-
"tsx": "^4.15.7",
|
|
67
|
-
"typescript": "^5.5.2"
|
|
52
|
+
"@eslint/js": "^10.0.1",
|
|
53
|
+
"@types/node": "^25.9.1",
|
|
54
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
55
|
+
"@vitest/eslint-plugin": "^1.6.18",
|
|
56
|
+
"eslint": "^10.4.0",
|
|
57
|
+
"eslint-config-prettier": "^10.1.8",
|
|
58
|
+
"eslint-import-resolver-typescript": "^4.4.4",
|
|
59
|
+
"eslint-plugin-import-x": "^4.16.2",
|
|
60
|
+
"eslint-plugin-promise": "^7.3.0",
|
|
61
|
+
"eslint-plugin-sonarjs": "^4.0.3",
|
|
62
|
+
"eslint-plugin-unicorn": "^64.0.0",
|
|
63
|
+
"globals": "^17.6.0",
|
|
64
|
+
"prettier": "^3.8.3",
|
|
65
|
+
"typescript": "^6.0.3",
|
|
66
|
+
"typescript-eslint": "^8.60.0",
|
|
67
|
+
"vitest": "^4.1.7"
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"commander": "^
|
|
71
|
-
"postgres": "^3.4.
|
|
72
|
-
"tslib": "^2.6.3"
|
|
70
|
+
"commander": "^14.0.3",
|
|
71
|
+
"postgres": "^3.4.9"
|
|
73
72
|
}
|
|
74
73
|
}
|
package/dist/cjs/src/cli.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/cjs/src/cli.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const tslib_1 = require("tslib");
|
|
4
|
-
const process = tslib_1.__importStar(require("node:process"));
|
|
5
|
-
const commander_1 = require("commander");
|
|
6
|
-
const postgres_1 = tslib_1.__importDefault(require("postgres"));
|
|
7
|
-
const utils_1 = require("./utils");
|
|
8
|
-
const index_1 = require("./index");
|
|
9
|
-
const program = new commander_1.Command();
|
|
10
|
-
let migrator;
|
|
11
|
-
program
|
|
12
|
-
.version('0.0.1')
|
|
13
|
-
.description('Database migration tool')
|
|
14
|
-
.option('-d, --migrationDir <filepath>', 'Specify migration directory(Default: ./migrations)')
|
|
15
|
-
.hook('preAction', cmd => {
|
|
16
|
-
const opts = cmd.opts();
|
|
17
|
-
migrator = new index_1.Migrator((0, postgres_1.default)({
|
|
18
|
-
onnotice: () => {
|
|
19
|
-
}
|
|
20
|
-
}), opts.migrationDir);
|
|
21
|
-
});
|
|
22
|
-
program
|
|
23
|
-
.command('up')
|
|
24
|
-
.description('Run all up migrations')
|
|
25
|
-
.action(() => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
|
26
|
-
try {
|
|
27
|
-
yield migrator.up({ eventHandler: console.log });
|
|
28
|
-
console.log('Migrations up completed successfully.');
|
|
29
|
-
process.exit(0);
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
console.error('Error during migrations up:', error);
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
}));
|
|
36
|
-
program
|
|
37
|
-
.command('down')
|
|
38
|
-
.description('Run all down migrations')
|
|
39
|
-
.action(() => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
|
40
|
-
try {
|
|
41
|
-
yield migrator.down({ eventHandler: console.log });
|
|
42
|
-
console.log('Migrations down completed successfully.');
|
|
43
|
-
process.exit(0);
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
console.error('Error during migrations down:', error);
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
}));
|
|
50
|
-
program
|
|
51
|
-
.command('create')
|
|
52
|
-
.description('Generate migration files in the migrations folder: one for applying changes (up), and one for reverting them (down).')
|
|
53
|
-
.argument('<filename>', 'file name to be created')
|
|
54
|
-
.action((fileName) => {
|
|
55
|
-
const result = (0, utils_1.createFile)(migrator.migrationsDir, fileName);
|
|
56
|
-
for (const f of result)
|
|
57
|
-
console.log(`Successfully created migration files: ${f}`);
|
|
58
|
-
process.exit(0);
|
|
59
|
-
});
|
|
60
|
-
program
|
|
61
|
-
.command('go')
|
|
62
|
-
.description('Navigate to a specific version; version 0 performs a rollback, reverting all migrations.')
|
|
63
|
-
.argument('<version>', 'version to migrate to')
|
|
64
|
-
.action((version) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
|
65
|
-
const parsedVersion = Number.parseInt(version, 10);
|
|
66
|
-
if (Number.isNaN(parsedVersion)) {
|
|
67
|
-
console.error('Invalid version number.');
|
|
68
|
-
process.exit(1);
|
|
69
|
-
}
|
|
70
|
-
try {
|
|
71
|
-
yield migrator.go(parsedVersion, { eventHandler: console.log });
|
|
72
|
-
process.exit(0);
|
|
73
|
-
}
|
|
74
|
-
catch (error) {
|
|
75
|
-
console.error('Error during migrations:', error);
|
|
76
|
-
process.exit(1);
|
|
77
|
-
}
|
|
78
|
-
}));
|
|
79
|
-
program.parse(process.argv);
|
package/dist/cjs/src/index.d.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { ReservedSql, Sql } from 'postgres';
|
|
2
|
-
import { Direction } from './utils';
|
|
3
|
-
interface Option {
|
|
4
|
-
eventHandler: (info: string) => void;
|
|
5
|
-
}
|
|
6
|
-
export interface IMigrator {
|
|
7
|
-
migrationsDir: string;
|
|
8
|
-
up: (opts?: Option) => Promise<void>;
|
|
9
|
-
down: (opts?: Option) => Promise<void>;
|
|
10
|
-
go: (version: number, opts?: Option) => Promise<void>;
|
|
11
|
-
getCurrentVersion: () => Promise<number>;
|
|
12
|
-
}
|
|
13
|
-
export declare class Migrator implements IMigrator {
|
|
14
|
-
private readonly dbClient;
|
|
15
|
-
readonly migrationsDir: string;
|
|
16
|
-
constructor(dbClient: Sql, migrationsDir?: string);
|
|
17
|
-
ensureMigrationTable(tx: ReservedSql): Promise<void>;
|
|
18
|
-
up(): Promise<void>;
|
|
19
|
-
down(): Promise<void>;
|
|
20
|
-
go(version: number, opts?: Option): Promise<void>;
|
|
21
|
-
migrate(direction: Direction, opts?: Option): Promise<void>;
|
|
22
|
-
getCurrentVersion(): Promise<number>;
|
|
23
|
-
getCurrentVersionWithTx(tx: ReservedSql): Promise<number>;
|
|
24
|
-
acquireLock(tx: ReservedSql): Promise<void>;
|
|
25
|
-
releaseLock(tx: ReservedSql): Promise<void>;
|
|
26
|
-
begin(tx: ReservedSql): Promise<void>;
|
|
27
|
-
commit(tx: ReservedSql): Promise<void>;
|
|
28
|
-
rollback(tx: ReservedSql): Promise<void>;
|
|
29
|
-
}
|
|
30
|
-
export {};
|
package/dist/cjs/src/index.js
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Migrator = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
6
|
-
const utils_1 = require("./utils");
|
|
7
|
-
class Migrator {
|
|
8
|
-
constructor(dbClient, migrationsDir = '') {
|
|
9
|
-
Object.defineProperty(this, "dbClient", {
|
|
10
|
-
enumerable: true,
|
|
11
|
-
configurable: true,
|
|
12
|
-
writable: true,
|
|
13
|
-
value: void 0
|
|
14
|
-
});
|
|
15
|
-
Object.defineProperty(this, "migrationsDir", {
|
|
16
|
-
enumerable: true,
|
|
17
|
-
configurable: true,
|
|
18
|
-
writable: true,
|
|
19
|
-
value: void 0
|
|
20
|
-
});
|
|
21
|
-
this.dbClient = dbClient;
|
|
22
|
-
this.migrationsDir = migrationsDir || `${process.cwd()}/migrations`;
|
|
23
|
-
}
|
|
24
|
-
ensureMigrationTable(tx) {
|
|
25
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
26
|
-
yield tx `CREATE TABLE IF NOT EXISTS migrations(
|
|
27
|
-
name varchar(500) PRIMARY KEY,
|
|
28
|
-
version smallint NOT NULL,
|
|
29
|
-
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);`;
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
up() {
|
|
33
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
34
|
-
return this.migrate('up');
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
down() {
|
|
38
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
39
|
-
return this.migrate('down');
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
go(version, opts) {
|
|
43
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
44
|
-
var _a, _b;
|
|
45
|
-
const tx = yield this.dbClient.reserve();
|
|
46
|
-
try {
|
|
47
|
-
yield this.acquireLock(tx);
|
|
48
|
-
yield this.begin(tx);
|
|
49
|
-
yield this.ensureMigrationTable(tx);
|
|
50
|
-
const currentVersion = yield this.getCurrentVersionWithTx(tx);
|
|
51
|
-
if (currentVersion === version) {
|
|
52
|
-
opts === null || opts === void 0 ? void 0 : opts.eventHandler(`Already at version ${version}`);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
const direction = version > currentVersion ? 'up' : 'down';
|
|
56
|
-
const fileNames = (0, utils_1.getMigrationFiles)(this.migrationsDir, direction);
|
|
57
|
-
if (direction === 'up') {
|
|
58
|
-
const fileVersion = Math.min(fileNames.length, version);
|
|
59
|
-
for (let i = currentVersion; i < fileVersion; i++) {
|
|
60
|
-
const file = (_a = fileNames[i]) !== null && _a !== void 0 ? _a : '';
|
|
61
|
-
yield Promise.all([
|
|
62
|
-
tx.file(node_path_1.default.join(this.migrationsDir, file)).execute(),
|
|
63
|
-
tx `INSERT INTO migrations(name, version) VALUES (${file}, ${i} + 1)`
|
|
64
|
-
]);
|
|
65
|
-
opts === null || opts === void 0 ? void 0 : opts.eventHandler(`Successfully migrated: ${file}`);
|
|
66
|
-
}
|
|
67
|
-
if (version > fileNames.length) {
|
|
68
|
-
opts === null || opts === void 0 ? void 0 : opts.eventHandler(`Currently at latest version: ${currentVersion}`);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
const start = fileNames.length - currentVersion;
|
|
73
|
-
const end = start + (currentVersion - version);
|
|
74
|
-
for (let i = start; i < end; i++) {
|
|
75
|
-
const file = (_b = fileNames[i]) !== null && _b !== void 0 ? _b : '';
|
|
76
|
-
yield Promise.all([
|
|
77
|
-
tx.file(node_path_1.default.join(this.migrationsDir, file)).execute(),
|
|
78
|
-
tx `DELETE FROM migrations WHERE version = ${fileNames.length - i}`
|
|
79
|
-
]);
|
|
80
|
-
opts === null || opts === void 0 ? void 0 : opts.eventHandler(`Successfully migrated: ${file}`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
yield this.commit(tx);
|
|
84
|
-
}
|
|
85
|
-
catch (error) {
|
|
86
|
-
yield this.rollback(tx);
|
|
87
|
-
throw error;
|
|
88
|
-
}
|
|
89
|
-
finally {
|
|
90
|
-
yield this.releaseLock(tx);
|
|
91
|
-
tx.release();
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
migrate(direction, opts) {
|
|
96
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
97
|
-
var _a;
|
|
98
|
-
const tx = yield this.dbClient.reserve();
|
|
99
|
-
try {
|
|
100
|
-
yield this.acquireLock(tx);
|
|
101
|
-
yield this.begin(tx);
|
|
102
|
-
yield this.ensureMigrationTable(tx);
|
|
103
|
-
const currentVersion = yield this.getCurrentVersionWithTx(tx);
|
|
104
|
-
const fileNames = (0, utils_1.getMigrationFiles)(this.migrationsDir, direction);
|
|
105
|
-
if (direction === 'up') {
|
|
106
|
-
for (const fileName of fileNames) {
|
|
107
|
-
const id = fileNames.indexOf(fileName);
|
|
108
|
-
if (id >= currentVersion) {
|
|
109
|
-
yield Promise.all([
|
|
110
|
-
tx.file(node_path_1.default.join(this.migrationsDir, fileName)).execute(),
|
|
111
|
-
tx `INSERT INTO migrations(name, version) VALUES (${fileName}, ${id} + 1)`
|
|
112
|
-
]);
|
|
113
|
-
opts === null || opts === void 0 ? void 0 : opts.eventHandler(`Successfully migrated: ${fileName}`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
const start = fileNames.length - currentVersion;
|
|
119
|
-
for (let i = start; i < fileNames.length; i++) {
|
|
120
|
-
const file = (_a = fileNames[i]) !== null && _a !== void 0 ? _a : '';
|
|
121
|
-
yield Promise.all([
|
|
122
|
-
tx.file(node_path_1.default.join(this.migrationsDir, file)).execute(),
|
|
123
|
-
tx `DELETE FROM migrations WHERE version = ${fileNames.length - i}`
|
|
124
|
-
]);
|
|
125
|
-
opts === null || opts === void 0 ? void 0 : opts.eventHandler(`Successfully migrated: ${file}`);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
yield this.commit(tx);
|
|
129
|
-
}
|
|
130
|
-
catch (error) {
|
|
131
|
-
yield this.rollback(tx);
|
|
132
|
-
throw error;
|
|
133
|
-
}
|
|
134
|
-
finally {
|
|
135
|
-
yield this.releaseLock(tx);
|
|
136
|
-
tx.release();
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
getCurrentVersion() {
|
|
141
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
142
|
-
var _a;
|
|
143
|
-
const result = yield this
|
|
144
|
-
.dbClient `SELECT version FROM migrations ORDER BY version DESC LIMIT 1`;
|
|
145
|
-
return result.length > 0 ? (_a = result[0]) === null || _a === void 0 ? void 0 : _a['version'] : 0;
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
getCurrentVersionWithTx(tx) {
|
|
149
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
150
|
-
var _a;
|
|
151
|
-
const result = yield tx `SELECT version FROM migrations ORDER BY version DESC LIMIT 1`;
|
|
152
|
-
return result.length > 0 ? (_a = result[0]) === null || _a === void 0 ? void 0 : _a['version'] : 0;
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
acquireLock(tx) {
|
|
156
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
157
|
-
yield tx `SELECT pg_advisory_lock(21421431414441411)`;
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
releaseLock(tx) {
|
|
161
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
162
|
-
yield tx `SELECT pg_advisory_unlock(21421431414441411)`;
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
begin(tx) {
|
|
166
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
167
|
-
yield tx `BEGIN`;
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
commit(tx) {
|
|
171
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
172
|
-
yield tx `COMMIT`;
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
rollback(tx) {
|
|
176
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
177
|
-
yield tx `ROLLBACK`;
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
exports.Migrator = Migrator;
|
package/dist/cjs/src/utils.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createFile = exports.createFolderIfNotExists = exports.getMigrationFiles = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
|
|
6
|
-
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
7
|
-
const getMigrationFiles = (dir, direction) => {
|
|
8
|
-
const files = node_fs_1.default
|
|
9
|
-
.readdirSync(dir)
|
|
10
|
-
.filter(file => file.endsWith(`_${direction}.sql`));
|
|
11
|
-
if (direction == 'up') {
|
|
12
|
-
return files.sort();
|
|
13
|
-
}
|
|
14
|
-
return files.sort((a, b) => b.localeCompare(a));
|
|
15
|
-
};
|
|
16
|
-
exports.getMigrationFiles = getMigrationFiles;
|
|
17
|
-
const createFolderIfNotExists = (filePath) => {
|
|
18
|
-
if (!node_fs_1.default.existsSync(filePath)) {
|
|
19
|
-
node_fs_1.default.mkdirSync(filePath, { recursive: true });
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
exports.createFolderIfNotExists = createFolderIfNotExists;
|
|
23
|
-
const createFile = (migrationDir, name) => {
|
|
24
|
-
(0, exports.createFolderIfNotExists)(migrationDir);
|
|
25
|
-
const timestamp = new Date().toISOString().replaceAll(/[.:TZ-]/g, '');
|
|
26
|
-
const ressult = [];
|
|
27
|
-
let fileName = `${timestamp}_${name}_up.sql`;
|
|
28
|
-
let filePath = node_path_1.default.join(migrationDir, fileName);
|
|
29
|
-
node_fs_1.default.writeFileSync(filePath, '-- up SQL here');
|
|
30
|
-
ressult.push(filePath);
|
|
31
|
-
fileName = `${timestamp}_${name}_down.sql`;
|
|
32
|
-
filePath = node_path_1.default.join(migrationDir, fileName);
|
|
33
|
-
node_fs_1.default.writeFileSync(filePath, '-- down SQL here');
|
|
34
|
-
ressult.push(filePath);
|
|
35
|
-
return ressult;
|
|
36
|
-
};
|
|
37
|
-
exports.createFile = createFile;
|
package/dist/esm/src/cli.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/esm/src/index.js
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import { __awaiter } from "tslib";
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { getMigrationFiles } from './utils';
|
|
4
|
-
export class Migrator {
|
|
5
|
-
constructor(dbClient, migrationsDir = '') {
|
|
6
|
-
Object.defineProperty(this, "dbClient", {
|
|
7
|
-
enumerable: true,
|
|
8
|
-
configurable: true,
|
|
9
|
-
writable: true,
|
|
10
|
-
value: void 0
|
|
11
|
-
});
|
|
12
|
-
Object.defineProperty(this, "migrationsDir", {
|
|
13
|
-
enumerable: true,
|
|
14
|
-
configurable: true,
|
|
15
|
-
writable: true,
|
|
16
|
-
value: void 0
|
|
17
|
-
});
|
|
18
|
-
this.dbClient = dbClient;
|
|
19
|
-
this.migrationsDir = migrationsDir || `${process.cwd()}/migrations`;
|
|
20
|
-
}
|
|
21
|
-
ensureMigrationTable(tx) {
|
|
22
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
23
|
-
yield tx `CREATE TABLE IF NOT EXISTS migrations(
|
|
24
|
-
name varchar(500) PRIMARY KEY,
|
|
25
|
-
version smallint NOT NULL,
|
|
26
|
-
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);`;
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
up() {
|
|
30
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
31
|
-
return this.migrate('up');
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
down() {
|
|
35
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
36
|
-
return this.migrate('down');
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
go(version, opts) {
|
|
40
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
41
|
-
var _a, _b;
|
|
42
|
-
const tx = yield this.dbClient.reserve();
|
|
43
|
-
try {
|
|
44
|
-
yield this.acquireLock(tx);
|
|
45
|
-
yield this.begin(tx);
|
|
46
|
-
yield this.ensureMigrationTable(tx);
|
|
47
|
-
const currentVersion = yield this.getCurrentVersionWithTx(tx);
|
|
48
|
-
if (currentVersion === version) {
|
|
49
|
-
opts === null || opts === void 0 ? void 0 : opts.eventHandler(`Already at version ${version}`);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const direction = version > currentVersion ? 'up' : 'down';
|
|
53
|
-
const fileNames = getMigrationFiles(this.migrationsDir, direction);
|
|
54
|
-
if (direction === 'up') {
|
|
55
|
-
const fileVersion = Math.min(fileNames.length, version);
|
|
56
|
-
for (let i = currentVersion; i < fileVersion; i++) {
|
|
57
|
-
const file = (_a = fileNames[i]) !== null && _a !== void 0 ? _a : '';
|
|
58
|
-
yield Promise.all([
|
|
59
|
-
tx.file(path.join(this.migrationsDir, file)).execute(),
|
|
60
|
-
tx `INSERT INTO migrations(name, version) VALUES (${file}, ${i} + 1)`
|
|
61
|
-
]);
|
|
62
|
-
opts === null || opts === void 0 ? void 0 : opts.eventHandler(`Successfully migrated: ${file}`);
|
|
63
|
-
}
|
|
64
|
-
if (version > fileNames.length) {
|
|
65
|
-
opts === null || opts === void 0 ? void 0 : opts.eventHandler(`Currently at latest version: ${currentVersion}`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
const start = fileNames.length - currentVersion;
|
|
70
|
-
const end = start + (currentVersion - version);
|
|
71
|
-
for (let i = start; i < end; i++) {
|
|
72
|
-
const file = (_b = fileNames[i]) !== null && _b !== void 0 ? _b : '';
|
|
73
|
-
yield Promise.all([
|
|
74
|
-
tx.file(path.join(this.migrationsDir, file)).execute(),
|
|
75
|
-
tx `DELETE FROM migrations WHERE version = ${fileNames.length - i}`
|
|
76
|
-
]);
|
|
77
|
-
opts === null || opts === void 0 ? void 0 : opts.eventHandler(`Successfully migrated: ${file}`);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
yield this.commit(tx);
|
|
81
|
-
}
|
|
82
|
-
catch (error) {
|
|
83
|
-
yield this.rollback(tx);
|
|
84
|
-
throw error;
|
|
85
|
-
}
|
|
86
|
-
finally {
|
|
87
|
-
yield this.releaseLock(tx);
|
|
88
|
-
tx.release();
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
migrate(direction, opts) {
|
|
93
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
94
|
-
var _a;
|
|
95
|
-
const tx = yield this.dbClient.reserve();
|
|
96
|
-
try {
|
|
97
|
-
yield this.acquireLock(tx);
|
|
98
|
-
yield this.begin(tx);
|
|
99
|
-
yield this.ensureMigrationTable(tx);
|
|
100
|
-
const currentVersion = yield this.getCurrentVersionWithTx(tx);
|
|
101
|
-
const fileNames = getMigrationFiles(this.migrationsDir, direction);
|
|
102
|
-
if (direction === 'up') {
|
|
103
|
-
for (const fileName of fileNames) {
|
|
104
|
-
const id = fileNames.indexOf(fileName);
|
|
105
|
-
if (id >= currentVersion) {
|
|
106
|
-
yield Promise.all([
|
|
107
|
-
tx.file(path.join(this.migrationsDir, fileName)).execute(),
|
|
108
|
-
tx `INSERT INTO migrations(name, version) VALUES (${fileName}, ${id} + 1)`
|
|
109
|
-
]);
|
|
110
|
-
opts === null || opts === void 0 ? void 0 : opts.eventHandler(`Successfully migrated: ${fileName}`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
const start = fileNames.length - currentVersion;
|
|
116
|
-
for (let i = start; i < fileNames.length; i++) {
|
|
117
|
-
const file = (_a = fileNames[i]) !== null && _a !== void 0 ? _a : '';
|
|
118
|
-
yield Promise.all([
|
|
119
|
-
tx.file(path.join(this.migrationsDir, file)).execute(),
|
|
120
|
-
tx `DELETE FROM migrations WHERE version = ${fileNames.length - i}`
|
|
121
|
-
]);
|
|
122
|
-
opts === null || opts === void 0 ? void 0 : opts.eventHandler(`Successfully migrated: ${file}`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
yield this.commit(tx);
|
|
126
|
-
}
|
|
127
|
-
catch (error) {
|
|
128
|
-
yield this.rollback(tx);
|
|
129
|
-
throw error;
|
|
130
|
-
}
|
|
131
|
-
finally {
|
|
132
|
-
yield this.releaseLock(tx);
|
|
133
|
-
tx.release();
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
getCurrentVersion() {
|
|
138
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
139
|
-
var _a;
|
|
140
|
-
const result = yield this
|
|
141
|
-
.dbClient `SELECT version FROM migrations ORDER BY version DESC LIMIT 1`;
|
|
142
|
-
return result.length > 0 ? (_a = result[0]) === null || _a === void 0 ? void 0 : _a['version'] : 0;
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
getCurrentVersionWithTx(tx) {
|
|
146
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
147
|
-
var _a;
|
|
148
|
-
const result = yield tx `SELECT version FROM migrations ORDER BY version DESC LIMIT 1`;
|
|
149
|
-
return result.length > 0 ? (_a = result[0]) === null || _a === void 0 ? void 0 : _a['version'] : 0;
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
acquireLock(tx) {
|
|
153
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
154
|
-
yield tx `SELECT pg_advisory_lock(21421431414441411)`;
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
releaseLock(tx) {
|
|
158
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
159
|
-
yield tx `SELECT pg_advisory_unlock(21421431414441411)`;
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
begin(tx) {
|
|
163
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
164
|
-
yield tx `BEGIN`;
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
commit(tx) {
|
|
168
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
169
|
-
yield tx `COMMIT`;
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
rollback(tx) {
|
|
173
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
174
|
-
yield tx `ROLLBACK`;
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
}
|
package/dist/esm/src/utils.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export type Direction = 'up' | 'down';
|
|
2
|
-
export declare const getMigrationFiles: (dir: string, direction: Direction) => string[];
|
|
3
|
-
export declare const createFolderIfNotExists: (filePath: string) => void;
|
|
4
|
-
export declare const createFile: (migrationDir: string, name: string) => string[];
|
|
File without changes
|