@devmoods/express-extras 0.42.0 → 0.43.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/dist/auth.d.ts +1 -1
- package/dist/auth.js +4 -4
- package/dist/config.d.ts +19 -0
- package/dist/config.js +27 -0
- package/dist/csrf.js +3 -3
- package/dist/faktory.js +2 -2
- package/dist/listen.js +4 -3
- package/dist/logger.js +2 -2
- package/dist/manage.js +6 -0
- package/dist/postgres.d.ts +1 -1
- package/dist/postgres.js +11 -6
- package/dist/redis.js +2 -2
- package/dist/sentry.js +2 -2
- package/dist/slack.js +2 -2
- package/package.json +6 -13
package/dist/auth.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export interface AuthOptions<UserModel extends AnyUser> {
|
|
|
48
48
|
export interface CreateAuth<UserModel extends AnyUser> {
|
|
49
49
|
createRouter: () => Router;
|
|
50
50
|
useCurrentUser: () => UserModel | undefined;
|
|
51
|
-
middleware: (options
|
|
51
|
+
middleware: (options?: AuthMiddlewareOptions<UserModel>) => RequestHandler;
|
|
52
52
|
createTemporaryToken(user: UserModel): Promise<string>;
|
|
53
53
|
}
|
|
54
54
|
export type AuthMiddlewareOptions<UserModel extends AnyUser> = false | {
|
package/dist/auth.js
CHANGED
|
@@ -3,17 +3,17 @@ import bcrypt from 'bcrypt';
|
|
|
3
3
|
import { Router, } from 'express';
|
|
4
4
|
import jwt from 'jsonwebtoken';
|
|
5
5
|
import { v4 } from 'uuid';
|
|
6
|
+
import { config } from './config.js';
|
|
6
7
|
import { setContext, useContext } from './context.js';
|
|
7
8
|
import { ApiError, BadRequest, Forbidden, NotFound, Unauthorized, } from './errors.js';
|
|
8
9
|
import { getLogger } from './logger.js';
|
|
9
10
|
import { route } from './middleware.js';
|
|
10
11
|
import {} from './redis.js';
|
|
11
|
-
import { readEnv } from './utils.js';
|
|
12
12
|
import { validateBody, validateParams, } from './validate.js';
|
|
13
13
|
const logger = getLogger('auth');
|
|
14
|
-
const TOKEN_EXPIRES_IN =
|
|
15
|
-
const TEMP_TOKEN_EXIRES_IN =
|
|
16
|
-
const AUTH_SECRET =
|
|
14
|
+
const TOKEN_EXPIRES_IN = config.value.AUTH_TOKEN_EXPIRES_IN;
|
|
15
|
+
const TEMP_TOKEN_EXIRES_IN = config.value.AUTH_TEMP_TOKEN_EXPIRES_IN;
|
|
16
|
+
const AUTH_SECRET = config.value.AUTH_SECRET;
|
|
17
17
|
const tokenSchema = {
|
|
18
18
|
type: 'string',
|
|
19
19
|
minLength: 50,
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const config: {
|
|
2
|
+
value: {
|
|
3
|
+
CI: boolean;
|
|
4
|
+
PORT: number;
|
|
5
|
+
AUTH_TOKEN_EXPIRES_IN: number;
|
|
6
|
+
AUTH_TEMP_TOKEN_EXPIRES_IN: number;
|
|
7
|
+
AUTH_SECRET: string;
|
|
8
|
+
REDIS_URL: string;
|
|
9
|
+
POSTGRES_URL: string;
|
|
10
|
+
SENTRY_DSN: string;
|
|
11
|
+
LOG_LEVEL: string;
|
|
12
|
+
LOG_SQL: boolean;
|
|
13
|
+
NODE_ENV: string;
|
|
14
|
+
CSRF_SECRET: string;
|
|
15
|
+
READINESS_PERIOD_MS: number;
|
|
16
|
+
GRACE_PERIOD_MS: number;
|
|
17
|
+
SLACK_WEBHOOK_URL: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { readEnv } from './utils.js';
|
|
2
|
+
function lazy(fn) {
|
|
3
|
+
return {
|
|
4
|
+
get value() {
|
|
5
|
+
const value = fn();
|
|
6
|
+
Object.defineProperty(this, 'value', { value });
|
|
7
|
+
return value;
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export const config = lazy(() => ({
|
|
12
|
+
CI: readEnv.boolean('CI', false),
|
|
13
|
+
PORT: readEnv.number('PORT', 0),
|
|
14
|
+
AUTH_TOKEN_EXPIRES_IN: readEnv.number('AUTH_TOKEN_EXPIRES', 3600 * 24 * 30), // 30 days
|
|
15
|
+
AUTH_TEMP_TOKEN_EXPIRES_IN: readEnv.number('AUTH_TEMP_TOKEN_EXPIRES', 3600),
|
|
16
|
+
AUTH_SECRET: readEnv('AUTH_JWT_SECRET', ''),
|
|
17
|
+
REDIS_URL: readEnv('REDIS_URL', ''),
|
|
18
|
+
POSTGRES_URL: readEnv('POSTGRES_URL', ''),
|
|
19
|
+
SENTRY_DSN: readEnv('SENTRY_DSN', ''),
|
|
20
|
+
LOG_LEVEL: readEnv('LOG_LEVEL', 'INFO'),
|
|
21
|
+
LOG_SQL: readEnv.boolean('LOG_SQL', false),
|
|
22
|
+
NODE_ENV: readEnv('NODE_ENV', 'development'),
|
|
23
|
+
CSRF_SECRET: readEnv('CSRF_SECRET', ''),
|
|
24
|
+
READINESS_PERIOD_MS: readEnv.number('READINESS_PERIOD_MS', 0),
|
|
25
|
+
GRACE_PERIOD_MS: readEnv.number('GRACE_PERIOD_MS', 20000),
|
|
26
|
+
SLACK_WEBHOOK_URL: readEnv('SLACK_WEBHOOK_URL', ''),
|
|
27
|
+
}));
|
package/dist/csrf.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import {} from 'express';
|
|
3
|
+
import { config } from './config.js';
|
|
3
4
|
import { setContext, useContext } from './context.js';
|
|
4
5
|
import { Forbidden } from './errors.js';
|
|
5
|
-
import { readEnv } from './utils.js';
|
|
6
6
|
const ignoredMethods = new Set(['GET', 'HEAD', 'OPTIONS']);
|
|
7
7
|
const defaultCookieName = () => {
|
|
8
|
-
return
|
|
8
|
+
return config.value.NODE_ENV === 'production' && !config.value.CI
|
|
9
9
|
? '__Host-devmoods.x-csrf-token'
|
|
10
10
|
: 'devmoods.x-csrf-token';
|
|
11
11
|
};
|
|
12
12
|
export const getCsrfSecret = () => {
|
|
13
|
-
const secret =
|
|
13
|
+
const secret = config.value.CSRF_SECRET;
|
|
14
14
|
if (!secret) {
|
|
15
15
|
throw new Error('CSRF_SECRET is not set');
|
|
16
16
|
}
|
package/dist/faktory.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'async_hooks';
|
|
2
|
-
import { randomUUID } from 'crypto';
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { create } from 'faktory-worker';
|
|
4
4
|
import { getLogger } from './logger.js';
|
|
5
5
|
import { startTimer } from './utils.js';
|
package/dist/listen.js
CHANGED
|
@@ -3,13 +3,14 @@ import {} from 'node:http';
|
|
|
3
3
|
import {} from 'node:net';
|
|
4
4
|
import { delay } from '@devmoods/fetch';
|
|
5
5
|
import {} from 'express';
|
|
6
|
+
import { config } from './config.js';
|
|
6
7
|
import { getLogger } from './logger.js';
|
|
7
8
|
import { before, route } from './middleware.js';
|
|
8
|
-
import { envPort
|
|
9
|
+
import { envPort } from './utils.js';
|
|
9
10
|
import { validateQuery } from './validate.js';
|
|
10
11
|
const logger = getLogger('http');
|
|
11
|
-
const READINESS_PERIOD =
|
|
12
|
-
const GRACE_PERIOD =
|
|
12
|
+
const READINESS_PERIOD = config.value.READINESS_PERIOD_MS;
|
|
13
|
+
const GRACE_PERIOD = config.value.GRACE_PERIOD_MS;
|
|
13
14
|
export function stoppable(server, grace = Infinity) {
|
|
14
15
|
let stopped = false;
|
|
15
16
|
let gracefully = true;
|
package/dist/logger.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as os from 'node:os';
|
|
2
2
|
import * as Sentry from '@sentry/node';
|
|
3
|
+
import { config } from './config.js';
|
|
3
4
|
import { useRequestId } from './context.js';
|
|
4
|
-
import { readEnv } from './utils.js';
|
|
5
5
|
const LEVELS = {
|
|
6
6
|
TRACE: 10,
|
|
7
7
|
DEBUG: 20,
|
|
@@ -24,7 +24,7 @@ function defaultGetContext() {
|
|
|
24
24
|
..._globalGetContext?.(),
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
-
export function createLogger({ name, getContext, level =
|
|
27
|
+
export function createLogger({ name, getContext, level = config.value.LOG_LEVEL, output = process.stdout, }) {
|
|
28
28
|
if (_loggers.has(name)) {
|
|
29
29
|
throw new Error(`A logger named ${name} already exists`);
|
|
30
30
|
}
|
package/dist/manage.js
CHANGED
|
@@ -2,6 +2,7 @@ import { program } from 'commander';
|
|
|
2
2
|
import {} from 'express';
|
|
3
3
|
import { hashPassword } from './auth.js';
|
|
4
4
|
import { CLI } from './cli.js';
|
|
5
|
+
import { config } from './config.js';
|
|
5
6
|
import { asCli } from './migrate.js';
|
|
6
7
|
import { parseRoutes } from './routes.js';
|
|
7
8
|
export function registerManagementCommand(register) {
|
|
@@ -44,4 +45,9 @@ export function registerDefaultCommands(app) {
|
|
|
44
45
|
cli.writeLine(`${hash}\n`);
|
|
45
46
|
}));
|
|
46
47
|
});
|
|
48
|
+
registerManagementCommand((command, cli) => {
|
|
49
|
+
command('show-config').action(cli.wrap(async () => {
|
|
50
|
+
console.log(config.value);
|
|
51
|
+
}));
|
|
52
|
+
});
|
|
47
53
|
}
|
package/dist/postgres.d.ts
CHANGED
|
@@ -39,7 +39,7 @@ export declare class Transaction extends Connection {
|
|
|
39
39
|
*
|
|
40
40
|
* @param config
|
|
41
41
|
*/
|
|
42
|
-
export declare function createPostgres({ onError, url, ...
|
|
42
|
+
export declare function createPostgres({ onError, url, ...postgresOptions }?: PostgresOptions): {
|
|
43
43
|
insert: <T extends QueryResultRow>(text: string | SqlStatement, values?: Value[]) => Promise<QueryResult<T>>;
|
|
44
44
|
query: <T extends QueryResultRow>(text: string | SqlStatement, values?: Value[]) => Promise<QueryResult<T>>;
|
|
45
45
|
first: <T extends QueryResultRow>(text: string | SqlStatement, values?: Value[]) => Promise<T | null>;
|
package/dist/postgres.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import pg, {} from 'pg';
|
|
2
2
|
import { format } from 'sql-formatter';
|
|
3
|
-
import {
|
|
3
|
+
import { config } from './config.js';
|
|
4
|
+
import { startTimer } from './utils.js';
|
|
4
5
|
export const escapePostgresIdentifier = pg.escapeIdentifier;
|
|
5
6
|
export const escapePostgresLiteral = pg.escapeLiteral;
|
|
6
7
|
export const formatSql = (statement) => {
|
|
@@ -39,7 +40,7 @@ export class Connection {
|
|
|
39
40
|
if (this.options.onLogStatement) {
|
|
40
41
|
return this.options.onLogStatement(text, values, duration);
|
|
41
42
|
}
|
|
42
|
-
if (
|
|
43
|
+
if (config.value.LOG_SQL) {
|
|
43
44
|
console.log(`\n${formatSql(text)}\n\nValues: ${JSON.stringify(values)}\nDuration: ${duration}ms\n`);
|
|
44
45
|
}
|
|
45
46
|
}
|
|
@@ -93,15 +94,17 @@ export class Transaction extends Connection {
|
|
|
93
94
|
*
|
|
94
95
|
* @param config
|
|
95
96
|
*/
|
|
96
|
-
export function createPostgres({ onError, url =
|
|
97
|
-
const pool = new pg.Pool({ connectionString: url, ...
|
|
97
|
+
export function createPostgres({ onError, url = config.value.POSTGRES_URL, ...postgresOptions } = {}) {
|
|
98
|
+
const pool = new pg.Pool({ connectionString: url, ...postgresOptions });
|
|
98
99
|
pool.on('error', (e, client) => {
|
|
99
100
|
onError?.(e, client);
|
|
100
101
|
});
|
|
101
102
|
async function withConnection(callback) {
|
|
102
103
|
const client = await pool.connect();
|
|
103
104
|
try {
|
|
104
|
-
const result = await callback(new Connection(client, {
|
|
105
|
+
const result = await callback(new Connection(client, {
|
|
106
|
+
onLogStatement: postgresOptions.onLogStatement,
|
|
107
|
+
}));
|
|
105
108
|
return result;
|
|
106
109
|
}
|
|
107
110
|
finally {
|
|
@@ -127,7 +130,9 @@ export function createPostgres({ onError, url = readEnv('POSTGRES_URL'), ...conf
|
|
|
127
130
|
const client = await pool.connect();
|
|
128
131
|
try {
|
|
129
132
|
await client.query('BEGIN');
|
|
130
|
-
const result = await callback(new Transaction(client, {
|
|
133
|
+
const result = await callback(new Transaction(client, {
|
|
134
|
+
onLogStatement: postgresOptions.onLogStatement,
|
|
135
|
+
}));
|
|
131
136
|
await client.query('COMMIT');
|
|
132
137
|
return result;
|
|
133
138
|
}
|
package/dist/redis.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createClient } from 'redis';
|
|
2
|
-
import {
|
|
3
|
-
export function createRedis({ url =
|
|
2
|
+
import { config } from './config.js';
|
|
3
|
+
export function createRedis({ url = config.value.REDIS_URL, onError, } = {}) {
|
|
4
4
|
const client = createClient({ url });
|
|
5
5
|
// Always attach a listener even if
|
|
6
6
|
// onError is not provided.
|
package/dist/sentry.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { nodeProfilingIntegration } from '@sentry/profiling-node';
|
|
2
|
-
import {
|
|
2
|
+
import { config } from './config.js';
|
|
3
3
|
export function sentryOptions(options = {}) {
|
|
4
4
|
return {
|
|
5
|
-
dsn:
|
|
5
|
+
dsn: config.value.SENTRY_DSN,
|
|
6
6
|
tracesSampleRate: 1.0,
|
|
7
7
|
profilesSampleRate: 1.0,
|
|
8
8
|
integrations: [nodeProfilingIntegration()],
|
package/dist/slack.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createFetch } from '@devmoods/fetch';
|
|
2
|
+
import { config } from './config.js';
|
|
2
3
|
import { getLogger } from './logger.js';
|
|
3
|
-
import { readEnv } from './utils.js';
|
|
4
4
|
const logger = getLogger('slack');
|
|
5
5
|
/**
|
|
6
6
|
* Send messages to Slack using Incoming Webhooks
|
|
@@ -15,7 +15,7 @@ const logger = getLogger('slack');
|
|
|
15
15
|
*
|
|
16
16
|
* See https://api.slack.com/messaging/webhooks
|
|
17
17
|
*/
|
|
18
|
-
export function createSlack({ webhookUrl =
|
|
18
|
+
export function createSlack({ webhookUrl = config.value.SLACK_WEBHOOK_URL, dryRun = process.env.NODE_ENV !== 'production', } = {}) {
|
|
19
19
|
const fetch = createFetch();
|
|
20
20
|
/**
|
|
21
21
|
* Post a message to Slack
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devmoods/express-extras",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"description": "Kubernetes-ready utilities for rapid Node.js application development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,19 +18,15 @@
|
|
|
18
18
|
],
|
|
19
19
|
"license": "ISC",
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@devmoods/eslint-config": "^1.
|
|
21
|
+
"@devmoods/eslint-config": "^2.1.0",
|
|
22
22
|
"@types/koa-compose": "^3.2.8",
|
|
23
23
|
"@types/node": "22.10.1",
|
|
24
24
|
"@types/supertest": "^6.0.2",
|
|
25
|
-
"@typescript-eslint/eslint-plugin": "^8.17.0",
|
|
26
|
-
"@typescript-eslint/parser": "^8.17.0",
|
|
27
25
|
"@vitest/coverage-v8": "^2.1.8",
|
|
28
26
|
"cookiejar": "^2.1.4",
|
|
29
|
-
"eslint": "^
|
|
30
|
-
"eslint-plugin-import": "^2.31.0",
|
|
31
|
-
"eslint-plugin-prettier": "^5.2.1",
|
|
27
|
+
"eslint": "^9.16.0",
|
|
32
28
|
"faktory-worker": "^4.6.0",
|
|
33
|
-
"prettier": "3.4.
|
|
29
|
+
"prettier": "3.4.2",
|
|
34
30
|
"supertest": "7.0.0",
|
|
35
31
|
"ts-node": "10.9.2",
|
|
36
32
|
"tsx": "^4.19.2",
|
|
@@ -58,7 +54,7 @@
|
|
|
58
54
|
"commander": "^12.1.0",
|
|
59
55
|
"compose-middleware": "^5.0.1",
|
|
60
56
|
"cookie-parser": "^1.4.7",
|
|
61
|
-
"dotenv": "^16.4.
|
|
57
|
+
"dotenv": "^16.4.7",
|
|
62
58
|
"express": "^4.21.1",
|
|
63
59
|
"helmet": "^8.0.0",
|
|
64
60
|
"jsonwebtoken": "^9.0.2",
|
|
@@ -71,9 +67,6 @@
|
|
|
71
67
|
"prettier": {
|
|
72
68
|
"singleQuote": true
|
|
73
69
|
},
|
|
74
|
-
"eslintConfig": {
|
|
75
|
-
"extends": "@devmoods/eslint-config"
|
|
76
|
-
},
|
|
77
70
|
"peerDependencies": {
|
|
78
71
|
"faktory-worker": "^4.5.1"
|
|
79
72
|
},
|
|
@@ -84,7 +77,7 @@
|
|
|
84
77
|
"start": "pushd example; pnpm run start --watch; popd example;",
|
|
85
78
|
"start:worker": "pushd example; pnpm run start:worker; popd example;",
|
|
86
79
|
"clean": "rm -rf dist",
|
|
87
|
-
"lint": "eslint
|
|
80
|
+
"lint": "eslint",
|
|
88
81
|
"test": "./test.sh",
|
|
89
82
|
"build": "pnpm run clean && tsc --project tsconfig.build.json",
|
|
90
83
|
"format": "prettier --write '**/*.{js,css,md,ts,tsx}'"
|