pgroll 0.0.9 → 0.0.10

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 CHANGED
@@ -94,12 +94,13 @@ pgroll [global options] <command>
94
94
 
95
95
  ### Global options
96
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. |
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
+ | `-s, --schema <schema>` | Schema for pgroll's internal migrations table (default: `public`). |
102
+ | `-V, --version` | Print the `pgroll` version. |
103
+ | `-h, --help` | Show help. |
103
104
 
104
105
  ### Commands
105
106
 
@@ -159,12 +160,13 @@ await migrator.go(0, { eventHandler: info => console.log(info) });
159
160
  await sql.end();
160
161
  ```
161
162
 
162
- ### `new Migrator(dbClient, migrationsDir?)`
163
+ ### `new Migrator(dbClient, migrationsDir?, schema?)`
163
164
 
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`. |
165
+ | Parameter | Type | Description |
166
+ | --------------- | ------------------- | -------------------------------------------------------------------- |
167
+ | `dbClient` | `Sql` (PostgresJS) | A `postgres` client instance. |
168
+ | `migrationsDir` | `string` (optional) | Directory of migration files. Defaults to `<cwd>/migrations`. |
169
+ | `schema` | `string` (optional) | Schema for pgroll's internal migrations table. Defaults to `public`. |
168
170
 
169
171
  ### Methods
170
172
 
@@ -180,10 +182,12 @@ human-readable message as each migration is applied.
180
182
 
181
183
  ## How it works
182
184
 
183
- On the first run, `pgroll` creates a bookkeeping table:
185
+ On the first run, `pgroll` creates a bookkeeping table in the configured schema (default
186
+ `public`, override with `-s, --schema`). All references to it are schema-qualified, so it is
187
+ unaffected by any `search_path` a migration sets:
184
188
 
185
189
  ```sql
186
- CREATE TABLE IF NOT EXISTS migrations (
190
+ CREATE TABLE IF NOT EXISTS <schema>.migrations (
187
191
  name varchar(500) PRIMARY KEY,
188
192
  version smallint NOT NULL,
189
193
  applied_at timestamp DEFAULT CURRENT_TIMESTAMP
package/dist/src/cli.js CHANGED
@@ -10,8 +10,9 @@ let migrator;
10
10
  program
11
11
  .version('0.0.9')
12
12
  .description('Database migration tool')
13
- .option('-d, --migrationDir <filepath>', 'Specify migration directory(Default: ./migrations)')
13
+ .option('-d, --migrationDir <filepath>', 'Specify migration directory (Default: ./migrations)')
14
14
  .option('-u, --url <url>', 'PostgreSQL connection URL (overrides PG* env vars)')
15
+ .option('-s, --schema <schema>', 'Specify schema (Default: public)')
15
16
  .hook('preAction', cmd => {
16
17
  const opts = cmd.opts();
17
18
  const pgOptions = {
@@ -19,7 +20,7 @@ program
19
20
  // do nothing
20
21
  }
21
22
  };
22
- migrator = new Migrator(opts.url ? postgres(opts.url, pgOptions) : postgres(pgOptions), opts.migrationDir);
23
+ migrator = new Migrator(opts.url ? postgres(opts.url, pgOptions) : postgres(pgOptions), opts.migrationDir, opts.schema);
23
24
  });
24
25
  program
25
26
  .command('up')
@@ -64,8 +65,8 @@ program
64
65
  .description('Navigate to a specific version; version 0 performs a rollback, reverting all migrations.')
65
66
  .argument('<version>', 'version to migrate to')
66
67
  .action(async (version) => {
67
- const parsedVersion = Number.parseInt(version, 10);
68
- if (Number.isNaN(parsedVersion)) {
68
+ const parsedVersion = Number(version);
69
+ if (!Number.isSafeInteger(parsedVersion) || parsedVersion < 0) {
69
70
  console.error('Invalid version number.');
70
71
  process.exit(1);
71
72
  }
@@ -5,6 +5,7 @@ interface Option {
5
5
  }
6
6
  export interface IMigrator {
7
7
  migrationsDir: string;
8
+ schema: string;
8
9
  up: (opts?: Option) => Promise<void>;
9
10
  down: (opts?: Option) => Promise<void>;
10
11
  go: (version: number, opts?: Option) => Promise<void>;
@@ -13,7 +14,8 @@ export interface IMigrator {
13
14
  export declare class Migrator implements IMigrator {
14
15
  private readonly dbClient;
15
16
  readonly migrationsDir: string;
16
- constructor(dbClient: Sql, migrationsDir?: string);
17
+ readonly schema: string;
18
+ constructor(dbClient: Sql, migrationsDir?: string, schema?: string);
17
19
  ensureMigrationTable(tx: ReservedSql): Promise<void>;
18
20
  up(): Promise<void>;
19
21
  down(): Promise<void>;
package/dist/src/index.js CHANGED
@@ -4,12 +4,15 @@ import { getMigrationFiles } from "./utils.js";
4
4
  export class Migrator {
5
5
  dbClient;
6
6
  migrationsDir;
7
- constructor(dbClient, migrationsDir = '') {
7
+ schema;
8
+ constructor(dbClient, migrationsDir = '', schema = 'public') {
8
9
  this.dbClient = dbClient;
9
10
  this.migrationsDir = migrationsDir || `${process.cwd()}/migrations`;
11
+ this.schema = schema;
10
12
  }
11
13
  async ensureMigrationTable(tx) {
12
- await tx `CREATE TABLE IF NOT EXISTS migrations(
14
+ await tx `CREATE SCHEMA IF NOT EXISTS ${tx(this.schema)}`;
15
+ await tx `CREATE TABLE IF NOT EXISTS ${tx(this.schema)}.migrations(
13
16
  name varchar(500) PRIMARY KEY,
14
17
  version smallint NOT NULL,
15
18
  applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);`;
@@ -29,6 +32,7 @@ export class Migrator {
29
32
  const currentVersion = await this.getCurrentVersionWithTx(tx);
30
33
  if (currentVersion === version) {
31
34
  opts?.eventHandler(`Already at version ${version}`);
35
+ await this.commit(tx);
32
36
  return;
33
37
  }
34
38
  const direction = version > currentVersion ? 'up' : 'down';
@@ -37,10 +41,8 @@ export class Migrator {
37
41
  const fileVersion = Math.min(fileNames.length, version);
38
42
  for (let i = currentVersion; i < fileVersion; i++) {
39
43
  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
+ await tx.file(path.join(this.migrationsDir, file)).execute();
45
+ await tx `INSERT INTO ${tx(this.schema)}.migrations(name, version) VALUES (${file}, ${i} + 1)`;
44
46
  opts?.eventHandler(`Successfully migrated: ${file}`);
45
47
  }
46
48
  if (version > fileNames.length) {
@@ -54,10 +56,8 @@ export class Migrator {
54
56
  const end = start + (currentVersion - version);
55
57
  for (let i = start; i < end; i++) {
56
58
  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
- ]);
59
+ await tx.file(path.join(this.migrationsDir, file)).execute();
60
+ await tx `DELETE FROM ${tx(this.schema)}.migrations WHERE version = ${fileNames.length - i}`;
61
61
  opts?.eventHandler(`Successfully migrated: ${file}`);
62
62
  }
63
63
  }
@@ -84,10 +84,8 @@ export class Migrator {
84
84
  for (const fileName of fileNames) {
85
85
  const id = fileNames.indexOf(fileName);
86
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
- ]);
87
+ await tx.file(path.join(this.migrationsDir, fileName)).execute();
88
+ await tx `INSERT INTO ${tx(this.schema)}.migrations(name, version) VALUES (${fileName}, ${id} + 1)`;
91
89
  opts?.eventHandler(`Successfully migrated: ${fileName}`);
92
90
  }
93
91
  }
@@ -97,10 +95,8 @@ export class Migrator {
97
95
  const start = fileNames.length - currentVersion;
98
96
  for (let i = start; i < fileNames.length; i++) {
99
97
  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
- ]);
98
+ await tx.file(path.join(this.migrationsDir, file)).execute();
99
+ await tx `DELETE FROM ${tx(this.schema)}.migrations WHERE version = ${fileNames.length - i}`;
104
100
  opts?.eventHandler(`Successfully migrated: ${file}`);
105
101
  }
106
102
  }
@@ -116,11 +112,12 @@ export class Migrator {
116
112
  }
117
113
  }
118
114
  async getCurrentVersion() {
119
- const result = await this.dbClient `SELECT version FROM migrations ORDER BY version DESC LIMIT 1`;
115
+ const result = await this
116
+ .dbClient `SELECT version FROM ${this.dbClient(this.schema)}.migrations ORDER BY version DESC LIMIT 1`;
120
117
  return result.length > 0 ? result[0]?.['version'] : 0;
121
118
  }
122
119
  async getCurrentVersionWithTx(tx) {
123
- const result = await tx `SELECT version FROM migrations ORDER BY version DESC LIMIT 1`;
120
+ const result = await tx `SELECT version FROM ${tx(this.schema)}.migrations ORDER BY version DESC LIMIT 1`;
124
121
  return result.length > 0 ? result[0]?.['version'] : 0;
125
122
  }
126
123
  async acquireLock(tx) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgroll",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "Postgres migration tool",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -50,24 +50,24 @@
50
50
  "license": "ISC",
51
51
  "devDependencies": {
52
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",
53
+ "@types/node": "^26.0.1",
54
+ "@vitest/coverage-v8": "^4.1.9",
55
+ "@vitest/eslint-plugin": "^1.6.20",
56
+ "eslint": "^10.5.0",
57
57
  "eslint-config-prettier": "^10.1.8",
58
- "eslint-import-resolver-typescript": "^4.4.4",
59
- "eslint-plugin-import-x": "^4.16.2",
58
+ "eslint-import-resolver-typescript": "^4.4.5",
59
+ "eslint-plugin-import-x": "^4.17.0",
60
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",
61
+ "eslint-plugin-sonarjs": "^4.1.0",
62
+ "eslint-plugin-unicorn": "^69.0.0",
63
+ "globals": "^17.7.0",
64
+ "prettier": "^3.8.4",
65
65
  "typescript": "^6.0.3",
66
- "typescript-eslint": "^8.60.0",
67
- "vitest": "^4.1.7"
66
+ "typescript-eslint": "^8.62.0",
67
+ "vitest": "^4.1.9"
68
68
  },
69
69
  "dependencies": {
70
- "commander": "^14.0.3",
70
+ "commander": "^15.0.0",
71
71
  "postgres": "^3.4.9"
72
72
  }
73
73
  }