@devmoods/express-extras 0.64.0 → 0.65.1
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 +0 -111
- package/dist/auth/emailAndPassword.js +2 -1
- package/dist/auth/google.d.ts +4 -4
- package/dist/auth/google.js +14 -11
- package/dist/auth/passwordless.js +2 -1
- package/dist/auth/token.js +6 -6
- package/dist/auth/types.d.ts +1 -1
- package/dist/auth.d.ts +1 -1
- package/dist/auth.js +17 -10
- package/dist/cache.d.ts +3 -0
- package/dist/cache.js +3 -2
- package/dist/channels.d.ts +6 -5
- package/dist/channels.js +9 -4
- package/dist/cli.js +1 -1
- package/dist/config.d.ts +72 -66
- package/dist/config.js +38 -36
- package/dist/context.d.ts +9 -2
- package/dist/context.js +18 -9
- package/dist/csrf.js +6 -6
- package/dist/email.d.ts +1 -1
- package/dist/email.js +3 -3
- package/dist/errors.js +1 -1
- package/dist/faktory.d.ts +1 -1
- package/dist/faktory.js +5 -15
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -3
- package/dist/listen.d.ts +4 -4
- package/dist/listen.js +41 -43
- package/dist/logger.d.ts +1 -0
- package/dist/logger.js +11 -5
- package/dist/manage.d.ts +4 -6
- package/dist/manage.js +41 -37
- package/dist/middleware.d.ts +3 -0
- package/dist/middleware.js +24 -7
- package/dist/rate-limit.d.ts +1 -0
- package/dist/rate-limit.js +25 -3
- package/dist/redis.d.ts +1 -1
- package/dist/redis.js +4 -4
- package/dist/sentry.js +11 -5
- package/dist/setupTests.global.d.ts +2 -0
- package/dist/setupTests.global.js +4 -0
- package/dist/setupTests.js +3 -0
- package/dist/slack.js +2 -2
- package/dist/utils.d.ts +0 -13
- package/dist/utils.js +0 -21
- package/dist/validate.js +7 -4
- package/dist/vite/__tests__/fixtures/src/entry-server.d.ts +5 -0
- package/dist/vite/__tests__/fixtures/src/entry-server.js +15 -0
- package/dist/vite/defineViteConfig.js +11 -1
- package/dist/vite/index.d.ts +1 -0
- package/dist/vite/index.js +3 -0
- package/dist/vite/ssr.d.ts +1 -0
- package/dist/vite/ssr.js +9 -4
- package/dist/vitest/index.d.ts +5 -0
- package/dist/vitest/index.js +24 -0
- package/dist/vitest/matchers.d.ts +1 -1
- package/dist/vitest/matchers.js +6 -2
- package/dist/vitest/testClient.d.ts +30 -0
- package/dist/vitest/testClient.js +98 -0
- package/dist/webapi.js +4 -0
- package/package.json +31 -42
- package/dist/__tests__/__migrations__/1_a.d.ts +0 -3
- package/dist/__tests__/__migrations__/1_a.js +0 -9
- package/dist/migrate.d.ts +0 -47
- package/dist/migrate.js +0 -255
- package/dist/postgres-ts-generator.d.ts +0 -11
- package/dist/postgres-ts-generator.js +0 -114
- package/dist/postgres.d.ts +0 -71
- package/dist/postgres.js +0 -261
- package/dist/sql.d.ts +0 -33
- package/dist/sql.js +0 -135
- package/dist/vite.d.ts +0 -1
- package/dist/vite.js +0 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type Server } from 'node:http';
|
|
2
|
+
import { type FetchClientOptions } from '@devmoods/fetch';
|
|
3
|
+
import { type Application } from 'express';
|
|
4
|
+
import * as tough from 'tough-cookie';
|
|
5
|
+
export declare function createTestClient(server: Server | Application, options?: FetchClientOptions): Promise<{
|
|
6
|
+
duplicate(): Promise</*elided*/ any>;
|
|
7
|
+
cookieJar: tough.CookieJar;
|
|
8
|
+
fetch: {
|
|
9
|
+
<TResponseData, TRequestData = any, TTransformedResponseData = TResponseData, TTransformedRequestData = TRequestData, TSchema extends import("@standard-schema/spec", { with: { "resolution-mode": "require" } }).StandardSchemaV1<TTransformedResponseData, any> = import("@standard-schema/spec", { with: { "resolution-mode": "require" } }).StandardSchemaV1<TTransformedResponseData, TTransformedResponseData>>(url: string, options?: import("@devmoods/fetch").RequestOptions<TRequestData, TTransformedRequestData, TResponseData, TTransformedResponseData, TSchema>): Promise<import("@devmoods/fetch").HttpResponse<import("@standard-schema/spec", { with: { "resolution-mode": "require" } }).StandardSchemaV1.InferOutput<TSchema>>>;
|
|
10
|
+
intercept(interceptor: import("@devmoods/fetch").FetchInterceptor): () => void;
|
|
11
|
+
};
|
|
12
|
+
post: {
|
|
13
|
+
<TResponseData, TRequestData = any, TTransformedResponseData = TResponseData, TTransformedRequestData = TRequestData, TSchema extends import("@standard-schema/spec", { with: { "resolution-mode": "require" } }).StandardSchemaV1<TTransformedResponseData, any> = import("@standard-schema/spec", { with: { "resolution-mode": "require" } }).StandardSchemaV1<TTransformedResponseData, TTransformedResponseData>>(url: string, options?: import("@devmoods/fetch").RequestOptions<TRequestData, TTransformedRequestData, TResponseData, TTransformedResponseData, TSchema>): Promise<import("@devmoods/fetch").HttpResponse<import("@standard-schema/spec", { with: { "resolution-mode": "require" } }).StandardSchemaV1.InferOutput<TSchema>>>;
|
|
14
|
+
intercept(interceptor: import("@devmoods/fetch").FetchInterceptor): () => void;
|
|
15
|
+
};
|
|
16
|
+
get: {
|
|
17
|
+
<TResponseData, TRequestData = any, TTransformedResponseData = TResponseData, TTransformedRequestData = TRequestData, TSchema extends import("@standard-schema/spec", { with: { "resolution-mode": "require" } }).StandardSchemaV1<TTransformedResponseData, any> = import("@standard-schema/spec", { with: { "resolution-mode": "require" } }).StandardSchemaV1<TTransformedResponseData, TTransformedResponseData>>(url: string, options?: import("@devmoods/fetch").RequestOptions<TRequestData, TTransformedRequestData, TResponseData, TTransformedResponseData, TSchema>): Promise<import("@devmoods/fetch").HttpResponse<import("@standard-schema/spec", { with: { "resolution-mode": "require" } }).StandardSchemaV1.InferOutput<TSchema>>>;
|
|
18
|
+
intercept(interceptor: import("@devmoods/fetch").FetchInterceptor): () => void;
|
|
19
|
+
};
|
|
20
|
+
put: {
|
|
21
|
+
<TResponseData, TRequestData = any, TTransformedResponseData = TResponseData, TTransformedRequestData = TRequestData, TSchema extends import("@standard-schema/spec", { with: { "resolution-mode": "require" } }).StandardSchemaV1<TTransformedResponseData, any> = import("@standard-schema/spec", { with: { "resolution-mode": "require" } }).StandardSchemaV1<TTransformedResponseData, TTransformedResponseData>>(url: string, options?: import("@devmoods/fetch").RequestOptions<TRequestData, TTransformedRequestData, TResponseData, TTransformedResponseData, TSchema>): Promise<import("@devmoods/fetch").HttpResponse<import("@standard-schema/spec", { with: { "resolution-mode": "require" } }).StandardSchemaV1.InferOutput<TSchema>>>;
|
|
22
|
+
intercept(interceptor: import("@devmoods/fetch").FetchInterceptor): () => void;
|
|
23
|
+
};
|
|
24
|
+
delete: {
|
|
25
|
+
<TResponseData, TRequestData = any, TTransformedResponseData = TResponseData, TTransformedRequestData = TRequestData, TSchema extends import("@standard-schema/spec", { with: { "resolution-mode": "require" } }).StandardSchemaV1<TTransformedResponseData, any> = import("@standard-schema/spec", { with: { "resolution-mode": "require" } }).StandardSchemaV1<TTransformedResponseData, TTransformedResponseData>>(url: string, options?: import("@devmoods/fetch").RequestOptions<TRequestData, TTransformedRequestData, TResponseData, TTransformedResponseData, TSchema>): Promise<import("@devmoods/fetch").HttpResponse<import("@standard-schema/spec", { with: { "resolution-mode": "require" } }).StandardSchemaV1.InferOutput<TSchema>>>;
|
|
26
|
+
intercept(interceptor: import("@devmoods/fetch").FetchInterceptor): () => void;
|
|
27
|
+
};
|
|
28
|
+
close: () => Promise<void>;
|
|
29
|
+
[Symbol.asyncDispose]: () => Promise<void>;
|
|
30
|
+
}>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import {} from 'node:net';
|
|
3
|
+
import { createFetch, HttpError, } from '@devmoods/fetch';
|
|
4
|
+
import {} from 'express';
|
|
5
|
+
import * as tough from 'tough-cookie';
|
|
6
|
+
export async function createTestClient(server, options) {
|
|
7
|
+
// Express app
|
|
8
|
+
if ('route' in server) {
|
|
9
|
+
server = createServer(server);
|
|
10
|
+
}
|
|
11
|
+
let address = server.address();
|
|
12
|
+
/* c8 ignore next 5 */
|
|
13
|
+
if (typeof address === 'string') {
|
|
14
|
+
throw new Error(`Can't use a server listening on a UNIX domain socket: ${address}`);
|
|
15
|
+
}
|
|
16
|
+
let started = false;
|
|
17
|
+
if (address == null) {
|
|
18
|
+
await new Promise((resolve) => {
|
|
19
|
+
server.once('listening', resolve);
|
|
20
|
+
server.listen(0);
|
|
21
|
+
});
|
|
22
|
+
started = true;
|
|
23
|
+
address = server.address();
|
|
24
|
+
}
|
|
25
|
+
const cookieJar = new tough.CookieJar();
|
|
26
|
+
const fetch = createFetch({
|
|
27
|
+
getRootUrl: () => `http://localhost:${address.port}`,
|
|
28
|
+
redirect: 'manual',
|
|
29
|
+
...options,
|
|
30
|
+
});
|
|
31
|
+
fetch.intercept({
|
|
32
|
+
request: async (req) => {
|
|
33
|
+
req.headers.set('cookie', await cookieJar.getCookieString(req.url));
|
|
34
|
+
},
|
|
35
|
+
response: async (response) => {
|
|
36
|
+
const cookies = response.headers.getSetCookie();
|
|
37
|
+
await Promise.all(cookies.map(async (cookie) => await cookieJar.setCookie(cookie, response.url, {
|
|
38
|
+
ignoreError: false,
|
|
39
|
+
})));
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
const post = async (url, opt) => {
|
|
43
|
+
return fetch(url, { method: 'POST', ...opt }).catch((e) => {
|
|
44
|
+
if (e instanceof HttpError) {
|
|
45
|
+
return e.response;
|
|
46
|
+
}
|
|
47
|
+
throw e;
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
post.intercept = fetch.intercept;
|
|
51
|
+
const get = async (url, opt) => {
|
|
52
|
+
return fetch(url, { method: 'GET', ...opt }).catch((e) => {
|
|
53
|
+
if (e instanceof HttpError) {
|
|
54
|
+
return e.response;
|
|
55
|
+
}
|
|
56
|
+
throw e;
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
get.intercept = fetch.intercept;
|
|
60
|
+
const put = async (url, opt) => {
|
|
61
|
+
return fetch(url, { method: 'PUT', ...opt }).catch((e) => {
|
|
62
|
+
if (e instanceof HttpError) {
|
|
63
|
+
return e.response;
|
|
64
|
+
}
|
|
65
|
+
throw e;
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
put.intercept = fetch.intercept;
|
|
69
|
+
const delete_ = async (url, opt) => {
|
|
70
|
+
return fetch(url, { method: 'DELETE', ...opt }).catch((e) => {
|
|
71
|
+
if (e instanceof HttpError) {
|
|
72
|
+
return e.response;
|
|
73
|
+
}
|
|
74
|
+
throw e;
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
delete_.intercept = fetch.intercept;
|
|
78
|
+
const close = async () => {
|
|
79
|
+
if (started) {
|
|
80
|
+
server.close();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
duplicate() {
|
|
85
|
+
return createTestClient(server, options);
|
|
86
|
+
},
|
|
87
|
+
cookieJar,
|
|
88
|
+
fetch,
|
|
89
|
+
post,
|
|
90
|
+
get,
|
|
91
|
+
put,
|
|
92
|
+
delete: delete_,
|
|
93
|
+
close,
|
|
94
|
+
[Symbol.asyncDispose]: async () => {
|
|
95
|
+
await close();
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
package/dist/webapi.js
CHANGED
|
@@ -4,6 +4,7 @@ export function createFetchHeaders(requestHeaders) {
|
|
|
4
4
|
const headers = new Headers();
|
|
5
5
|
for (const [key, values] of Object.entries(requestHeaders)) {
|
|
6
6
|
if (values) {
|
|
7
|
+
/* c8 ignore next 4 */
|
|
7
8
|
if (Array.isArray(values)) {
|
|
8
9
|
for (const value of values) {
|
|
9
10
|
headers.append(key, value);
|
|
@@ -20,8 +21,10 @@ export function createFetchRequest(req) {
|
|
|
20
21
|
try {
|
|
21
22
|
const origin = `${req.protocol}://${req.get('host')}`;
|
|
22
23
|
// Note: This had to take originalUrl into account for presumably vite's proxying
|
|
24
|
+
/* c8 ignore next 1 */
|
|
23
25
|
const url = new URL(req.originalUrl || req.url, origin);
|
|
24
26
|
const controller = new AbortController();
|
|
27
|
+
/* c8 ignore next 3 */
|
|
25
28
|
req.on('close', () => {
|
|
26
29
|
controller.abort();
|
|
27
30
|
});
|
|
@@ -34,6 +37,7 @@ export function createFetchRequest(req) {
|
|
|
34
37
|
init.body = req.body;
|
|
35
38
|
}
|
|
36
39
|
return new Request(url.href, init);
|
|
40
|
+
/* c8 ignore next 7 */
|
|
37
41
|
}
|
|
38
42
|
catch (error) {
|
|
39
43
|
if (error instanceof TypeError) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devmoods/express-extras",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.65.1",
|
|
4
4
|
"description": "Kubernetes-ready utilities for rapid Node.js application development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -12,10 +12,16 @@
|
|
|
12
12
|
"default": "./dist/index.js"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
+
"./config": {
|
|
16
|
+
"import": {
|
|
17
|
+
"types": "./dist/config.d.ts",
|
|
18
|
+
"default": "./dist/config.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
15
21
|
"./vite": {
|
|
16
22
|
"import": {
|
|
17
23
|
"types": "./dist/vite/vite.d.ts",
|
|
18
|
-
"default": "./dist/vite.js"
|
|
24
|
+
"default": "./dist/vite/index.js"
|
|
19
25
|
}
|
|
20
26
|
},
|
|
21
27
|
"./vite/ssr": {
|
|
@@ -37,9 +43,13 @@
|
|
|
37
43
|
}
|
|
38
44
|
}
|
|
39
45
|
},
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
46
|
+
"scripts": {
|
|
47
|
+
"clean": "rm -rf dist",
|
|
48
|
+
"lint": "eslint",
|
|
49
|
+
"test": "NODE_ENV=test DOTENV_CONFIG_PATH=.env.test vitest",
|
|
50
|
+
"test:coverage": "pnpm run test --coverage",
|
|
51
|
+
"build": "pnpm run clean && tsc",
|
|
52
|
+
"prepublishOnly": "pnpm run clean && pnpm run build"
|
|
43
53
|
},
|
|
44
54
|
"repository": {
|
|
45
55
|
"type": "git",
|
|
@@ -50,40 +60,31 @@
|
|
|
50
60
|
],
|
|
51
61
|
"license": "ISC",
|
|
52
62
|
"devDependencies": {
|
|
53
|
-
"@devmoods/eslint-config": "^2.12.0",
|
|
54
63
|
"@types/koa-compose": "^3.2.8",
|
|
55
|
-
"@
|
|
56
|
-
"@types/supertest": "^6.0.3",
|
|
57
|
-
"@vitejs/plugin-react": "^5.0.2",
|
|
58
|
-
"@vitest/coverage-v8": "^3.2.4",
|
|
64
|
+
"@vitejs/plugin-react": "^5.0.3",
|
|
59
65
|
"arktype": "^2.1.22",
|
|
60
|
-
"cookiejar": "^2.1.4",
|
|
61
|
-
"eslint": "^9.35.0",
|
|
62
66
|
"faktory-worker": "^4.7.1",
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"tsx": "^4.20.5",
|
|
67
|
-
"typescript": "5.9.2",
|
|
68
|
-
"vite": "^7.1.5",
|
|
69
|
-
"vitest": "^3.2.4",
|
|
70
|
-
"vitest-fail-on-console": "^0.10.1"
|
|
67
|
+
"react": "^19.1.1",
|
|
68
|
+
"react-dom": "^19.1.1",
|
|
69
|
+
"vite": "^7.1.7"
|
|
71
70
|
},
|
|
72
71
|
"dependencies": {
|
|
72
|
+
"@devmoods/config": "0.1.1",
|
|
73
73
|
"@devmoods/fetch": "^4.2.0",
|
|
74
|
-
"@
|
|
75
|
-
"@
|
|
76
|
-
"@
|
|
74
|
+
"@devmoods/observability": "0.1.1",
|
|
75
|
+
"@devmoods/postgres": "0.1.1",
|
|
76
|
+
"@hey-api/openapi-ts": "^0.84.3",
|
|
77
|
+
"@sentry/node": "^10.15.0",
|
|
78
|
+
"@sentry/node-native": "^10.15.0",
|
|
79
|
+
"@sentry/profiling-node": "^10.15.0",
|
|
77
80
|
"@standard-schema/spec": "^1.0.0",
|
|
78
81
|
"@types/bcrypt": "^6.0.0",
|
|
79
82
|
"@types/cookie-parser": "^1.4.9",
|
|
80
83
|
"@types/express": "^5.0.3",
|
|
81
84
|
"@types/jsonwebtoken": "^9.0.10",
|
|
82
85
|
"@types/nodemailer": "^7.0.1",
|
|
83
|
-
"@types/pg": "^8.15.5",
|
|
84
86
|
"@types/qs": "^6.14.0",
|
|
85
87
|
"@types/superagent": "^8.1.9",
|
|
86
|
-
"@types/uuid": "^10.0.0",
|
|
87
88
|
"ajv": "^8.17.1",
|
|
88
89
|
"argon2": "^0.44.0",
|
|
89
90
|
"bcrypt": "^6.0.0",
|
|
@@ -97,15 +98,11 @@
|
|
|
97
98
|
"helmet": "^8.1.0",
|
|
98
99
|
"jsonwebtoken": "^9.0.2",
|
|
99
100
|
"nodemailer": "^7.0.6",
|
|
100
|
-
"pg": "^8.16.3",
|
|
101
|
-
"pg-structure": "^7.15.3",
|
|
102
101
|
"redis": "^5.8.2",
|
|
103
102
|
"sonda": "^0.9.0",
|
|
104
|
-
"
|
|
105
|
-
"uuid": "^13.0.0"
|
|
106
|
-
|
|
107
|
-
"prettier": {
|
|
108
|
-
"singleQuote": true
|
|
103
|
+
"tough-cookie": "^6.0.0",
|
|
104
|
+
"uuid": "^13.0.0",
|
|
105
|
+
"vitest-fail-on-console": "^0.10.1"
|
|
109
106
|
},
|
|
110
107
|
"peerDependencies": {
|
|
111
108
|
"@vitejs/plugin-react": ">=4.3.4",
|
|
@@ -117,13 +114,5 @@
|
|
|
117
114
|
"engines": {
|
|
118
115
|
"node": ">=20 <25"
|
|
119
116
|
},
|
|
120
|
-
"
|
|
121
|
-
|
|
122
|
-
"start:worker": "pushd example; pnpm run start:worker; popd example;",
|
|
123
|
-
"clean": "rm -rf dist",
|
|
124
|
-
"lint": "eslint",
|
|
125
|
-
"test": "./test.sh",
|
|
126
|
-
"build": "pnpm run clean && tsc --project tsconfig.build.json",
|
|
127
|
-
"format": "prettier --write '**/*.{js,css,md,ts,tsx}'"
|
|
128
|
-
}
|
|
129
|
-
}
|
|
117
|
+
"gitHead": "916426bf70f924d62e9dfaccbb22145d41eece28"
|
|
118
|
+
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import {} from '../../postgres.js';
|
|
2
|
-
export async function up(query) {
|
|
3
|
-
await query('CREATE TABLE baz (id serial primary key)');
|
|
4
|
-
await query('CREATE TABLE foo (id serial primary key)');
|
|
5
|
-
}
|
|
6
|
-
export async function down(query) {
|
|
7
|
-
await query('DROP TABLE foo');
|
|
8
|
-
await query('DROP TABLE baz');
|
|
9
|
-
}
|
package/dist/migrate.d.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { type Command } from 'commander';
|
|
2
|
-
import { type CLI } from './cli.js';
|
|
3
|
-
import { createPostgres } from './postgres.js';
|
|
4
|
-
interface Migration {
|
|
5
|
-
name: string;
|
|
6
|
-
filename: string;
|
|
7
|
-
version: number;
|
|
8
|
-
}
|
|
9
|
-
interface MigrationFileAPI {
|
|
10
|
-
useTransaction?: boolean;
|
|
11
|
-
up(fn: any): Promise<void> | Promise<void> | string | undefined;
|
|
12
|
-
down(fn: any): Promise<string> | Promise<void> | string | undefined;
|
|
13
|
-
}
|
|
14
|
-
export declare const asCli: (command: Command, cli: CLI) => void;
|
|
15
|
-
export interface MigratorOptions {
|
|
16
|
-
postgres?: ReturnType<typeof createPostgres>;
|
|
17
|
-
migrationTableName?: string;
|
|
18
|
-
verbose?: boolean;
|
|
19
|
-
pathToMigrations?: string;
|
|
20
|
-
}
|
|
21
|
-
export declare class Migrator {
|
|
22
|
-
private postgres;
|
|
23
|
-
private migrationTableName;
|
|
24
|
-
private pathToMigrations;
|
|
25
|
-
private filenameRegExp;
|
|
26
|
-
private verbose;
|
|
27
|
-
constructor({ postgres, pathToMigrations, migrationTableName, verbose, }?: MigratorOptions);
|
|
28
|
-
end(): Promise<void>;
|
|
29
|
-
init(): Promise<boolean>;
|
|
30
|
-
isInstalled(): Promise<boolean>;
|
|
31
|
-
create(name: string | null | undefined, format?: 'ts' | 'sql'): Promise<string>;
|
|
32
|
-
runMigrations(direction: 'up' | 'down'): Promise<number>;
|
|
33
|
-
log(...args: any[]): void;
|
|
34
|
-
executeMigration(migrations: Migration[], currentVersion: number, direction: 'up' | 'down'): Promise<number>;
|
|
35
|
-
readMigrationFile(filename: string): Promise<MigrationFileAPI>;
|
|
36
|
-
migrate(migrator: MigrationFileAPI, direction: 'up' | 'down'): Promise<void>;
|
|
37
|
-
updateCurrentVersion(version: number): Promise<import("pg").QueryResult<import("pg").QueryResultRow>>;
|
|
38
|
-
getCurrentVersion(): Promise<number>;
|
|
39
|
-
getMigrations(): Promise<{
|
|
40
|
-
filename: string;
|
|
41
|
-
version: number;
|
|
42
|
-
name: string;
|
|
43
|
-
}[]>;
|
|
44
|
-
getPreviousMigrations(migrations: Migration[], currentVersion: number): Migration[];
|
|
45
|
-
getPendingMigrations(migrations: Migration[], currentVersion: number): Migration[];
|
|
46
|
-
}
|
|
47
|
-
export {};
|
package/dist/migrate.js
DELETED
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from 'node:fs';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import {} from 'commander';
|
|
5
|
-
import {} from './cli.js';
|
|
6
|
-
import { createPostgres } from './postgres.js';
|
|
7
|
-
import { formatSql, sql } from './sql.js';
|
|
8
|
-
export const asCli = (command, cli) => {
|
|
9
|
-
command.command('up').action(cli.wrap(async () => {
|
|
10
|
-
const migrator = new Migrator();
|
|
11
|
-
try {
|
|
12
|
-
await migrator.runMigrations('up');
|
|
13
|
-
}
|
|
14
|
-
finally {
|
|
15
|
-
await migrator.end();
|
|
16
|
-
}
|
|
17
|
-
}));
|
|
18
|
-
command.command('down').action(cli.wrap(async () => {
|
|
19
|
-
const migrator = new Migrator();
|
|
20
|
-
try {
|
|
21
|
-
await migrator.runMigrations('down');
|
|
22
|
-
}
|
|
23
|
-
finally {
|
|
24
|
-
await migrator.end();
|
|
25
|
-
}
|
|
26
|
-
}));
|
|
27
|
-
command.command('init').action(cli.wrap(async () => {
|
|
28
|
-
const migrator = new Migrator();
|
|
29
|
-
try {
|
|
30
|
-
const created = await migrator.init();
|
|
31
|
-
if (created) {
|
|
32
|
-
cli.greenLine('Migrations table was successfully setup.');
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
cli.greenLine('Migrations table already existing.');
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
finally {
|
|
39
|
-
await migrator.end();
|
|
40
|
-
}
|
|
41
|
-
}));
|
|
42
|
-
command
|
|
43
|
-
.command('create')
|
|
44
|
-
.argument('<string>', 'name of migration')
|
|
45
|
-
.option('--typescript', 'use typescript')
|
|
46
|
-
.action(cli.wrap(async (name, flags) => {
|
|
47
|
-
const migrator = new Migrator();
|
|
48
|
-
try {
|
|
49
|
-
const createdMigration = await migrator.create(name, flags.typescript ? 'ts' : 'sql');
|
|
50
|
-
migrator.log(chalk.green('Created migration', createdMigration));
|
|
51
|
-
}
|
|
52
|
-
finally {
|
|
53
|
-
await migrator.end();
|
|
54
|
-
}
|
|
55
|
-
}));
|
|
56
|
-
};
|
|
57
|
-
export class Migrator {
|
|
58
|
-
postgres;
|
|
59
|
-
migrationTableName;
|
|
60
|
-
pathToMigrations;
|
|
61
|
-
filenameRegExp = /(\d+)_(.*)\.(js|ts|mjs|mts|sql)/;
|
|
62
|
-
verbose;
|
|
63
|
-
constructor({ postgres = createPostgres({ onLogStatement: () => { } }), pathToMigrations = path.resolve(process.cwd(), 'db', 'migrations'), migrationTableName = 'migrations', verbose = true, } = {}) {
|
|
64
|
-
this.postgres = postgres;
|
|
65
|
-
this.pathToMigrations = pathToMigrations;
|
|
66
|
-
this.migrationTableName = migrationTableName;
|
|
67
|
-
this.verbose = verbose;
|
|
68
|
-
}
|
|
69
|
-
end() {
|
|
70
|
-
return this.postgres.end();
|
|
71
|
-
}
|
|
72
|
-
async init() {
|
|
73
|
-
if (await this.isInstalled()) {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
await fs.mkdir(this.pathToMigrations, { recursive: true });
|
|
77
|
-
await this.postgres.query(sql `
|
|
78
|
-
CREATE TABLE IF NOT EXISTS ${sql.identifier(this.migrationTableName)} (
|
|
79
|
-
id bigserial PRIMARY KEY,
|
|
80
|
-
version int NOT NULL,
|
|
81
|
-
run_at timestamp with time zone NOT NULL
|
|
82
|
-
);
|
|
83
|
-
`);
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
async isInstalled() {
|
|
87
|
-
try {
|
|
88
|
-
await this.postgres.query(sql `SELECT * FROM ${sql.identifier(this.migrationTableName)}`);
|
|
89
|
-
await fs.stat(this.pathToMigrations);
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
async create(name, format = 'sql') {
|
|
97
|
-
if (!name) {
|
|
98
|
-
throw new Error('Missing positional argument <name>');
|
|
99
|
-
}
|
|
100
|
-
if (!(await this.isInstalled())) {
|
|
101
|
-
await this.init();
|
|
102
|
-
}
|
|
103
|
-
const existingMigrations = await this.getMigrations();
|
|
104
|
-
const lastVersion = existingMigrations.length
|
|
105
|
-
? existingMigrations[existingMigrations.length - 1].version
|
|
106
|
-
: 0;
|
|
107
|
-
const filename = path.join(this.pathToMigrations, `${lastVersion + 1}_${name}.${format}`);
|
|
108
|
-
const getTemplate = () => {
|
|
109
|
-
switch (format) {
|
|
110
|
-
case 'sql':
|
|
111
|
-
return `-- Migration ${name} created ${new Date()}\n\nSELECT 1;\n\n-- ROLLBACK --\n\nSELECT 1;\n`;
|
|
112
|
-
case 'ts':
|
|
113
|
-
return `// Migration ${name} created ${new Date()}\n\nexport function up(query: any) {}\n\nexport function down(query: any) {}\n`;
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
await fs.writeFile(filename, getTemplate());
|
|
117
|
-
return filename;
|
|
118
|
-
}
|
|
119
|
-
async runMigrations(direction) {
|
|
120
|
-
if (direction !== 'up' && direction !== 'down') {
|
|
121
|
-
throw new Error('Only up and down are supported.');
|
|
122
|
-
}
|
|
123
|
-
if (!(await this.isInstalled())) {
|
|
124
|
-
await this.init();
|
|
125
|
-
}
|
|
126
|
-
const migrations = await this.getMigrations();
|
|
127
|
-
const currentVersion = await this.getCurrentVersion();
|
|
128
|
-
return this.executeMigration(migrations, currentVersion, direction);
|
|
129
|
-
}
|
|
130
|
-
log(...args) {
|
|
131
|
-
if (this.verbose) {
|
|
132
|
-
console.log(...args);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
async executeMigration(migrations, currentVersion, direction) {
|
|
136
|
-
if (direction === 'up') {
|
|
137
|
-
const pendingMigrations = this.getPendingMigrations(migrations, currentVersion);
|
|
138
|
-
if (!pendingMigrations.length) {
|
|
139
|
-
this.log(chalk.green('No pending migrations'));
|
|
140
|
-
return currentVersion;
|
|
141
|
-
}
|
|
142
|
-
for (const migration of pendingMigrations) {
|
|
143
|
-
this.log('Running migration', migration.filename);
|
|
144
|
-
const migrator = await this.readMigrationFile(migration.filename);
|
|
145
|
-
if (migrator.useTransaction !== false) {
|
|
146
|
-
await this.postgres.transaction(async () => {
|
|
147
|
-
await this.migrate(migrator, direction);
|
|
148
|
-
await this.updateCurrentVersion(migration.version);
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
await this.postgres.withConnection(async () => {
|
|
153
|
-
await this.migrate(migrator, direction);
|
|
154
|
-
await this.updateCurrentVersion(migration.version);
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
this.log(chalk.green('Done!'));
|
|
159
|
-
return await this.getCurrentVersion();
|
|
160
|
-
}
|
|
161
|
-
else if (direction === 'down') {
|
|
162
|
-
const previousMigrations = this.getPreviousMigrations(migrations, currentVersion);
|
|
163
|
-
const previousMigration = previousMigrations[0];
|
|
164
|
-
const updatedVersion = previousMigration ? previousMigration.version : 0;
|
|
165
|
-
const migrationToRollback = migrations.find((migration) => migration.version === currentVersion);
|
|
166
|
-
if (!migrationToRollback) {
|
|
167
|
-
this.log(chalk.green('No migrations have been run, so nothing to rollback to.'));
|
|
168
|
-
return currentVersion;
|
|
169
|
-
}
|
|
170
|
-
this.log('Rolling back migration', migrationToRollback.filename);
|
|
171
|
-
const migrator = await this.readMigrationFile(migrationToRollback.filename);
|
|
172
|
-
if (migrator.useTransaction !== false) {
|
|
173
|
-
await this.postgres.transaction(async () => {
|
|
174
|
-
await this.migrate(migrator, 'down');
|
|
175
|
-
await this.updateCurrentVersion(updatedVersion);
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
else {
|
|
179
|
-
await this.postgres.withConnection(async () => {
|
|
180
|
-
await this.migrate(migrator, 'down');
|
|
181
|
-
await this.updateCurrentVersion(updatedVersion);
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
return await this.getCurrentVersion();
|
|
185
|
-
/* c8 ignore next 2 */
|
|
186
|
-
}
|
|
187
|
-
throw new Error(`Invalid direction ${direction}`);
|
|
188
|
-
}
|
|
189
|
-
async readMigrationFile(filename) {
|
|
190
|
-
if (!filename.endsWith('.sql')) {
|
|
191
|
-
return import(filename);
|
|
192
|
-
}
|
|
193
|
-
const content = await fs.readFile(filename, 'utf-8');
|
|
194
|
-
const [up, down] = content.split(/[-\s]+ROLLBACK[-\s]+/i);
|
|
195
|
-
return {
|
|
196
|
-
useTransaction: !/[-\s]+NO[\s]*TRANSACTION[-\s]+/i.test(content),
|
|
197
|
-
up() {
|
|
198
|
-
return up.trim();
|
|
199
|
-
},
|
|
200
|
-
down() {
|
|
201
|
-
return (down || '').trim();
|
|
202
|
-
},
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
async migrate(migrator, direction) {
|
|
206
|
-
try {
|
|
207
|
-
const queryWithLog = (statement) => {
|
|
208
|
-
this.log(chalk.yellow(formatSql(statement)));
|
|
209
|
-
return this.postgres.query(statement);
|
|
210
|
-
};
|
|
211
|
-
const returnValue = await migrator[direction](queryWithLog.bind(this.postgres));
|
|
212
|
-
if (typeof returnValue === 'string') {
|
|
213
|
-
if (returnValue !== '') {
|
|
214
|
-
await queryWithLog(returnValue);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
await returnValue;
|
|
219
|
-
}
|
|
220
|
-
/* c8 ignore next 3 */
|
|
221
|
-
}
|
|
222
|
-
catch (error) {
|
|
223
|
-
throw new Error(`Migration failed: ${error.message}`);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
updateCurrentVersion(version) {
|
|
227
|
-
return this.postgres.query(`INSERT INTO ${this.migrationTableName} (version, run_at) VALUES ($1, $2)`, [version, new Date()]);
|
|
228
|
-
}
|
|
229
|
-
async getCurrentVersion() {
|
|
230
|
-
const result = await this.postgres.first(`SELECT version FROM ${this.migrationTableName} ORDER BY run_at DESC LIMIT 1`);
|
|
231
|
-
return result ? result.version : 0;
|
|
232
|
-
}
|
|
233
|
-
async getMigrations() {
|
|
234
|
-
const files = await fs.readdir(this.pathToMigrations);
|
|
235
|
-
return files
|
|
236
|
-
.map((filename) => filename.match(this.filenameRegExp))
|
|
237
|
-
.filter(notNullOrUndefined)
|
|
238
|
-
.map(([filename, version, name]) => ({
|
|
239
|
-
filename: path.join(this.pathToMigrations, filename),
|
|
240
|
-
version: parseInt(version, 10),
|
|
241
|
-
name,
|
|
242
|
-
}))
|
|
243
|
-
.sort((a, b) => a.version - b.version);
|
|
244
|
-
}
|
|
245
|
-
getPreviousMigrations(migrations, currentVersion) {
|
|
246
|
-
const reversedMigrations = migrations.reverse();
|
|
247
|
-
return reversedMigrations.filter((migration) => migration.version < currentVersion);
|
|
248
|
-
}
|
|
249
|
-
getPendingMigrations(migrations, currentVersion) {
|
|
250
|
-
return migrations.filter((migration) => migration.version > currentVersion);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
function notNullOrUndefined(x) {
|
|
254
|
-
return x != null;
|
|
255
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export interface GenerateDatabaseTypesOptions {
|
|
2
|
-
outputPath: string;
|
|
3
|
-
connectionString: string;
|
|
4
|
-
schema?: string;
|
|
5
|
-
excludeTables?: string[];
|
|
6
|
-
includeTables?: string[];
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* Generates complete TypeScript definitions from database schema
|
|
10
|
-
*/
|
|
11
|
-
export declare function generateDatabaseTypes(options: GenerateDatabaseTypesOptions): Promise<void>;
|