@devmoods/express-extras 0.17.1 → 0.17.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/dist/context.d.ts +1 -1
- package/dist/errors.js +9 -1
- package/dist/migrate.d.ts +7 -2
- package/dist/migrate.js +34 -13
- package/dist/postgres.d.ts +3 -0
- package/dist/postgres.js +16 -2
- package/package.json +14 -14
package/dist/context.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export declare const getContext: typeof useContext;
|
|
|
17
17
|
/**
|
|
18
18
|
* Set a value on the request context
|
|
19
19
|
*/
|
|
20
|
-
export declare function setContext<T>(key: string, value: T):
|
|
20
|
+
export declare function setContext<T>(key: string, value: T): any;
|
|
21
21
|
export declare function useRequestId(): string | undefined;
|
|
22
22
|
/**
|
|
23
23
|
* @deprecated
|
package/dist/errors.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as Sentry from '@sentry/node';
|
|
2
2
|
import { errors } from 'compose-middleware';
|
|
3
3
|
import { STATUS_CODES } from 'node:http';
|
|
4
|
+
import { IntegrityError } from './postgres.js';
|
|
4
5
|
export class ApiError extends Error {
|
|
5
6
|
}
|
|
6
7
|
export const BadRequest = (reason) => makeError(400, 'Invalid Params', { reason });
|
|
@@ -36,6 +37,12 @@ export function mapToProblem(error) {
|
|
|
36
37
|
status: 500,
|
|
37
38
|
};
|
|
38
39
|
}
|
|
40
|
+
function convertToAPIError(error) {
|
|
41
|
+
if (error instanceof IntegrityError) {
|
|
42
|
+
return Conflict(error.message);
|
|
43
|
+
}
|
|
44
|
+
return error;
|
|
45
|
+
}
|
|
39
46
|
function _errorHandler({ onError }) {
|
|
40
47
|
return (err, req, res, next // eslint-disable-line
|
|
41
48
|
) => {
|
|
@@ -43,7 +50,8 @@ function _errorHandler({ onError }) {
|
|
|
43
50
|
err = makeError(500);
|
|
44
51
|
}
|
|
45
52
|
onError?.(err);
|
|
46
|
-
const
|
|
53
|
+
const apiError = convertToAPIError(err);
|
|
54
|
+
const problem = mapToProblem(apiError);
|
|
47
55
|
res.status(problem.status).json(problem);
|
|
48
56
|
};
|
|
49
57
|
}
|
package/dist/migrate.d.ts
CHANGED
|
@@ -4,6 +4,11 @@ interface Migration {
|
|
|
4
4
|
filename: string;
|
|
5
5
|
version: number;
|
|
6
6
|
}
|
|
7
|
+
interface MigrationFileAPI {
|
|
8
|
+
useTransaction?: boolean;
|
|
9
|
+
up(fn: any): Promise<string> | Promise<void> | string | void;
|
|
10
|
+
down(fn: any): Promise<string> | Promise<void> | string | void;
|
|
11
|
+
}
|
|
7
12
|
export declare function cli(): Promise<void>;
|
|
8
13
|
interface MigratorOptions {
|
|
9
14
|
postgres?: ReturnType<typeof createPostgres>;
|
|
@@ -23,8 +28,8 @@ export declare class Migrator {
|
|
|
23
28
|
runMigrations(direction: 'up' | 'down'): Promise<number>;
|
|
24
29
|
log(...args: any[]): void;
|
|
25
30
|
executeMigration(migrations: Migration[], currentVersion: number, direction: 'up' | 'down'): Promise<number>;
|
|
26
|
-
readMigrationFile(filename: string): Promise<
|
|
27
|
-
migrate(tx: Connection,
|
|
31
|
+
readMigrationFile(filename: string): Promise<MigrationFileAPI>;
|
|
32
|
+
migrate(tx: Connection, migrator: MigrationFileAPI, direction: 'up' | 'down'): Promise<void>;
|
|
28
33
|
updateCurrentVersion(connection: Connection, version: number): Promise<import("pg").QueryResult<import("pg").QueryResultRow>>;
|
|
29
34
|
getCurrentVersion(): Promise<number>;
|
|
30
35
|
getMigrations(): Promise<{
|
package/dist/migrate.js
CHANGED
|
@@ -104,10 +104,22 @@ export class Migrator {
|
|
|
104
104
|
}
|
|
105
105
|
for (const migration of pendingMigrations) {
|
|
106
106
|
this.log('Running migration', migration.filename);
|
|
107
|
-
await this.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
107
|
+
const migrator = await this.readMigrationFile(migration.filename);
|
|
108
|
+
if (!migrator.up || !migrator.down) {
|
|
109
|
+
throw new Error('Migrations must export both an up() and a down() function.');
|
|
110
|
+
}
|
|
111
|
+
if (migrator.useTransaction !== false) {
|
|
112
|
+
await this.postgres.transaction(async (tx) => {
|
|
113
|
+
await this.migrate(tx, migrator, direction);
|
|
114
|
+
await this.updateCurrentVersion(tx, migration.version);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
await this.postgres.withConnection(async (tx) => {
|
|
119
|
+
await this.migrate(tx, migrator, direction);
|
|
120
|
+
await this.updateCurrentVersion(tx, migration.version);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
111
123
|
}
|
|
112
124
|
this.log(chalk.green('Done!'));
|
|
113
125
|
return await this.getCurrentVersion();
|
|
@@ -122,10 +134,22 @@ export class Migrator {
|
|
|
122
134
|
return currentVersion;
|
|
123
135
|
}
|
|
124
136
|
this.log('Rolling back migration', migrationToRollback.filename);
|
|
125
|
-
await this.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
137
|
+
const migrator = await this.readMigrationFile(migrationToRollback.filename);
|
|
138
|
+
if (!migrator.up || !migrator.down) {
|
|
139
|
+
throw new Error('Migrations must export both an up() and a down() function.');
|
|
140
|
+
}
|
|
141
|
+
if (migrator.useTransaction !== false) {
|
|
142
|
+
await this.postgres.transaction(async (tx) => {
|
|
143
|
+
await this.migrate(tx, migrator, 'down');
|
|
144
|
+
await this.updateCurrentVersion(tx, updatedVersion);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
await this.postgres.withConnection(async (tx) => {
|
|
149
|
+
await this.migrate(tx, migrator, 'down');
|
|
150
|
+
await this.updateCurrentVersion(tx, updatedVersion);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
129
153
|
return await this.getCurrentVersion();
|
|
130
154
|
}
|
|
131
155
|
throw new Error(`Invalid direction ${direction}`);
|
|
@@ -137,6 +161,7 @@ export class Migrator {
|
|
|
137
161
|
const content = await fs.readFile(filename, 'utf-8');
|
|
138
162
|
const [up, down] = content.split('-- ROLLBACK --');
|
|
139
163
|
return {
|
|
164
|
+
useTransaction: content.includes('-- NO TRANSACTION --'),
|
|
140
165
|
up() {
|
|
141
166
|
return up.trim();
|
|
142
167
|
},
|
|
@@ -145,11 +170,7 @@ export class Migrator {
|
|
|
145
170
|
},
|
|
146
171
|
};
|
|
147
172
|
}
|
|
148
|
-
async migrate(tx,
|
|
149
|
-
const migrator = await this.readMigrationFile(migration.filename);
|
|
150
|
-
if (!migrator.up || !migrator.down) {
|
|
151
|
-
throw new Error('Migrations must export both an up() and a down() function.');
|
|
152
|
-
}
|
|
173
|
+
async migrate(tx, migrator, direction) {
|
|
153
174
|
try {
|
|
154
175
|
const queryWithLog = (sql) => {
|
|
155
176
|
this.log(chalk.yellow(sql.toString()));
|
package/dist/postgres.d.ts
CHANGED
|
@@ -9,6 +9,9 @@ export declare class DoesNotExistError extends Error {
|
|
|
9
9
|
export declare class MultipleRowsReturnedError extends Error {
|
|
10
10
|
constructor(message: string);
|
|
11
11
|
}
|
|
12
|
+
export declare class IntegrityError extends Error {
|
|
13
|
+
constructor(message: string);
|
|
14
|
+
}
|
|
12
15
|
type Value = null | string | number | Date;
|
|
13
16
|
export declare class Connection {
|
|
14
17
|
client: PoolClient;
|
package/dist/postgres.js
CHANGED
|
@@ -12,12 +12,26 @@ export class MultipleRowsReturnedError extends Error {
|
|
|
12
12
|
this.name = 'MultipleRowsReturnedError';
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
+
export class IntegrityError extends Error {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'IntegrityError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
15
21
|
export class Connection {
|
|
16
22
|
constructor(client) {
|
|
17
23
|
this.client = client;
|
|
18
24
|
}
|
|
19
25
|
async query(text, values) {
|
|
20
|
-
|
|
26
|
+
try {
|
|
27
|
+
return await this.client.query(text, values);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error.code && error.code === '23505') {
|
|
31
|
+
throw new IntegrityError(`${error.message}: ${error.detail}`);
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
21
35
|
}
|
|
22
36
|
async all(text, values) {
|
|
23
37
|
const result = await this.client.query(text, values);
|
|
@@ -27,7 +41,7 @@ export class Connection {
|
|
|
27
41
|
return [];
|
|
28
42
|
}
|
|
29
43
|
async insert(text, values) {
|
|
30
|
-
return await this.
|
|
44
|
+
return await this.query(text, values);
|
|
31
45
|
}
|
|
32
46
|
async first(text, values) {
|
|
33
47
|
const result = await this.client.query(text, values);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devmoods/express-extras",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.4",
|
|
4
4
|
"description": "Kubernetes-ready utilities for rapid Node.js application development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"license": "ISC",
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@devmoods/eslint-config": "^1.1.0",
|
|
38
|
-
"@types/node": "20.
|
|
38
|
+
"@types/node": "20.3.1",
|
|
39
39
|
"@types/supertest": "2.0.12",
|
|
40
|
-
"@typescript-eslint/eslint-plugin": "^5.59.
|
|
41
|
-
"@typescript-eslint/parser": "^5.59.
|
|
40
|
+
"@typescript-eslint/eslint-plugin": "^5.59.11",
|
|
41
|
+
"@typescript-eslint/parser": "^5.59.11",
|
|
42
42
|
"chalk": "^5.2.0",
|
|
43
|
-
"eslint": "^8.
|
|
43
|
+
"eslint": "^8.43.0",
|
|
44
44
|
"eslint-plugin-jsx-a11y": "^6.7.1",
|
|
45
45
|
"eslint-plugin-prettier": "^4.2.1",
|
|
46
46
|
"eslint-plugin-react": "^7.32.2",
|
|
@@ -51,37 +51,37 @@
|
|
|
51
51
|
"prettier": "2.8.8",
|
|
52
52
|
"supertest": "6.3.3",
|
|
53
53
|
"ts-node": "10.9.1",
|
|
54
|
-
"typescript": "5.
|
|
55
|
-
"vitest": "^0.
|
|
54
|
+
"typescript": "5.1.3",
|
|
55
|
+
"vitest": "^0.32.2"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@devmoods/fetch": "^3.3.0",
|
|
59
59
|
"@godaddy/terminus": "^4.12.0",
|
|
60
|
-
"@sentry/node": "^7.
|
|
61
|
-
"@sentry/tracing": "^7.
|
|
60
|
+
"@sentry/node": "^7.55.2",
|
|
61
|
+
"@sentry/tracing": "^7.55.2",
|
|
62
62
|
"@types/bcrypt": "^5.0.0",
|
|
63
63
|
"@types/bunyan": "^1.8.8",
|
|
64
|
-
"@types/cls-hooked": "^4.3.
|
|
64
|
+
"@types/cls-hooked": "^4.3.4",
|
|
65
65
|
"@types/cookie-parser": "^1.4.3",
|
|
66
66
|
"@types/express": "^4.17.17",
|
|
67
67
|
"@types/jsonwebtoken": "^9.0.2",
|
|
68
68
|
"@types/nodemailer": "^6.4.8",
|
|
69
|
-
"@types/pg": "^8.10.
|
|
70
|
-
"@types/uuid": "^9.0.
|
|
69
|
+
"@types/pg": "^8.10.2",
|
|
70
|
+
"@types/uuid": "^9.0.2",
|
|
71
71
|
"ajv": "^8.12.0",
|
|
72
72
|
"bcrypt": "^5.1.0",
|
|
73
73
|
"bunyan": "^1.8.15",
|
|
74
74
|
"cls-hooked": "^4.2.2",
|
|
75
75
|
"compose-middleware": "^5.0.1",
|
|
76
76
|
"cookie-parser": "^1.4.6",
|
|
77
|
-
"dotenv": "^16.
|
|
77
|
+
"dotenv": "^16.3.1",
|
|
78
78
|
"express": "^4.18.2",
|
|
79
79
|
"helmet": "^7.0.0",
|
|
80
80
|
"jsonwebtoken": "^9.0.0",
|
|
81
81
|
"meow": "^12.0.1",
|
|
82
82
|
"nodemailer": "^6.9.3",
|
|
83
83
|
"pg": "^8.11.0",
|
|
84
|
-
"redis": "^4.6.
|
|
84
|
+
"redis": "^4.6.7",
|
|
85
85
|
"sql-template-strings": "^2.2.2",
|
|
86
86
|
"uuid": "^9.0.0"
|
|
87
87
|
},
|