@devmoods/express-extras 0.63.1 → 0.65.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 +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 +9 -5
- package/dist/channels.js +115 -23
- package/dist/cli.js +1 -1
- package/dist/config.d.ts +66 -66
- package/dist/config.js +35 -36
- package/dist/context.d.ts +5 -2
- package/dist/context.js +10 -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 +5 -4
- package/dist/listen.js +47 -45
- 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 +3 -0
- package/dist/redis.js +25 -2
- package/dist/sentry.js +7 -3
- package/dist/setupTests.global.d.ts +2 -0
- package/dist/setupTests.global.js +4 -0
- package/dist/setupTests.js +5 -1
- package/dist/slack.js +2 -2
- package/dist/utils.d.ts +2 -14
- package/dist/utils.js +2 -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 +6 -0
- package/dist/vitest/index.js +25 -0
- package/dist/vitest/matchers.d.ts +11 -0
- package/dist/vitest/matchers.js +65 -0
- package/dist/vitest/testClient.d.ts +30 -0
- package/dist/vitest/testClient.js +98 -0
- package/dist/webapi.js +4 -0
- package/package.json +38 -43
- 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/vitest.d.ts +0 -1
- package/dist/vite/vitest.js +0 -1
- package/dist/vite.d.ts +0 -1
- package/dist/vite.js +0 -1
package/dist/config.d.ts
CHANGED
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
export declare
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
};
|
|
1
|
+
declare module '@devmoods/config' {
|
|
2
|
+
interface Config extends ConfigFromDefs<typeof app> {
|
|
3
|
+
}
|
|
4
|
+
}
|
|
5
|
+
declare const app: import("@devmoods/config").Defs<{
|
|
6
|
+
CI: boolean;
|
|
7
|
+
PORT: number;
|
|
8
|
+
PUBLIC_URL: string;
|
|
9
|
+
AUTH_TOKEN_EXPIRES: number;
|
|
10
|
+
AUTH_TEMP_TOKEN_EXPIRES: number;
|
|
11
|
+
AUTH_OTP_EXPIRES: number;
|
|
12
|
+
AUTH_JWT_SECRET: string;
|
|
13
|
+
AUTH_COOKIE_SAME_SITE: string;
|
|
14
|
+
REDIS_URL: string;
|
|
15
|
+
REDIS_KEY_PREFIX: string;
|
|
16
|
+
SENTRY_DSN: string;
|
|
17
|
+
LOG_LEVEL: string;
|
|
18
|
+
LOG_SQL: boolean;
|
|
19
|
+
NODE_ENV: string;
|
|
20
|
+
CSRF_SECRET: string;
|
|
21
|
+
READINESS_PERIOD_MS: number;
|
|
22
|
+
GRACE_PERIOD_MS: number;
|
|
23
|
+
SLACK_WEBHOOK_URL: string;
|
|
24
|
+
VITE_USE_DEV_SERVER: boolean;
|
|
25
|
+
EMAIL_HOST: string;
|
|
26
|
+
EMAIL_PORT: number;
|
|
27
|
+
EMAIL_USER: string;
|
|
28
|
+
EMAIL_PASS: string;
|
|
29
|
+
EMAIL_SENDER_NAME: string;
|
|
30
|
+
EMAIL_SENDER_ADDRESS: string;
|
|
31
|
+
GOOGLE_AUTH_CLIENT_ID: string;
|
|
32
|
+
GOOGLE_AUTH_CLIENT_SECRET: string;
|
|
33
|
+
}>;
|
|
34
|
+
export declare const config: import("@devmoods/config").Defs<{
|
|
35
|
+
CI: boolean;
|
|
36
|
+
PORT: number;
|
|
37
|
+
PUBLIC_URL: string;
|
|
38
|
+
AUTH_TOKEN_EXPIRES: number;
|
|
39
|
+
AUTH_TEMP_TOKEN_EXPIRES: number;
|
|
40
|
+
AUTH_OTP_EXPIRES: number;
|
|
41
|
+
AUTH_JWT_SECRET: string;
|
|
42
|
+
AUTH_COOKIE_SAME_SITE: string;
|
|
43
|
+
REDIS_URL: string;
|
|
44
|
+
REDIS_KEY_PREFIX: string;
|
|
45
|
+
SENTRY_DSN: string;
|
|
46
|
+
LOG_LEVEL: string;
|
|
47
|
+
LOG_SQL: boolean;
|
|
48
|
+
NODE_ENV: string;
|
|
49
|
+
CSRF_SECRET: string;
|
|
50
|
+
READINESS_PERIOD_MS: number;
|
|
51
|
+
GRACE_PERIOD_MS: number;
|
|
52
|
+
SLACK_WEBHOOK_URL: string;
|
|
53
|
+
VITE_USE_DEV_SERVER: boolean;
|
|
54
|
+
EMAIL_HOST: string;
|
|
55
|
+
EMAIL_PORT: number;
|
|
56
|
+
EMAIL_USER: string;
|
|
57
|
+
EMAIL_PASS: string;
|
|
58
|
+
EMAIL_SENDER_NAME: string;
|
|
59
|
+
EMAIL_SENDER_ADDRESS: string;
|
|
60
|
+
GOOGLE_AUTH_CLIENT_ID: string;
|
|
61
|
+
GOOGLE_AUTH_CLIENT_SECRET: string;
|
|
62
|
+
} & {
|
|
63
|
+
POSTGRES_URL: string;
|
|
64
|
+
POSTGRES_PRINT_SQL: boolean;
|
|
65
|
+
}>;
|
|
66
|
+
export {};
|
package/dist/config.js
CHANGED
|
@@ -1,39 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
GRACE_PERIOD_MS: readEnv.number('GRACE_PERIOD_MS', 20000),
|
|
24
|
-
SLACK_WEBHOOK_URL: readEnv('SLACK_WEBHOOK_URL', ''),
|
|
25
|
-
VITE_USE_DEV_SERVER: readEnv.boolean('VITE_USE_DEV_SERVER', false),
|
|
1
|
+
import { define, merge, t } from '@devmoods/config';
|
|
2
|
+
import { postgresConfig } from '@devmoods/postgres';
|
|
3
|
+
const app = define({
|
|
4
|
+
CI: t.boolean(false),
|
|
5
|
+
PORT: t.number(0),
|
|
6
|
+
PUBLIC_URL: t.string(),
|
|
7
|
+
AUTH_TOKEN_EXPIRES: t.number(3600 * 24 * 30),
|
|
8
|
+
AUTH_TEMP_TOKEN_EXPIRES: t.number(3600),
|
|
9
|
+
AUTH_OTP_EXPIRES: t.number(5 * 60),
|
|
10
|
+
AUTH_JWT_SECRET: t.string(),
|
|
11
|
+
AUTH_COOKIE_SAME_SITE: t.enum(['strict', 'lax', 'none'], 'strict'),
|
|
12
|
+
REDIS_URL: t.string(),
|
|
13
|
+
REDIS_KEY_PREFIX: t.string(),
|
|
14
|
+
SENTRY_DSN: t.string(),
|
|
15
|
+
LOG_LEVEL: t.string(),
|
|
16
|
+
LOG_SQL: t.boolean(),
|
|
17
|
+
NODE_ENV: t.string(),
|
|
18
|
+
CSRF_SECRET: t.string(),
|
|
19
|
+
READINESS_PERIOD_MS: t.number(0),
|
|
20
|
+
GRACE_PERIOD_MS: t.number(20_000),
|
|
21
|
+
SLACK_WEBHOOK_URL: t.string(),
|
|
22
|
+
VITE_USE_DEV_SERVER: t.boolean(false),
|
|
26
23
|
/**
|
|
27
24
|
* Email configuration
|
|
28
25
|
*/
|
|
29
|
-
EMAIL_HOST:
|
|
30
|
-
EMAIL_PORT:
|
|
31
|
-
EMAIL_USER:
|
|
32
|
-
EMAIL_PASS:
|
|
33
|
-
EMAIL_SENDER_NAME:
|
|
34
|
-
EMAIL_SENDER_ADDRESS:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
26
|
+
EMAIL_HOST: t.string(),
|
|
27
|
+
EMAIL_PORT: t.number(587),
|
|
28
|
+
EMAIL_USER: t.string(),
|
|
29
|
+
EMAIL_PASS: t.string(),
|
|
30
|
+
EMAIL_SENDER_NAME: t.string(),
|
|
31
|
+
EMAIL_SENDER_ADDRESS: t.string(),
|
|
32
|
+
/**
|
|
33
|
+
* Google configuration
|
|
34
|
+
*/
|
|
35
|
+
GOOGLE_AUTH_CLIENT_ID: t.string(),
|
|
36
|
+
GOOGLE_AUTH_CLIENT_SECRET: t.string(),
|
|
37
|
+
});
|
|
38
|
+
export const config = merge(app, postgresConfig);
|
package/dist/context.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
2
|
import { type Request, type RequestHandler } from 'express';
|
|
3
3
|
export declare const requestContext: AsyncLocalStorage<Map<string, any>>;
|
|
4
|
+
export declare const REQUEST_ID_CONTEXT_KEY = "requestId";
|
|
5
|
+
export declare const JOB_ID_CONTEXT_KEY = "jobId";
|
|
4
6
|
export interface RequestContextOptions {
|
|
5
7
|
getRequestId?: (req: Request) => string | undefined;
|
|
6
8
|
}
|
|
@@ -11,14 +13,15 @@ export declare function withRequestContext({ getRequestId, }?: RequestContextOpt
|
|
|
11
13
|
/**
|
|
12
14
|
* Return a value from the request context
|
|
13
15
|
*/
|
|
14
|
-
export declare function
|
|
16
|
+
export declare function useRequestContext<T>(key: string): T | undefined;
|
|
15
17
|
/**
|
|
16
18
|
* Set a value on the request context
|
|
17
19
|
*/
|
|
18
|
-
export declare function
|
|
20
|
+
export declare function setRequestContext<T>(key: string, value: T): void;
|
|
19
21
|
/**
|
|
20
22
|
* Obtain the requestId from the current async request context.
|
|
21
23
|
*/
|
|
22
24
|
export declare function useRequestId(): string | undefined;
|
|
25
|
+
export declare function useJobId(): string | undefined;
|
|
23
26
|
export declare const ANONYMOUS_SESSION_KEY = "anonymous-session";
|
|
24
27
|
export declare function useAnonymousSession(): string | undefined;
|
package/dist/context.js
CHANGED
|
@@ -2,7 +2,8 @@ import { AsyncLocalStorage } from 'node:async_hooks';
|
|
|
2
2
|
import {} from 'express';
|
|
3
3
|
import { v4 as uuid } from 'uuid';
|
|
4
4
|
export const requestContext = new AsyncLocalStorage();
|
|
5
|
-
const
|
|
5
|
+
export const REQUEST_ID_CONTEXT_KEY = 'requestId';
|
|
6
|
+
export const JOB_ID_CONTEXT_KEY = 'jobId';
|
|
6
7
|
/**
|
|
7
8
|
* Middleware for setting a `requestId` on the request context.
|
|
8
9
|
*/
|
|
@@ -10,10 +11,7 @@ export function withRequestContext({ getRequestId = (req) => req.headers['X-Requ
|
|
|
10
11
|
return (req, res, next) => {
|
|
11
12
|
const requestId = getRequestId(req) || uuid();
|
|
12
13
|
requestContext.run(new Map(), () => {
|
|
13
|
-
|
|
14
|
-
if (store) {
|
|
15
|
-
store.set(REQUEST_ID, requestId);
|
|
16
|
-
}
|
|
14
|
+
setRequestContext(REQUEST_ID_CONTEXT_KEY, requestId);
|
|
17
15
|
next();
|
|
18
16
|
});
|
|
19
17
|
};
|
|
@@ -21,7 +19,7 @@ export function withRequestContext({ getRequestId = (req) => req.headers['X-Requ
|
|
|
21
19
|
/**
|
|
22
20
|
* Return a value from the request context
|
|
23
21
|
*/
|
|
24
|
-
export function
|
|
22
|
+
export function useRequestContext(key) {
|
|
25
23
|
const store = requestContext.getStore();
|
|
26
24
|
if (store) {
|
|
27
25
|
return store.get(key);
|
|
@@ -30,7 +28,7 @@ export function useContext(key) {
|
|
|
30
28
|
/**
|
|
31
29
|
* Set a value on the request context
|
|
32
30
|
*/
|
|
33
|
-
export function
|
|
31
|
+
export function setRequestContext(key, value) {
|
|
34
32
|
const store = requestContext.getStore();
|
|
35
33
|
if (store) {
|
|
36
34
|
store.set(key, value);
|
|
@@ -40,9 +38,12 @@ export function setContext(key, value) {
|
|
|
40
38
|
* Obtain the requestId from the current async request context.
|
|
41
39
|
*/
|
|
42
40
|
export function useRequestId() {
|
|
43
|
-
return
|
|
41
|
+
return useRequestContext(REQUEST_ID_CONTEXT_KEY);
|
|
42
|
+
}
|
|
43
|
+
export function useJobId() {
|
|
44
|
+
return useRequestContext(JOB_ID_CONTEXT_KEY);
|
|
44
45
|
}
|
|
45
46
|
export const ANONYMOUS_SESSION_KEY = 'anonymous-session';
|
|
46
47
|
export function useAnonymousSession() {
|
|
47
|
-
return
|
|
48
|
+
return useRequestContext(ANONYMOUS_SESSION_KEY);
|
|
48
49
|
}
|
package/dist/csrf.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { config } from '@devmoods/config';
|
|
2
3
|
import {} from 'express';
|
|
3
|
-
import {
|
|
4
|
-
import { setContext, useContext } from './context.js';
|
|
4
|
+
import { setRequestContext, useRequestContext } from './context.js';
|
|
5
5
|
import { Forbidden } from './errors.js';
|
|
6
6
|
const ignoredMethods = new Set(['GET', 'HEAD', 'OPTIONS']);
|
|
7
7
|
const defaultCookieName = () => {
|
|
8
|
-
return config.
|
|
8
|
+
return config.NODE_ENV === 'production' && !config.CI
|
|
9
9
|
? '__Host-devmoods.x-csrf-token'
|
|
10
10
|
: 'devmoods.x-csrf-token';
|
|
11
11
|
};
|
|
12
12
|
export const getCsrfSecret = () => {
|
|
13
|
-
const secret = config.
|
|
13
|
+
const secret = config.CSRF_SECRET;
|
|
14
14
|
if (!secret) {
|
|
15
15
|
throw new Error('CSRF_SECRET is not set');
|
|
16
16
|
}
|
|
@@ -31,10 +31,10 @@ export function csrf({ exempt = false, sessionIdentifier, cookieName, } = {}) {
|
|
|
31
31
|
}
|
|
32
32
|
const CSRF_CONTEXT_KEY = '@csrfConfig';
|
|
33
33
|
export function setCsrfOptions(options) {
|
|
34
|
-
|
|
34
|
+
setRequestContext(CSRF_CONTEXT_KEY, options);
|
|
35
35
|
}
|
|
36
36
|
function useCsrfOptions() {
|
|
37
|
-
return (
|
|
37
|
+
return (useRequestContext(CSRF_CONTEXT_KEY) || {
|
|
38
38
|
exempt: true,
|
|
39
39
|
cookieName: defaultCookieName(),
|
|
40
40
|
sessionIdentifier: '',
|
package/dist/email.d.ts
CHANGED
package/dist/email.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { config } from '@devmoods/config';
|
|
1
2
|
import * as nodemailer from 'nodemailer';
|
|
2
|
-
import { config } from './config.js';
|
|
3
3
|
import { getLogger } from './logger.js';
|
|
4
4
|
const logger = getLogger('email');
|
|
5
|
-
export function createEmail({ host = config.
|
|
5
|
+
export function createEmail({ host = config.EMAIL_HOST, port = config.EMAIL_PORT, user = config.EMAIL_USER, pass = config.EMAIL_PASS, senderName = config.EMAIL_SENDER_NAME, senderAddress = config.EMAIL_SENDER_ADDRESS, onEmailSent, } = {}) {
|
|
6
6
|
const transporter = nodemailer.createTransport({
|
|
7
7
|
host,
|
|
8
|
-
port
|
|
8
|
+
port,
|
|
9
9
|
secure: false,
|
|
10
10
|
auth: user && pass ? { user, pass } : undefined,
|
|
11
11
|
});
|
package/dist/errors.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { STATUS_CODES } from 'node:http';
|
|
2
|
+
import { DoesNotExistError, IntegrityError } from '@devmoods/postgres';
|
|
2
3
|
import * as Sentry from '@sentry/node';
|
|
3
4
|
import { errors } from 'compose-middleware';
|
|
4
5
|
import {} from 'express';
|
|
5
6
|
import { InvalidToken } from './auth/errors.js';
|
|
6
7
|
import { getLogger } from './logger.js';
|
|
7
|
-
import { DoesNotExistError, IntegrityError } from './postgres.js';
|
|
8
8
|
export class ApiError extends Error {
|
|
9
9
|
status;
|
|
10
10
|
payload;
|
package/dist/faktory.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export interface FaktoryWorkerOptions<GlobalContext> {
|
|
|
26
26
|
faktory?: typeof create;
|
|
27
27
|
jobContext?: GlobalContext;
|
|
28
28
|
}
|
|
29
|
-
export
|
|
29
|
+
export { createFaktoryWorker as createJobWorker };
|
|
30
30
|
export declare function createFaktoryWorker<GlobalContext>({ queueName, jobs, faktory: createFactory, jobContext, }: FaktoryWorkerOptions<GlobalContext>): Promise<{
|
|
31
31
|
start(workerOptions?: WorkerOptions): Promise<void>;
|
|
32
32
|
stop(): Promise<void>;
|
package/dist/faktory.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { startTimer } from '@devmoods/observability';
|
|
3
3
|
import { create } from 'faktory-worker';
|
|
4
|
+
import { JOB_ID_CONTEXT_KEY, requestContext, setRequestContext, } from './context.js';
|
|
4
5
|
import { getLogger } from './logger.js';
|
|
5
|
-
import { startTimer } from './utils.js';
|
|
6
6
|
const logger = getLogger('faktory');
|
|
7
|
-
const JOB_ID = 'jobId';
|
|
8
7
|
export function createJobs({ queueName, faktory: createFactory = create, }) {
|
|
9
8
|
const faktory = createFactory();
|
|
10
9
|
function job(optionsOrFn, fnOrUndefined) {
|
|
@@ -56,13 +55,7 @@ export function createJobs({ queueName, faktory: createFactory = create, }) {
|
|
|
56
55
|
}
|
|
57
56
|
return { job, queueName };
|
|
58
57
|
}
|
|
59
|
-
|
|
60
|
-
export function useJobId() {
|
|
61
|
-
const store = context.getStore();
|
|
62
|
-
if (store) {
|
|
63
|
-
return store.get(JOB_ID);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
58
|
+
export { createFaktoryWorker as createJobWorker };
|
|
66
59
|
export async function createFaktoryWorker({ queueName, jobs, faktory: createFactory = create, jobContext, }) {
|
|
67
60
|
const faktory = createFactory();
|
|
68
61
|
const filter = (j) => !!Object.prototype.hasOwnProperty.call(j, '$job');
|
|
@@ -86,11 +79,8 @@ export async function createFaktoryWorker({ queueName, jobs, faktory: createFact
|
|
|
86
79
|
jobName: ctx.job.jobtype,
|
|
87
80
|
jobId,
|
|
88
81
|
});
|
|
89
|
-
await
|
|
90
|
-
|
|
91
|
-
if (store) {
|
|
92
|
-
store.set(JOB_ID, jobId);
|
|
93
|
-
}
|
|
82
|
+
await requestContext.run(new Map(), async () => {
|
|
83
|
+
setRequestContext(JOB_ID_CONTEXT_KEY, jobId);
|
|
94
84
|
await next();
|
|
95
85
|
});
|
|
96
86
|
const duration = timer.getDuration();
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export * from '@devmoods/observability';
|
|
2
|
+
export * from '@devmoods/postgres';
|
|
1
3
|
export * from './auth.js';
|
|
2
4
|
export * from './cache.js';
|
|
3
5
|
export * from './cookie-consent.js';
|
|
@@ -13,14 +15,12 @@ export * from './listen.js';
|
|
|
13
15
|
export * from './logger.js';
|
|
14
16
|
export * from './manage.js';
|
|
15
17
|
export * from './middleware.js';
|
|
16
|
-
export * from './postgres.js';
|
|
17
18
|
export * from './rate-limit.js';
|
|
18
19
|
export * from './redis.js';
|
|
19
20
|
export * from './router.js';
|
|
20
21
|
export * from './routes.js';
|
|
21
22
|
export * from './sentry.js';
|
|
22
23
|
export * from './slack.js';
|
|
23
|
-
export * from './sql.js';
|
|
24
24
|
export * from './utils.js';
|
|
25
25
|
export * from './validate.js';
|
|
26
26
|
export * from './webapi.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
/* c8 ignore
|
|
1
|
+
/* c8 ignore start */
|
|
2
|
+
export * from '@devmoods/observability';
|
|
3
|
+
export * from '@devmoods/postgres';
|
|
2
4
|
export * from './auth.js';
|
|
3
5
|
export * from './cache.js';
|
|
4
6
|
export * from './cookie-consent.js';
|
|
@@ -14,14 +16,13 @@ export * from './listen.js';
|
|
|
14
16
|
export * from './logger.js';
|
|
15
17
|
export * from './manage.js';
|
|
16
18
|
export * from './middleware.js';
|
|
17
|
-
export * from './postgres.js';
|
|
18
19
|
export * from './rate-limit.js';
|
|
19
20
|
export * from './redis.js';
|
|
20
21
|
export * from './router.js';
|
|
21
22
|
export * from './routes.js';
|
|
22
23
|
export * from './sentry.js';
|
|
23
24
|
export * from './slack.js';
|
|
24
|
-
export * from './sql.js';
|
|
25
25
|
export * from './utils.js';
|
|
26
26
|
export * from './validate.js';
|
|
27
27
|
export * from './webapi.js';
|
|
28
|
+
/* c8 ignore stop */
|
package/dist/listen.d.ts
CHANGED
|
@@ -7,12 +7,13 @@ interface StoppableServer extends Server {
|
|
|
7
7
|
export declare function stoppable(server: Server, grace?: number): StoppableServer;
|
|
8
8
|
interface ExpressServerOptions {
|
|
9
9
|
onShutdown?(): Promise<void> | void;
|
|
10
|
+
beforeShutdown?(): Promise<void> | void;
|
|
10
11
|
healthCheck?(type?: 'live' | 'ready'): Promise<unknown>;
|
|
11
12
|
}
|
|
12
|
-
|
|
13
|
+
interface ExpressServer extends StoppableServer {
|
|
13
14
|
start(): Promise<void>;
|
|
14
|
-
|
|
15
|
+
port: number;
|
|
15
16
|
terminate(): Promise<void>;
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
}
|
|
18
|
+
export declare function createExpressServer(app: Application, options?: ExpressServerOptions): ExpressServer;
|
|
18
19
|
export {};
|
package/dist/listen.js
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import * as http from 'node:http';
|
|
2
2
|
import {} from 'node:http';
|
|
3
3
|
import {} from 'node:net';
|
|
4
|
+
import { config } from '@devmoods/config';
|
|
4
5
|
import { delay } from '@devmoods/fetch';
|
|
6
|
+
import { startTimer } from '@devmoods/observability';
|
|
5
7
|
import {} from 'express';
|
|
6
|
-
import { config } from './config.js';
|
|
7
8
|
import { BadRequest } from './errors.js';
|
|
8
9
|
import { getLogger } from './logger.js';
|
|
9
10
|
import { route } from './middleware.js';
|
|
10
|
-
import { envPort
|
|
11
|
-
import { validateQuery } from './validate.js';
|
|
11
|
+
import { envPort } from './utils.js';
|
|
12
12
|
const logger = getLogger('http');
|
|
13
13
|
const appTimer = startTimer();
|
|
14
|
-
const READINESS_PERIOD = config.value.READINESS_PERIOD_MS;
|
|
15
|
-
const GRACE_PERIOD = config.value.GRACE_PERIOD_MS;
|
|
16
14
|
export function stoppable(server, grace = Infinity) {
|
|
17
15
|
let stopped = false;
|
|
18
16
|
let gracefully = true;
|
|
@@ -76,7 +74,7 @@ export function stoppable(server, grace = Infinity) {
|
|
|
76
74
|
return server;
|
|
77
75
|
}
|
|
78
76
|
export function createExpressServer(app, options = {}) {
|
|
79
|
-
const server = stoppable(http.createServer(app),
|
|
77
|
+
const server = stoppable(http.createServer(app), config.GRACE_PERIOD_MS);
|
|
80
78
|
let stopping = false;
|
|
81
79
|
async function onSignal(signal) {
|
|
82
80
|
if (stopping) {
|
|
@@ -86,10 +84,14 @@ export function createExpressServer(app, options = {}) {
|
|
|
86
84
|
const timer = startTimer();
|
|
87
85
|
try {
|
|
88
86
|
logger.info(`app shutting down due to ${signal}`);
|
|
89
|
-
await delay(
|
|
90
|
-
await
|
|
87
|
+
await delay(config.READINESS_PERIOD_MS + 1000);
|
|
88
|
+
await options.beforeShutdown?.();
|
|
89
|
+
const gracefully = await server.stop();
|
|
91
90
|
await options.onShutdown?.();
|
|
92
|
-
logger.info('app shut down', {
|
|
91
|
+
logger.info('app shut down', {
|
|
92
|
+
duration: timer.getDuration(),
|
|
93
|
+
gracefully,
|
|
94
|
+
});
|
|
93
95
|
process.exit(0);
|
|
94
96
|
}
|
|
95
97
|
catch (error) {
|
|
@@ -104,23 +106,18 @@ export function createExpressServer(app, options = {}) {
|
|
|
104
106
|
}
|
|
105
107
|
const healthTypes = ['live', 'ready'];
|
|
106
108
|
if (!app.get('__healthAttached')) {
|
|
107
|
-
app.get('/health/:type?',
|
|
108
|
-
type: 'object',
|
|
109
|
-
properties: {
|
|
110
|
-
type: { type: 'string', enum: ['live', 'ready'] },
|
|
111
|
-
},
|
|
112
|
-
}), route(async (req) => {
|
|
109
|
+
app.get('/health/:type?', route(async (req) => {
|
|
113
110
|
const timestamp = new Date().toISOString();
|
|
111
|
+
const validateType = (type) => {
|
|
112
|
+
return healthTypes.includes(type);
|
|
113
|
+
};
|
|
114
|
+
const type = req.params.type || req.query.type || 'ready';
|
|
115
|
+
if (!validateType(type)) {
|
|
116
|
+
throw BadRequest('Invalid health type');
|
|
117
|
+
}
|
|
114
118
|
try {
|
|
115
119
|
if (stopping) {
|
|
116
|
-
throw new Error();
|
|
117
|
-
}
|
|
118
|
-
const validateType = (type) => {
|
|
119
|
-
return healthTypes.includes(type);
|
|
120
|
-
};
|
|
121
|
-
const type = req.params.type || req.query.type || 'ready';
|
|
122
|
-
if (!validateType(type)) {
|
|
123
|
-
throw BadRequest('Invalid health type');
|
|
120
|
+
throw new Error('The server is shutting down');
|
|
124
121
|
}
|
|
125
122
|
const meta = await options.healthCheck?.(type);
|
|
126
123
|
return { status: 'ok', type, meta, timestamp };
|
|
@@ -132,31 +129,36 @@ export function createExpressServer(app, options = {}) {
|
|
|
132
129
|
}));
|
|
133
130
|
app.set('__healthAttached', true);
|
|
134
131
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
resolve();
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
},
|
|
148
|
-
stop() {
|
|
149
|
-
return server.stop();
|
|
150
|
-
},
|
|
151
|
-
terminate() {
|
|
152
|
-
return onSignal('SIGTERM');
|
|
153
|
-
},
|
|
154
|
-
get port() {
|
|
155
|
-
const address = server.address();
|
|
156
|
-
if (address == null || typeof address === 'string') {
|
|
132
|
+
const expressServer = server;
|
|
133
|
+
Object.defineProperty(expressServer, 'port', {
|
|
134
|
+
get() {
|
|
135
|
+
const address = this.address();
|
|
136
|
+
if (address == null) {
|
|
137
|
+
throw new Error('address is null, server not listening?');
|
|
138
|
+
}
|
|
139
|
+
/* c8 ignore next 3 */
|
|
140
|
+
if (typeof address === 'string') {
|
|
157
141
|
throw new Error('address was a string, should not happen');
|
|
158
142
|
}
|
|
159
143
|
return address.port;
|
|
160
144
|
},
|
|
145
|
+
enumerable: true,
|
|
146
|
+
configurable: true,
|
|
147
|
+
});
|
|
148
|
+
expressServer.start = function start() {
|
|
149
|
+
const port = app.get('port') ?? envPort(0);
|
|
150
|
+
return new Promise((resolve) => {
|
|
151
|
+
server.listen(port, () => {
|
|
152
|
+
logger.info('app listening', {
|
|
153
|
+
port: expressServer.port,
|
|
154
|
+
duration: appTimer.getDuration(),
|
|
155
|
+
});
|
|
156
|
+
resolve();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
expressServer.terminate = function terminate() {
|
|
161
|
+
return onSignal('SIGTERM');
|
|
161
162
|
};
|
|
163
|
+
return expressServer;
|
|
162
164
|
}
|
package/dist/logger.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface LoggerOptions {
|
|
|
15
15
|
output?: LoggerOutputWriter;
|
|
16
16
|
}
|
|
17
17
|
export declare function setGlobalLoggerContext(getContext: () => LoggerContext): void;
|
|
18
|
+
export declare function clearGlobalLoggerContext(): void;
|
|
18
19
|
export type Logger = ReturnType<typeof createLogger>;
|
|
19
20
|
export declare function createLogger({ name, getContext, level, output, }: LoggerOptions): {
|
|
20
21
|
debug(message: string, context?: LoggerContext): void;
|