@blackglory/sqlite3-migrations 0.1.0 → 0.2.0

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
@@ -17,8 +17,8 @@ yarn add @blackglory/sqlite3-migrations
17
17
  ```ts
18
18
  interface IMigration {
19
19
  version: number
20
- up: string | ((db: Database) => PromiseLike<void>)
21
- down: string | ((db: Database) => PromiseLike<void>)
20
+ up: string | ((db: Database) => void)
21
+ down: string | ((db: Database) => void)
22
22
  }
23
23
  ```
24
24
 
@@ -28,15 +28,30 @@ You may need [migration-files].
28
28
 
29
29
  ### migrate
30
30
  ```ts
31
- function migrate(db: Database, migrations: IMigration[], targetVersion?: number): void
31
+ function migrate(
32
+ db: Database
33
+ , migrations: IMigration[]
34
+ , options?: {
35
+ targetVersion?: number
36
+ throwOnNewerVersion?: boolean = false
37
+ }
38
+ ): Promise<void>
32
39
  ```
33
40
 
34
- If targetVersion is `undefined`, then use the maximum version of migrations.
41
+ If `options.targetVersion` is `undefined`,
42
+ the maximum version of the `migrations` is used.
35
43
 
36
- ## FAQ
37
- ### Can multiple instances migrate in parallel?
44
+ When the maximum known migration version is less than the `user_version`,
45
+ it means the current instance is outdated.
46
+ - When `options.throwOnNewerVersion` is `false` (default),
47
+ it will skip the migration,
48
+ so your outdated instance continues to run.
49
+ - When `options.throwOnNewerVersion` is `true`,
50
+ it will throw an error,
51
+ so your outdated instance fails immediately.
52
+
53
+ #### Can multiple instances migrate in parallel?
38
54
  Yes, the `user_version` update is visible to every database connection.
39
- When the maximum migration version is less than the `user_version` (which means it is an obsolete instance), it will skip the migration.
40
55
 
41
- You may need a proper retry strategy,
42
- because each migration uses `BEGIN IMMEDIATE` to ensure that parallel write transactions fail early.
56
+ Each migration uses `BEGIN IMMEDIATE` to ensure that parallel write transactions fail early.
57
+ Therefore, you may need a proper retry strategy.
package/lib/migrate.d.ts CHANGED
@@ -4,4 +4,9 @@ export interface IMigration {
4
4
  up: string | ((db: Database) => PromiseLike<void>);
5
5
  down: string | ((db: Database) => PromiseLike<void>);
6
6
  }
7
- export declare function migrate(db: Database, migrations: IMigration[], targetVersion?: number): Promise<void>;
7
+ interface IMigrateOptions {
8
+ targetVersion?: number;
9
+ throwOnNewerVersion?: boolean;
10
+ }
11
+ export declare function migrate(db: Database, migrations: IMigration[], { targetVersion, throwOnNewerVersion }?: IMigrateOptions): Promise<void>;
12
+ export {};
package/lib/migrate.js CHANGED
@@ -1,27 +1,33 @@
1
1
  import { assert, isFunction } from '@blackglory/prelude';
2
2
  import { promisify } from 'extra-promise';
3
- export async function migrate(db, migrations, targetVersion = getMaximumVersion(migrations)) {
3
+ import { max } from 'extra-utils';
4
+ export async function migrate(db, migrations, { targetVersion = getMaximumVersion(migrations), throwOnNewerVersion = false } = {}) {
4
5
  const run = promisify(db.run.bind(db));
5
6
  const exec = promisify(db.exec.bind(db));
6
7
  const get = promisify(db.get.bind(db));
7
8
  const maxVersion = getMaximumVersion(migrations);
8
- await run('BEGIN IMMEDIATE');
9
- try {
10
- while (true) {
9
+ while (true) {
10
+ await run('BEGIN IMMEDIATE');
11
+ try {
11
12
  const done = await migrate(targetVersion, maxVersion);
13
+ await run('COMMIT');
12
14
  if (done)
13
15
  break;
14
16
  }
15
- await run('COMMIT');
16
- }
17
- catch (e) {
18
- await run('ROLLBACK');
19
- throw e;
17
+ catch (e) {
18
+ await run('ROLLBACK');
19
+ throw e;
20
+ }
20
21
  }
21
22
  async function migrate(targetVersion, maxVersion) {
22
23
  const currentVersion = await getDatabaseVersion();
23
24
  if (maxVersion < currentVersion) {
24
- return true;
25
+ if (throwOnNewerVersion) {
26
+ throw new Error(`Database version ${currentVersion} is higher than the maximum known migration version.`);
27
+ }
28
+ else {
29
+ return true;
30
+ }
25
31
  }
26
32
  else {
27
33
  if (currentVersion === targetVersion) {
@@ -41,7 +47,7 @@ export async function migrate(db, migrations, targetVersion = getMaximumVersion(
41
47
  const currentVersion = await getDatabaseVersion();
42
48
  const targetVersion = currentVersion + 1;
43
49
  const migration = migrations.find(x => x.version === targetVersion);
44
- assert(migration, `Cannot find migration for version ${targetVersion}`);
50
+ assert(migration, `Cannot find a migration for version ${targetVersion}.`);
45
51
  try {
46
52
  if (isFunction(migration.up)) {
47
53
  await migration.up(db);
@@ -51,8 +57,7 @@ export async function migrate(db, migrations, targetVersion = getMaximumVersion(
51
57
  }
52
58
  }
53
59
  catch (e) {
54
- console.error(`Upgrade from version ${currentVersion} to version ${targetVersion} failed.`);
55
- throw e;
60
+ throw new Error(`Upgrade from version ${currentVersion} to version ${targetVersion} failed.`, { cause: e });
56
61
  }
57
62
  await setDatabaseVersion(targetVersion);
58
63
  }
@@ -60,7 +65,7 @@ export async function migrate(db, migrations, targetVersion = getMaximumVersion(
60
65
  const currentVersion = await getDatabaseVersion();
61
66
  const targetVersion = currentVersion - 1;
62
67
  const migration = migrations.find(x => x.version === currentVersion);
63
- assert(migration, `Cannot find migration for version ${targetVersion}`);
68
+ assert(migration, `Cannot find a migration for version ${targetVersion}`);
64
69
  try {
65
70
  if (isFunction(migration.down)) {
66
71
  await migration.down(db);
@@ -70,20 +75,21 @@ export async function migrate(db, migrations, targetVersion = getMaximumVersion(
70
75
  }
71
76
  }
72
77
  catch (e) {
73
- console.error(`Downgrade from version ${currentVersion} to version ${targetVersion} failed.`);
74
- throw e;
78
+ throw new Error(`Downgrade from version ${currentVersion} to version ${targetVersion} failed.`, { cause: e });
75
79
  }
76
80
  await setDatabaseVersion(targetVersion);
77
81
  }
78
82
  async function getDatabaseVersion() {
79
- const row = await get('PRAGMA user_version;');
80
- return row.user_version;
83
+ const row = await get('PRAGMA user_version');
84
+ return row['user_version'];
81
85
  }
82
86
  async function setDatabaseVersion(version) {
83
87
  await run(`PRAGMA user_version = ${version}`);
84
88
  }
85
89
  }
86
90
  function getMaximumVersion(migrations) {
87
- return migrations.reduce((max, cur) => Math.max(cur.version, max), 0);
91
+ return migrations
92
+ .map(x => x.version)
93
+ .reduce(max);
88
94
  }
89
95
  //# sourceMappingURL=migrate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAQzC,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,EAAY,EACZ,UAAwB,EACxB,gBAAwB,iBAAiB,CAAC,UAAU,CAAC;IAErD,MAAM,GAAG,GAAG,SAAS,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;IACtC,MAAM,IAAI,GAAG,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;IACxC,MAAM,GAAG,GAAG,SAAS,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;IAEtC,MAAM,UAAU,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAA;IAEhD,MAAM,GAAG,CAAC,iBAAiB,CAAC,CAAA;IAC5B,IAAI,CAAC;QACH,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,aAAa,EAAE,UAAU,CAAC,CAAA;YACrD,IAAI,IAAI;gBAAE,MAAK;QACjB,CAAC;QAED,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAA;IACrB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,GAAG,CAAC,UAAU,CAAC,CAAA;QACrB,MAAM,CAAC,CAAA;IACT,CAAC;IAED,KAAK,UAAU,OAAO,CACpB,aAAqB,EACrB,UAAkB;QAElB,MAAM,cAAc,GAAG,MAAM,kBAAkB,EAAE,CAAA;QACjD,IAAI,UAAU,GAAG,cAAc,EAAE,CAAC;YAChC,OAAO,IAAI,CAAA;QACb,CAAC;aAAM,CAAC;YACN,IAAI,cAAc,KAAK,aAAa,EAAE,CAAC;gBACrC,OAAO,IAAI,CAAA;YACb,CAAC;iBAAM,IAAI,cAAc,GAAG,aAAa,EAAE,CAAC;gBAC1C,MAAM,OAAO,EAAE,CAAA;gBACf,OAAO,KAAK,CAAA;YACd,CAAC;iBAAM,CAAC;gBACN,MAAM,SAAS,EAAE,CAAA;gBACjB,OAAO,KAAK,CAAA;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,UAAU,OAAO;QACpB,MAAM,cAAc,GAAG,MAAM,kBAAkB,EAAE,CAAA;QACjD,MAAM,aAAa,GAAG,cAAc,GAAG,CAAC,CAAA;QAExC,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,aAAa,CAAC,CAAA;QACnE,MAAM,CAAC,SAAS,EAAE,qCAAqC,aAAa,EAAE,CAAC,CAAA;QAEvE,IAAI,CAAC;YACH,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC;gBAC7B,MAAM,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;YACxB,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAA;YAC1B,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,wBAAwB,cAAc,eAAe,aAAa,UAAU,CAAC,CAAA;YAC3F,MAAM,CAAC,CAAA;QACT,CAAC;QACD,MAAM,kBAAkB,CAAC,aAAa,CAAC,CAAA;IACzC,CAAC;IAED,KAAK,UAAU,SAAS;QACtB,MAAM,cAAc,GAAG,MAAM,kBAAkB,EAAE,CAAA;QACjD,MAAM,aAAa,GAAG,cAAc,GAAG,CAAC,CAAA;QAExC,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,cAAc,CAAC,CAAA;QACpE,MAAM,CAAC,SAAS,EAAE,qCAAqC,aAAa,EAAE,CAAC,CAAA;QAEvE,IAAI,CAAC;YACH,IAAI,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC/B,MAAM,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;YAC1B,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;YAC5B,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,0BAA0B,cAAc,eAAe,aAAa,UAAU,CAAC,CAAA;YAC7F,MAAM,CAAC,CAAA;QACT,CAAC;QACD,MAAM,kBAAkB,CAAC,aAAa,CAAC,CAAA;IACzC,CAAC;IAED,KAAK,UAAU,kBAAkB;QAC/B,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,sBAAsB,CAE3C,CAAA;QAED,OAAO,GAAG,CAAC,YAAY,CAAA;IACzB,CAAC;IAED,KAAK,UAAU,kBAAkB,CAAC,OAAe;QAE/C,MAAM,GAAG,CAAC,yBAA0B,OAAQ,EAAE,CAAC,CAAA;IACjD,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,UAAwB;IACjD,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;AACvE,CAAC"}
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blackglory/sqlite3-migrations",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A utility for database migrations with sqlite3",
5
5
  "keywords": [
6
6
  "migration",
@@ -22,12 +22,12 @@
22
22
  "license": "MIT",
23
23
  "sideEffects": false,
24
24
  "engines": {
25
- "node": ">=18.17.0"
25
+ "node": ">=22"
26
26
  },
27
27
  "scripts": {
28
28
  "prepare": "ts-patch install -s",
29
29
  "lint": "eslint --ext .js,.jsx,.ts,.tsx --quiet src __tests__",
30
- "test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --runInBand --config jest.config.cjs",
30
+ "test": "vitest run",
31
31
  "prepublishOnly": "run-s prepare clean build",
32
32
  "clean": "rimraf lib",
33
33
  "build": "tsc --project tsconfig.build.json",
@@ -40,30 +40,32 @@
40
40
  }
41
41
  },
42
42
  "devDependencies": {
43
- "@blackglory/jest-resolver": "^0.3.1",
44
- "@commitlint/cli": "^18.4.3",
45
- "@commitlint/config-conventional": "^18.4.3",
46
- "@types/jest": "^29.5.11",
47
- "@types/node": "18",
48
- "@typescript-eslint/eslint-plugin": "^6.14.0",
49
- "@typescript-eslint/parser": "^6.14.0",
50
- "cross-env": "^7.0.3",
51
- "eslint": "^8.55.0",
43
+ "@commitlint/cli": "^20.4.1",
44
+ "@commitlint/config-conventional": "^20.4.1",
45
+ "@eslint/js": "^10.0.1",
46
+ "@types/node": "22",
47
+ "@typescript-eslint/eslint-plugin": "^8.55.0",
48
+ "@typescript-eslint/parser": "^8.55.0",
49
+ "cross-env": "^10.1.0",
50
+ "eslint": "^10.0.0",
52
51
  "husky": "4",
53
- "jest": "^29.7.0",
54
- "jest-resolve": "^29.7.0",
55
52
  "npm-run-all": "^4.1.5",
56
- "rimraf": "^5.0.5",
57
- "sqlite3": "^5.1.6",
53
+ "return-style": "^3.0.1",
54
+ "rimraf": "^6.1.2",
55
+ "sqlite3": "^5.1.7",
58
56
  "standard-version": "^9.5.0",
59
- "ts-jest": "^29.1.1",
60
- "ts-patch": "^3.1.1",
61
- "typescript": "^5.3.3",
62
- "typescript-transform-paths": "^3.4.6"
57
+ "ts-patch": "^3.3.0",
58
+ "typescript": "^5.9.3",
59
+ "typescript-eslint": "^8.55.0",
60
+ "typescript-transform-paths": "^3.5.6",
61
+ "vite": "^7.3.1",
62
+ "vite-tsconfig-paths": "^6.1.0",
63
+ "vitest": "^4.0.18"
63
64
  },
64
65
  "dependencies": {
65
- "@blackglory/prelude": "^0.3.4",
66
- "extra-promise": "^6.0.8"
66
+ "@blackglory/prelude": "^0.4.0",
67
+ "extra-promise": "^7.0.1",
68
+ "extra-utils": "^5.20.0"
67
69
  },
68
70
  "peerDependencies": {
69
71
  "sqlite3": "^5.1.6"
package/src/migrate.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Database } from 'sqlite3'
2
2
  import { assert, isFunction } from '@blackglory/prelude'
3
3
  import { promisify } from 'extra-promise'
4
+ import { max } from 'extra-utils'
4
5
 
5
6
  export interface IMigration {
6
7
  version: number
@@ -8,10 +9,18 @@ export interface IMigration {
8
9
  down: string | ((db: Database) => PromiseLike<void>)
9
10
  }
10
11
 
12
+ interface IMigrateOptions {
13
+ targetVersion?: number
14
+ throwOnNewerVersion?: boolean
15
+ }
16
+
11
17
  export async function migrate(
12
18
  db: Database
13
19
  , migrations: IMigration[]
14
- , targetVersion: number = getMaximumVersion(migrations)
20
+ , {
21
+ targetVersion = getMaximumVersion(migrations)
22
+ , throwOnNewerVersion = false
23
+ }: IMigrateOptions = {}
15
24
  ): Promise<void> {
16
25
  const run = promisify(db.run.bind(db))
17
26
  const exec = promisify(db.exec.bind(db))
@@ -19,17 +28,19 @@ export async function migrate(
19
28
 
20
29
  const maxVersion = getMaximumVersion(migrations)
21
30
 
22
- await run('BEGIN IMMEDIATE')
23
- try {
24
- while (true) {
31
+ while (true) {
32
+ await run('BEGIN IMMEDIATE')
33
+ try {
25
34
  const done = await migrate(targetVersion, maxVersion)
35
+
36
+ await run('COMMIT')
37
+
26
38
  if (done) break
27
- }
39
+ } catch (e) {
40
+ await run('ROLLBACK')
28
41
 
29
- await run('COMMIT')
30
- } catch (e) {
31
- await run('ROLLBACK')
32
- throw e
42
+ throw e
43
+ }
33
44
  }
34
45
 
35
46
  async function migrate(
@@ -38,7 +49,11 @@ export async function migrate(
38
49
  ): Promise<boolean> {
39
50
  const currentVersion = await getDatabaseVersion()
40
51
  if (maxVersion < currentVersion) {
41
- return true
52
+ if (throwOnNewerVersion) {
53
+ throw new Error(`Database version ${currentVersion} is higher than the maximum known migration version.`)
54
+ } else {
55
+ return true
56
+ }
42
57
  } else {
43
58
  if (currentVersion === targetVersion) {
44
59
  return true
@@ -57,7 +72,7 @@ export async function migrate(
57
72
  const targetVersion = currentVersion + 1
58
73
 
59
74
  const migration = migrations.find(x => x.version === targetVersion)
60
- assert(migration, `Cannot find migration for version ${targetVersion}`)
75
+ assert(migration, `Cannot find a migration for version ${targetVersion}.`)
61
76
 
62
77
  try {
63
78
  if (isFunction(migration.up)) {
@@ -66,8 +81,10 @@ export async function migrate(
66
81
  await exec(migration.up)
67
82
  }
68
83
  } catch (e) {
69
- console.error(`Upgrade from version ${currentVersion} to version ${targetVersion} failed.`)
70
- throw e
84
+ throw new Error(
85
+ `Upgrade from version ${currentVersion} to version ${targetVersion} failed.`
86
+ , { cause: e }
87
+ )
71
88
  }
72
89
  await setDatabaseVersion(targetVersion)
73
90
  }
@@ -77,7 +94,7 @@ export async function migrate(
77
94
  const targetVersion = currentVersion - 1
78
95
 
79
96
  const migration = migrations.find(x => x.version === currentVersion)
80
- assert(migration, `Cannot find migration for version ${targetVersion}`)
97
+ assert(migration, `Cannot find a migration for version ${targetVersion}`)
81
98
 
82
99
  try {
83
100
  if (isFunction(migration.down)) {
@@ -86,26 +103,30 @@ export async function migrate(
86
103
  await exec(migration.down)
87
104
  }
88
105
  } catch (e) {
89
- console.error(`Downgrade from version ${currentVersion} to version ${targetVersion} failed.`)
90
- throw e
106
+ throw new Error(
107
+ `Downgrade from version ${currentVersion} to version ${targetVersion} failed.`
108
+ , { cause: e }
109
+ )
91
110
  }
92
111
  await setDatabaseVersion(targetVersion)
93
112
  }
94
113
 
95
114
  async function getDatabaseVersion(): Promise<number> {
96
- const row = await get('PRAGMA user_version;') as {
115
+ const row = await get('PRAGMA user_version') as {
97
116
  user_version: number
98
117
  }
99
118
 
100
- return row.user_version
119
+ return row['user_version']
101
120
  }
102
121
 
103
122
  async function setDatabaseVersion(version: number): Promise<void> {
104
123
  // PRAGMA不支持变量
105
- await run(`PRAGMA user_version = ${ version }`)
124
+ await run(`PRAGMA user_version = ${version}`)
106
125
  }
107
126
  }
108
127
 
109
128
  function getMaximumVersion(migrations: IMigration[]): number {
110
- return migrations.reduce((max, cur) => Math.max(cur.version, max), 0)
129
+ return migrations
130
+ .map(x => x.version)
131
+ .reduce(max)
111
132
  }