@philosophocat/postgres-migrations 0.1.2 → 0.1.4
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 +1 -0
- package/dist/index.cjs +62 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +62 -34
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -91,10 +91,13 @@ var Repository = class {
|
|
|
91
91
|
var Migrator = class {
|
|
92
92
|
constructor(options) {
|
|
93
93
|
this.options = options;
|
|
94
|
-
this.repo = new Repository(options);
|
|
95
94
|
}
|
|
96
95
|
migrations = [];
|
|
97
|
-
|
|
96
|
+
log = (message, level = "info") => {
|
|
97
|
+
if (this.options.verbose || level === "warn") {
|
|
98
|
+
console.log(message);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
98
101
|
scan = async (dir) => {
|
|
99
102
|
const files = (0, import_node_fs.readdirSync)(dir).filter((f) => f.endsWith(".js") || f.endsWith(".ts")).sort();
|
|
100
103
|
for (const file of files) {
|
|
@@ -103,7 +106,7 @@ var Migrator = class {
|
|
|
103
106
|
const module2 = await import(filePath);
|
|
104
107
|
const m = module2.migration || module2.default;
|
|
105
108
|
if (!m) {
|
|
106
|
-
|
|
109
|
+
this.log(`Skipping ${file}: no migration export found`, "warn");
|
|
107
110
|
continue;
|
|
108
111
|
}
|
|
109
112
|
if (this.migrations.find((migration) => migration.name === name)) {
|
|
@@ -118,57 +121,82 @@ var Migrator = class {
|
|
|
118
121
|
};
|
|
119
122
|
up = async () => {
|
|
120
123
|
if (this.migrations.length === 0) {
|
|
121
|
-
return
|
|
124
|
+
return this.log("Empty migrations list");
|
|
125
|
+
}
|
|
126
|
+
const connection = await this.options.sql.reserve();
|
|
127
|
+
try {
|
|
128
|
+
await this.upLocked(connection);
|
|
129
|
+
} finally {
|
|
130
|
+
connection.release();
|
|
122
131
|
}
|
|
123
|
-
|
|
132
|
+
};
|
|
133
|
+
upLocked = async (connection) => {
|
|
134
|
+
const repo = new Repository({ ...this.options, sql: connection }), locked = await repo.tryLock();
|
|
124
135
|
if (!locked) {
|
|
125
|
-
return
|
|
136
|
+
return this.log("Migrations are locked by another process");
|
|
126
137
|
}
|
|
127
138
|
try {
|
|
128
|
-
const applied = await
|
|
139
|
+
const applied = await repo.listApplied();
|
|
129
140
|
for (const m of this.migrations) {
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
await m.up(trx);
|
|
134
|
-
await this.repo.markApplied(m.name, trx);
|
|
135
|
-
});
|
|
136
|
-
}
|
|
141
|
+
if (applied.has(m.name)) continue;
|
|
142
|
+
await this.applyMigration(m, repo, connection);
|
|
143
|
+
this.log(`Applied: ${m.name}`);
|
|
137
144
|
}
|
|
138
|
-
} catch (e) {
|
|
139
|
-
console.error("Migration failed:", e);
|
|
140
|
-
throw e;
|
|
141
145
|
} finally {
|
|
142
|
-
await
|
|
146
|
+
await repo.unlock();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
applyMigration = async (m, repo, connection) => {
|
|
150
|
+
await connection`BEGIN`;
|
|
151
|
+
try {
|
|
152
|
+
await m.up(connection);
|
|
153
|
+
await repo.markApplied(m.name, connection);
|
|
154
|
+
await connection`COMMIT`;
|
|
155
|
+
} catch (err) {
|
|
156
|
+
await connection`ROLLBACK`;
|
|
157
|
+
throw err;
|
|
143
158
|
}
|
|
144
159
|
};
|
|
145
160
|
down = async (count = 1) => {
|
|
146
|
-
const
|
|
161
|
+
const connection = await this.options.sql.reserve();
|
|
162
|
+
try {
|
|
163
|
+
await this.downLocked(connection, count);
|
|
164
|
+
} finally {
|
|
165
|
+
connection.release();
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
downLocked = async (connection, count) => {
|
|
169
|
+
const repo = new Repository({ ...this.options, sql: connection }), locked = await repo.tryLock();
|
|
147
170
|
if (!locked) {
|
|
148
|
-
return
|
|
171
|
+
return this.log("Migrations locked");
|
|
149
172
|
}
|
|
150
173
|
try {
|
|
151
|
-
const applied = await
|
|
152
|
-
|
|
174
|
+
const applied = await repo.listApplied(), toRollback = this.migrations.slice().reverse().filter((m) => applied.has(m.name));
|
|
175
|
+
if (toRollback.length === 0) {
|
|
176
|
+
return this.log("Nothing to rollback");
|
|
177
|
+
}
|
|
153
178
|
let rolledBackCount = 0;
|
|
154
179
|
for (const m of toRollback) {
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
await this.options.sql.begin(async (trx) => {
|
|
159
|
-
await m.down(trx);
|
|
160
|
-
await this.repo.unmarkApplied(m.name, trx);
|
|
161
|
-
});
|
|
162
|
-
console.log(`Reverted: ${m.name}`);
|
|
180
|
+
if (count > 0 && rolledBackCount >= count) break;
|
|
181
|
+
await this.revertMigration(m, repo, connection);
|
|
182
|
+
this.log(`Reverted: ${m.name}`);
|
|
163
183
|
rolledBackCount++;
|
|
164
184
|
}
|
|
165
185
|
} finally {
|
|
166
|
-
await
|
|
186
|
+
await repo.unlock();
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
revertMigration = async (m, repo, connection) => {
|
|
190
|
+
await connection`BEGIN`;
|
|
191
|
+
try {
|
|
192
|
+
await m.down(connection);
|
|
193
|
+
await repo.unmarkApplied(m.name, connection);
|
|
194
|
+
await connection`COMMIT`;
|
|
195
|
+
} catch (err) {
|
|
196
|
+
await connection`ROLLBACK`;
|
|
197
|
+
throw err;
|
|
167
198
|
}
|
|
168
199
|
};
|
|
169
|
-
async close() {
|
|
170
|
-
await this.options.sql.end();
|
|
171
|
-
}
|
|
172
200
|
};
|
|
173
201
|
// Annotate the CommonJS export names for ESM import in node:
|
|
174
202
|
0 && (module.exports = {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/migrator.ts","../src/repository.ts"],"sourcesContent":["export * from './types';\nexport * from './migrator';\n","import { readdirSync } from 'node:fs';\nimport { join, parse } from 'node:path';\nimport { Repository } from './repository';\nimport { Migration, MigratorOptions } from './types';\n\nexport class Migrator {\n\n private migrations: Migration[] = [];\n\n private repo: Repository;\n\n constructor(\n private readonly options: MigratorOptions\n ) {\n this.repo = new Repository(options);\n }\n\n public scan = async (\n dir: string\n ) => {\n const files = readdirSync(dir)\n .filter(f => f.endsWith('.js') || f.endsWith('.ts'))\n .sort();\n\n for (const file of files) {\n const filePath = join(dir, file);\n const name = parse(file).name;\n const module = await import(filePath);\n const m = module.migration || module.default;\n\n if (!m) {\n console.warn(`Skipping ${file}: no migration export found`);\n continue;\n }\n\n if (this.migrations.find(migration => migration.name === name)) {\n throw new Error(`Duplicated migration name: ${name}`);\n }\n\n this.migrations.push({\n up: m.up,\n down: m.down,\n name: m.name || name,\n });\n }\n }\n\n up = async () => {\n if (this.migrations.length === 0) {\n return console.log('Empty migrations list')\n }\n\n const locked = await this.repo.tryLock();\n if (!locked) {\n return console.log('Migrations are locked by another process');\n }\n\n try {\n const applied = await this.repo.listApplied();\n\n for (const m of this.migrations) {\n if (!applied.has(m.name)) {\n console.log(`Applying: ${ m.name }`);\n\n await this.options.sql.begin(async (trx) => {\n await m.up(trx);\n await this.repo.markApplied(m.name, trx);\n });\n }\n }\n } catch (e) {\n console.error('Migration failed:', e);\n throw e;\n } finally {\n await this.repo.unlock();\n }\n };\n\n down = async (\n count = 1\n ) => {\n const locked = await this.repo.tryLock();\n if (!locked) {\n return console.log('Migrations locked');\n }\n\n try {\n const applied = await this.repo.listApplied();\n const toRollback = this.migrations\n .slice()\n .reverse()\n .filter(m => applied.has(m.name));\n\n let rolledBackCount = 0;\n for (const m of toRollback) {\n if (rolledBackCount >= count && count !== -1) {\n break;\n }\n\n await this.options.sql.begin(async (trx) => {\n await m.down(trx);\n await this.repo.unmarkApplied(m.name, trx);\n });\n console.log(`Reverted: ${m.name}`);\n rolledBackCount++;\n }\n } finally {\n await this.repo.unlock();\n }\n }\n\n async close() {\n await this.options.sql.end();\n };\n}\n","import type { Sql, TransactionSql } from 'postgres';\nimport { MigratorOptions } from './types';\n\nexport class Repository {\n private readonly schema: string;\n private readonly tableName: string;\n private readonly sql: Sql;\n private readonly lockID: number;\n\n constructor({\n sql,\n schema = 'public',\n tableName = 'migrations',\n lockId = 2128506,\n }: MigratorOptions) {\n this.sql = sql;\n this.schema = schema;\n this.tableName = tableName;\n this.lockID = lockId;\n }\n\n async ensureTable() {\n await this.sql`CREATE SCHEMA IF NOT EXISTS ${this.sql(this.schema)}`;\n await this.sql`\n CREATE TABLE IF NOT EXISTS ${ this.sql(this.schema) }.${ this.sql(this.tableName) } (\n id SERIAL PRIMARY KEY,\n name VARCHAR(255) NOT NULL UNIQUE,\n created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n )\n `;\n };\n\n async tryLock(): Promise<boolean> {\n const [result] = await this.sql`\n SELECT pg_try_advisory_lock(${ this.lockID }) as locked\n `;\n return !!result?.locked;\n };\n\n async unlock(): Promise<void> {\n await this.sql`\n SELECT pg_advisory_unlock(${ this.lockID })\n `;\n };\n\n async listApplied(): Promise<Set<string>> {\n await this.ensureTable();\n\n const rows = await this.sql<{ name: string }[]>`\n SELECT name FROM ${ this.sql(this.schema) }.${ this.sql(this.tableName) }\n `;\n\n return new Set(rows.map(r => r.name));\n };\n\n async markApplied(\n name: string,\n trx?: TransactionSql\n ) {\n const sql = (trx || this.sql) as Sql;\n await sql`\n INSERT INTO ${ this.sql(this.schema) }.${ this.sql(this.tableName) } (name) VALUES (${name})\n `;\n }\n\n async unmarkApplied(\n name: string,\n trx?: TransactionSql\n ) {\n const sql = (trx || this.sql) as Sql;\n await sql`\n DELETE FROM ${ this.sql(this.schema) }.${ this.sql(this.tableName) } WHERE name = ${name}\n `;\n }\n}"],"mappings":"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","names":["module"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/migrator.ts","../src/repository.ts"],"sourcesContent":["export * from './types';\nexport * from './migrator';\n","import { readdirSync } from 'node:fs';\nimport { join, parse } from 'node:path';\nimport type { Sql } from 'postgres';\nimport { Repository } from './repository';\nimport { Migration, MigratorOptions } from './types';\n\nexport class Migrator {\n\n private migrations: Migration[] = [];\n\n constructor(\n private readonly options: MigratorOptions\n ) {};\n\n private log = (\n message: string,\n level: 'info' | 'warn' = 'info'\n ) => {\n if (this.options.verbose || level === 'warn') {\n console.log(message);\n }\n };\n\n public scan = async (\n dir: string\n ) => {\n const files = readdirSync(dir)\n .filter(f => f.endsWith('.js') || f.endsWith('.ts'))\n .sort();\n\n for (const file of files) {\n const filePath = join(dir, file);\n const name = parse(file).name;\n const module = await import(filePath);\n const m = module.migration || module.default;\n\n if (!m) {\n this.log(`Skipping ${file}: no migration export found`, 'warn');\n continue;\n }\n\n if (this.migrations.find(migration => migration.name === name)) {\n throw new Error(`Duplicated migration name: ${name}`);\n }\n\n this.migrations.push({\n up: m.up,\n down: m.down,\n name: m.name || name,\n });\n }\n }\n\n up = async () => {\n if (this.migrations.length === 0) {\n return this.log('Empty migrations list');\n }\n\n const connection = await this.options.sql.reserve();\n try {\n await this.upLocked(connection);\n } finally {\n connection.release();\n }\n };\n\n private upLocked = async (\n connection: Sql\n ) => {\n const\n repo = new Repository({ ...this.options, sql: connection }),\n locked = await repo.tryLock();\n\n if (!locked) {\n return this.log('Migrations are locked by another process');\n }\n\n try {\n const applied = await repo.listApplied();\n\n for (const m of this.migrations) {\n if (applied.has(m.name)) continue;\n await this.applyMigration(m, repo, connection);\n this.log(`Applied: ${m.name}`);\n }\n } finally {\n await repo.unlock();\n }\n }\n\n private applyMigration = async (\n m: Migration,\n repo: Repository,\n connection: Sql\n )=> {\n await connection`BEGIN`;\n try {\n await m.up(connection);\n await repo.markApplied(m.name, connection);\n await connection`COMMIT`;\n } catch (err) {\n await connection`ROLLBACK`;\n throw err;\n }\n }\n\n down = async (\n count = 1\n ) => {\n const connection = await this.options.sql.reserve();\n try {\n await this.downLocked(connection, count);\n } finally {\n connection.release();\n }\n }\n\n private downLocked = async (\n connection: Sql,\n count: number\n ) => {\n const\n repo = new Repository({ ...this.options, sql: connection }),\n locked = await repo.tryLock();\n\n if (!locked) {\n return this.log('Migrations locked');\n }\n\n try {\n const\n applied = await repo.listApplied(),\n toRollback = this.migrations\n .slice()\n .reverse()\n .filter(m => applied.has(m.name));\n\n if (toRollback.length === 0) {\n return this.log('Nothing to rollback');\n }\n\n let rolledBackCount = 0;\n for (const m of toRollback) {\n if (count > 0 && rolledBackCount >= count) break;\n\n await this.revertMigration(m, repo, connection);\n this.log(`Reverted: ${m.name}`);\n\n rolledBackCount++;\n }\n } finally {\n await repo.unlock();\n }\n }\n\n private revertMigration = async(\n m: Migration,\n repo: Repository,\n connection: Sql\n ) => {\n await connection`BEGIN`;\n try {\n await m.down(connection);\n await repo.unmarkApplied(m.name, connection);\n await connection`COMMIT`;\n } catch (err) {\n await connection`ROLLBACK`;\n throw err;\n }\n }\n}\n","import type { Sql } from 'postgres';\nimport { MigratorOptions } from './types';\n\nexport class Repository {\n private readonly schema: string;\n private readonly tableName: string;\n private readonly sql: Sql;\n private readonly lockID: number;\n\n constructor({\n sql,\n schema = 'public',\n tableName = 'migrations',\n lockId = 2128506,\n }: MigratorOptions) {\n this.sql = sql;\n this.schema = schema;\n this.tableName = tableName;\n this.lockID = lockId;\n }\n\n async ensureTable() {\n await this.sql`CREATE SCHEMA IF NOT EXISTS ${this.sql(this.schema)}`;\n await this.sql`\n CREATE TABLE IF NOT EXISTS ${ this.sql(this.schema) }.${ this.sql(this.tableName) } (\n id SERIAL PRIMARY KEY,\n name VARCHAR(255) NOT NULL UNIQUE,\n created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n )\n `;\n };\n\n async tryLock(): Promise<boolean> {\n const [result] = await this.sql`\n SELECT pg_try_advisory_lock(${ this.lockID }) as locked\n `;\n return !!result?.locked;\n };\n\n async unlock(): Promise<void> {\n await this.sql`\n SELECT pg_advisory_unlock(${ this.lockID })\n `;\n };\n\n async listApplied(): Promise<Set<string>> {\n await this.ensureTable();\n\n const rows = await this.sql<{ name: string }[]>`\n SELECT name FROM ${ this.sql(this.schema) }.${ this.sql(this.tableName) }\n `;\n\n return new Set(rows.map(r => r.name));\n };\n\n async markApplied(\n name: string,\n trx?: Sql\n ) {\n const sql = trx || this.sql;\n await sql`\n INSERT INTO ${ this.sql(this.schema) }.${ this.sql(this.tableName) } (name) VALUES (${name})\n `;\n }\n\n async unmarkApplied(\n name: string,\n trx?: Sql\n ) {\n const sql = trx || this.sql;\n await sql`\n DELETE FROM ${ this.sql(this.schema) }.${ this.sql(this.tableName) } WHERE name = ${name}\n `;\n }\n}"],"mappings":"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","names":["module"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -10,6 +10,7 @@ interface MigratorOptions {
|
|
|
10
10
|
schema?: string;
|
|
11
11
|
tableName?: string;
|
|
12
12
|
lockId?: number;
|
|
13
|
+
verbose?: boolean;
|
|
13
14
|
}
|
|
14
15
|
interface MigrationRecord {
|
|
15
16
|
id: number;
|
|
@@ -20,12 +21,15 @@ interface MigrationRecord {
|
|
|
20
21
|
declare class Migrator {
|
|
21
22
|
private readonly options;
|
|
22
23
|
private migrations;
|
|
23
|
-
private repo;
|
|
24
24
|
constructor(options: MigratorOptions);
|
|
25
|
+
private log;
|
|
25
26
|
scan: (dir: string) => Promise<void>;
|
|
26
27
|
up: () => Promise<void>;
|
|
28
|
+
private upLocked;
|
|
29
|
+
private applyMigration;
|
|
27
30
|
down: (count?: number) => Promise<void>;
|
|
28
|
-
|
|
31
|
+
private downLocked;
|
|
32
|
+
private revertMigration;
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
export { type Migration, type MigrationRecord, Migrator, type MigratorOptions };
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ interface MigratorOptions {
|
|
|
10
10
|
schema?: string;
|
|
11
11
|
tableName?: string;
|
|
12
12
|
lockId?: number;
|
|
13
|
+
verbose?: boolean;
|
|
13
14
|
}
|
|
14
15
|
interface MigrationRecord {
|
|
15
16
|
id: number;
|
|
@@ -20,12 +21,15 @@ interface MigrationRecord {
|
|
|
20
21
|
declare class Migrator {
|
|
21
22
|
private readonly options;
|
|
22
23
|
private migrations;
|
|
23
|
-
private repo;
|
|
24
24
|
constructor(options: MigratorOptions);
|
|
25
|
+
private log;
|
|
25
26
|
scan: (dir: string) => Promise<void>;
|
|
26
27
|
up: () => Promise<void>;
|
|
28
|
+
private upLocked;
|
|
29
|
+
private applyMigration;
|
|
27
30
|
down: (count?: number) => Promise<void>;
|
|
28
|
-
|
|
31
|
+
private downLocked;
|
|
32
|
+
private revertMigration;
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
export { type Migration, type MigrationRecord, Migrator, type MigratorOptions };
|
package/dist/index.js
CHANGED
|
@@ -65,10 +65,13 @@ var Repository = class {
|
|
|
65
65
|
var Migrator = class {
|
|
66
66
|
constructor(options) {
|
|
67
67
|
this.options = options;
|
|
68
|
-
this.repo = new Repository(options);
|
|
69
68
|
}
|
|
70
69
|
migrations = [];
|
|
71
|
-
|
|
70
|
+
log = (message, level = "info") => {
|
|
71
|
+
if (this.options.verbose || level === "warn") {
|
|
72
|
+
console.log(message);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
72
75
|
scan = async (dir) => {
|
|
73
76
|
const files = readdirSync(dir).filter((f) => f.endsWith(".js") || f.endsWith(".ts")).sort();
|
|
74
77
|
for (const file of files) {
|
|
@@ -77,7 +80,7 @@ var Migrator = class {
|
|
|
77
80
|
const module = await import(filePath);
|
|
78
81
|
const m = module.migration || module.default;
|
|
79
82
|
if (!m) {
|
|
80
|
-
|
|
83
|
+
this.log(`Skipping ${file}: no migration export found`, "warn");
|
|
81
84
|
continue;
|
|
82
85
|
}
|
|
83
86
|
if (this.migrations.find((migration) => migration.name === name)) {
|
|
@@ -92,57 +95,82 @@ var Migrator = class {
|
|
|
92
95
|
};
|
|
93
96
|
up = async () => {
|
|
94
97
|
if (this.migrations.length === 0) {
|
|
95
|
-
return
|
|
98
|
+
return this.log("Empty migrations list");
|
|
99
|
+
}
|
|
100
|
+
const connection = await this.options.sql.reserve();
|
|
101
|
+
try {
|
|
102
|
+
await this.upLocked(connection);
|
|
103
|
+
} finally {
|
|
104
|
+
connection.release();
|
|
96
105
|
}
|
|
97
|
-
|
|
106
|
+
};
|
|
107
|
+
upLocked = async (connection) => {
|
|
108
|
+
const repo = new Repository({ ...this.options, sql: connection }), locked = await repo.tryLock();
|
|
98
109
|
if (!locked) {
|
|
99
|
-
return
|
|
110
|
+
return this.log("Migrations are locked by another process");
|
|
100
111
|
}
|
|
101
112
|
try {
|
|
102
|
-
const applied = await
|
|
113
|
+
const applied = await repo.listApplied();
|
|
103
114
|
for (const m of this.migrations) {
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
await m.up(trx);
|
|
108
|
-
await this.repo.markApplied(m.name, trx);
|
|
109
|
-
});
|
|
110
|
-
}
|
|
115
|
+
if (applied.has(m.name)) continue;
|
|
116
|
+
await this.applyMigration(m, repo, connection);
|
|
117
|
+
this.log(`Applied: ${m.name}`);
|
|
111
118
|
}
|
|
112
|
-
} catch (e) {
|
|
113
|
-
console.error("Migration failed:", e);
|
|
114
|
-
throw e;
|
|
115
119
|
} finally {
|
|
116
|
-
await
|
|
120
|
+
await repo.unlock();
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
applyMigration = async (m, repo, connection) => {
|
|
124
|
+
await connection`BEGIN`;
|
|
125
|
+
try {
|
|
126
|
+
await m.up(connection);
|
|
127
|
+
await repo.markApplied(m.name, connection);
|
|
128
|
+
await connection`COMMIT`;
|
|
129
|
+
} catch (err) {
|
|
130
|
+
await connection`ROLLBACK`;
|
|
131
|
+
throw err;
|
|
117
132
|
}
|
|
118
133
|
};
|
|
119
134
|
down = async (count = 1) => {
|
|
120
|
-
const
|
|
135
|
+
const connection = await this.options.sql.reserve();
|
|
136
|
+
try {
|
|
137
|
+
await this.downLocked(connection, count);
|
|
138
|
+
} finally {
|
|
139
|
+
connection.release();
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
downLocked = async (connection, count) => {
|
|
143
|
+
const repo = new Repository({ ...this.options, sql: connection }), locked = await repo.tryLock();
|
|
121
144
|
if (!locked) {
|
|
122
|
-
return
|
|
145
|
+
return this.log("Migrations locked");
|
|
123
146
|
}
|
|
124
147
|
try {
|
|
125
|
-
const applied = await
|
|
126
|
-
|
|
148
|
+
const applied = await repo.listApplied(), toRollback = this.migrations.slice().reverse().filter((m) => applied.has(m.name));
|
|
149
|
+
if (toRollback.length === 0) {
|
|
150
|
+
return this.log("Nothing to rollback");
|
|
151
|
+
}
|
|
127
152
|
let rolledBackCount = 0;
|
|
128
153
|
for (const m of toRollback) {
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
await this.options.sql.begin(async (trx) => {
|
|
133
|
-
await m.down(trx);
|
|
134
|
-
await this.repo.unmarkApplied(m.name, trx);
|
|
135
|
-
});
|
|
136
|
-
console.log(`Reverted: ${m.name}`);
|
|
154
|
+
if (count > 0 && rolledBackCount >= count) break;
|
|
155
|
+
await this.revertMigration(m, repo, connection);
|
|
156
|
+
this.log(`Reverted: ${m.name}`);
|
|
137
157
|
rolledBackCount++;
|
|
138
158
|
}
|
|
139
159
|
} finally {
|
|
140
|
-
await
|
|
160
|
+
await repo.unlock();
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
revertMigration = async (m, repo, connection) => {
|
|
164
|
+
await connection`BEGIN`;
|
|
165
|
+
try {
|
|
166
|
+
await m.down(connection);
|
|
167
|
+
await repo.unmarkApplied(m.name, connection);
|
|
168
|
+
await connection`COMMIT`;
|
|
169
|
+
} catch (err) {
|
|
170
|
+
await connection`ROLLBACK`;
|
|
171
|
+
throw err;
|
|
141
172
|
}
|
|
142
173
|
};
|
|
143
|
-
async close() {
|
|
144
|
-
await this.options.sql.end();
|
|
145
|
-
}
|
|
146
174
|
};
|
|
147
175
|
export {
|
|
148
176
|
Migrator
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/migrator.ts","../src/repository.ts"],"sourcesContent":["import { readdirSync } from 'node:fs';\nimport { join, parse } from 'node:path';\nimport { Repository } from './repository';\nimport { Migration, MigratorOptions } from './types';\n\nexport class Migrator {\n\n private migrations: Migration[] = [];\n\n private repo: Repository;\n\n constructor(\n private readonly options: MigratorOptions\n ) {\n this.repo = new Repository(options);\n }\n\n public scan = async (\n dir: string\n ) => {\n const files = readdirSync(dir)\n .filter(f => f.endsWith('.js') || f.endsWith('.ts'))\n .sort();\n\n for (const file of files) {\n const filePath = join(dir, file);\n const name = parse(file).name;\n const module = await import(filePath);\n const m = module.migration || module.default;\n\n if (!m) {\n console.warn(`Skipping ${file}: no migration export found`);\n continue;\n }\n\n if (this.migrations.find(migration => migration.name === name)) {\n throw new Error(`Duplicated migration name: ${name}`);\n }\n\n this.migrations.push({\n up: m.up,\n down: m.down,\n name: m.name || name,\n });\n }\n }\n\n up = async () => {\n if (this.migrations.length === 0) {\n return console.log('Empty migrations list')\n }\n\n const locked = await this.repo.tryLock();\n if (!locked) {\n return console.log('Migrations are locked by another process');\n }\n\n try {\n const applied = await this.repo.listApplied();\n\n for (const m of this.migrations) {\n if (!applied.has(m.name)) {\n console.log(`Applying: ${ m.name }`);\n\n await this.options.sql.begin(async (trx) => {\n await m.up(trx);\n await this.repo.markApplied(m.name, trx);\n });\n }\n }\n } catch (e) {\n console.error('Migration failed:', e);\n throw e;\n } finally {\n await this.repo.unlock();\n }\n };\n\n down = async (\n count = 1\n ) => {\n const locked = await this.repo.tryLock();\n if (!locked) {\n return console.log('Migrations locked');\n }\n\n try {\n const applied = await this.repo.listApplied();\n const toRollback = this.migrations\n .slice()\n .reverse()\n .filter(m => applied.has(m.name));\n\n let rolledBackCount = 0;\n for (const m of toRollback) {\n if (rolledBackCount >= count && count !== -1) {\n break;\n }\n\n await this.options.sql.begin(async (trx) => {\n await m.down(trx);\n await this.repo.unmarkApplied(m.name, trx);\n });\n console.log(`Reverted: ${m.name}`);\n rolledBackCount++;\n }\n } finally {\n await this.repo.unlock();\n }\n }\n\n async close() {\n await this.options.sql.end();\n };\n}\n","import type { Sql, TransactionSql } from 'postgres';\nimport { MigratorOptions } from './types';\n\nexport class Repository {\n private readonly schema: string;\n private readonly tableName: string;\n private readonly sql: Sql;\n private readonly lockID: number;\n\n constructor({\n sql,\n schema = 'public',\n tableName = 'migrations',\n lockId = 2128506,\n }: MigratorOptions) {\n this.sql = sql;\n this.schema = schema;\n this.tableName = tableName;\n this.lockID = lockId;\n }\n\n async ensureTable() {\n await this.sql`CREATE SCHEMA IF NOT EXISTS ${this.sql(this.schema)}`;\n await this.sql`\n CREATE TABLE IF NOT EXISTS ${ this.sql(this.schema) }.${ this.sql(this.tableName) } (\n id SERIAL PRIMARY KEY,\n name VARCHAR(255) NOT NULL UNIQUE,\n created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n )\n `;\n };\n\n async tryLock(): Promise<boolean> {\n const [result] = await this.sql`\n SELECT pg_try_advisory_lock(${ this.lockID }) as locked\n `;\n return !!result?.locked;\n };\n\n async unlock(): Promise<void> {\n await this.sql`\n SELECT pg_advisory_unlock(${ this.lockID })\n `;\n };\n\n async listApplied(): Promise<Set<string>> {\n await this.ensureTable();\n\n const rows = await this.sql<{ name: string }[]>`\n SELECT name FROM ${ this.sql(this.schema) }.${ this.sql(this.tableName) }\n `;\n\n return new Set(rows.map(r => r.name));\n };\n\n async markApplied(\n name: string,\n trx?: TransactionSql\n ) {\n const sql = (trx || this.sql) as Sql;\n await sql`\n INSERT INTO ${ this.sql(this.schema) }.${ this.sql(this.tableName) } (name) VALUES (${name})\n `;\n }\n\n async unmarkApplied(\n name: string,\n trx?: TransactionSql\n ) {\n const sql = (trx || this.sql) as Sql;\n await sql`\n DELETE FROM ${ this.sql(this.schema) }.${ this.sql(this.tableName) } WHERE name = ${name}\n `;\n }\n}"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/migrator.ts","../src/repository.ts"],"sourcesContent":["import { readdirSync } from 'node:fs';\nimport { join, parse } from 'node:path';\nimport type { Sql } from 'postgres';\nimport { Repository } from './repository';\nimport { Migration, MigratorOptions } from './types';\n\nexport class Migrator {\n\n private migrations: Migration[] = [];\n\n constructor(\n private readonly options: MigratorOptions\n ) {};\n\n private log = (\n message: string,\n level: 'info' | 'warn' = 'info'\n ) => {\n if (this.options.verbose || level === 'warn') {\n console.log(message);\n }\n };\n\n public scan = async (\n dir: string\n ) => {\n const files = readdirSync(dir)\n .filter(f => f.endsWith('.js') || f.endsWith('.ts'))\n .sort();\n\n for (const file of files) {\n const filePath = join(dir, file);\n const name = parse(file).name;\n const module = await import(filePath);\n const m = module.migration || module.default;\n\n if (!m) {\n this.log(`Skipping ${file}: no migration export found`, 'warn');\n continue;\n }\n\n if (this.migrations.find(migration => migration.name === name)) {\n throw new Error(`Duplicated migration name: ${name}`);\n }\n\n this.migrations.push({\n up: m.up,\n down: m.down,\n name: m.name || name,\n });\n }\n }\n\n up = async () => {\n if (this.migrations.length === 0) {\n return this.log('Empty migrations list');\n }\n\n const connection = await this.options.sql.reserve();\n try {\n await this.upLocked(connection);\n } finally {\n connection.release();\n }\n };\n\n private upLocked = async (\n connection: Sql\n ) => {\n const\n repo = new Repository({ ...this.options, sql: connection }),\n locked = await repo.tryLock();\n\n if (!locked) {\n return this.log('Migrations are locked by another process');\n }\n\n try {\n const applied = await repo.listApplied();\n\n for (const m of this.migrations) {\n if (applied.has(m.name)) continue;\n await this.applyMigration(m, repo, connection);\n this.log(`Applied: ${m.name}`);\n }\n } finally {\n await repo.unlock();\n }\n }\n\n private applyMigration = async (\n m: Migration,\n repo: Repository,\n connection: Sql\n )=> {\n await connection`BEGIN`;\n try {\n await m.up(connection);\n await repo.markApplied(m.name, connection);\n await connection`COMMIT`;\n } catch (err) {\n await connection`ROLLBACK`;\n throw err;\n }\n }\n\n down = async (\n count = 1\n ) => {\n const connection = await this.options.sql.reserve();\n try {\n await this.downLocked(connection, count);\n } finally {\n connection.release();\n }\n }\n\n private downLocked = async (\n connection: Sql,\n count: number\n ) => {\n const\n repo = new Repository({ ...this.options, sql: connection }),\n locked = await repo.tryLock();\n\n if (!locked) {\n return this.log('Migrations locked');\n }\n\n try {\n const\n applied = await repo.listApplied(),\n toRollback = this.migrations\n .slice()\n .reverse()\n .filter(m => applied.has(m.name));\n\n if (toRollback.length === 0) {\n return this.log('Nothing to rollback');\n }\n\n let rolledBackCount = 0;\n for (const m of toRollback) {\n if (count > 0 && rolledBackCount >= count) break;\n\n await this.revertMigration(m, repo, connection);\n this.log(`Reverted: ${m.name}`);\n\n rolledBackCount++;\n }\n } finally {\n await repo.unlock();\n }\n }\n\n private revertMigration = async(\n m: Migration,\n repo: Repository,\n connection: Sql\n ) => {\n await connection`BEGIN`;\n try {\n await m.down(connection);\n await repo.unmarkApplied(m.name, connection);\n await connection`COMMIT`;\n } catch (err) {\n await connection`ROLLBACK`;\n throw err;\n }\n }\n}\n","import type { Sql } from 'postgres';\nimport { MigratorOptions } from './types';\n\nexport class Repository {\n private readonly schema: string;\n private readonly tableName: string;\n private readonly sql: Sql;\n private readonly lockID: number;\n\n constructor({\n sql,\n schema = 'public',\n tableName = 'migrations',\n lockId = 2128506,\n }: MigratorOptions) {\n this.sql = sql;\n this.schema = schema;\n this.tableName = tableName;\n this.lockID = lockId;\n }\n\n async ensureTable() {\n await this.sql`CREATE SCHEMA IF NOT EXISTS ${this.sql(this.schema)}`;\n await this.sql`\n CREATE TABLE IF NOT EXISTS ${ this.sql(this.schema) }.${ this.sql(this.tableName) } (\n id SERIAL PRIMARY KEY,\n name VARCHAR(255) NOT NULL UNIQUE,\n created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n )\n `;\n };\n\n async tryLock(): Promise<boolean> {\n const [result] = await this.sql`\n SELECT pg_try_advisory_lock(${ this.lockID }) as locked\n `;\n return !!result?.locked;\n };\n\n async unlock(): Promise<void> {\n await this.sql`\n SELECT pg_advisory_unlock(${ this.lockID })\n `;\n };\n\n async listApplied(): Promise<Set<string>> {\n await this.ensureTable();\n\n const rows = await this.sql<{ name: string }[]>`\n SELECT name FROM ${ this.sql(this.schema) }.${ this.sql(this.tableName) }\n `;\n\n return new Set(rows.map(r => r.name));\n };\n\n async markApplied(\n name: string,\n trx?: Sql\n ) {\n const sql = trx || this.sql;\n await sql`\n INSERT INTO ${ this.sql(this.schema) }.${ this.sql(this.tableName) } (name) VALUES (${name})\n `;\n }\n\n async unmarkApplied(\n name: string,\n trx?: Sql\n ) {\n const sql = trx || this.sql;\n await sql`\n DELETE FROM ${ this.sql(this.schema) }.${ this.sql(this.tableName) } WHERE name = ${name}\n `;\n }\n}"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@philosophocat/postgres-migrations",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Simple migrations for postgres.js",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./dist/index.
|
|
7
|
-
"module": "./dist/index.
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
|
-
"import": "./dist/index.
|
|
13
|
-
"require": "./dist/index.
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
-
"build": "tsup",
|
|
20
|
+
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
21
21
|
"dev": "tsup --watch",
|
|
22
22
|
"lint": "tsc --noEmit",
|
|
23
23
|
"test": "vitest run",
|