@devmoods/express-extras 0.64.0 → 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 +6 -5
- package/dist/channels.js +9 -4
- 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 +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 +7 -3
- 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
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
|
@@ -10,10 +10,10 @@ interface ExpressServerOptions {
|
|
|
10
10
|
beforeShutdown?(): Promise<void> | void;
|
|
11
11
|
healthCheck?(type?: 'live' | 'ready'): Promise<unknown>;
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
interface ExpressServer extends StoppableServer {
|
|
14
14
|
start(): Promise<void>;
|
|
15
|
-
|
|
15
|
+
port: number;
|
|
16
16
|
terminate(): Promise<void>;
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
}
|
|
18
|
+
export declare function createExpressServer(app: Application, options?: ExpressServerOptions): ExpressServer;
|
|
19
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,7 +84,7 @@ 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(
|
|
87
|
+
await delay(config.READINESS_PERIOD_MS + 1000);
|
|
90
88
|
await options.beforeShutdown?.();
|
|
91
89
|
const gracefully = await server.stop();
|
|
92
90
|
await options.onShutdown?.();
|
|
@@ -108,23 +106,18 @@ export function createExpressServer(app, options = {}) {
|
|
|
108
106
|
}
|
|
109
107
|
const healthTypes = ['live', 'ready'];
|
|
110
108
|
if (!app.get('__healthAttached')) {
|
|
111
|
-
app.get('/health/:type?',
|
|
112
|
-
type: 'object',
|
|
113
|
-
properties: {
|
|
114
|
-
type: { type: 'string', enum: ['live', 'ready'] },
|
|
115
|
-
},
|
|
116
|
-
}), route(async (req) => {
|
|
109
|
+
app.get('/health/:type?', route(async (req) => {
|
|
117
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
|
+
}
|
|
118
118
|
try {
|
|
119
119
|
if (stopping) {
|
|
120
|
-
throw new Error();
|
|
121
|
-
}
|
|
122
|
-
const validateType = (type) => {
|
|
123
|
-
return healthTypes.includes(type);
|
|
124
|
-
};
|
|
125
|
-
const type = req.params.type || req.query.type || 'ready';
|
|
126
|
-
if (!validateType(type)) {
|
|
127
|
-
throw BadRequest('Invalid health type');
|
|
120
|
+
throw new Error('The server is shutting down');
|
|
128
121
|
}
|
|
129
122
|
const meta = await options.healthCheck?.(type);
|
|
130
123
|
return { status: 'ok', type, meta, timestamp };
|
|
@@ -136,31 +129,36 @@ export function createExpressServer(app, options = {}) {
|
|
|
136
129
|
}));
|
|
137
130
|
app.set('__healthAttached', true);
|
|
138
131
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
resolve();
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
},
|
|
152
|
-
stop() {
|
|
153
|
-
return server.stop();
|
|
154
|
-
},
|
|
155
|
-
terminate() {
|
|
156
|
-
return onSignal('SIGTERM');
|
|
157
|
-
},
|
|
158
|
-
get port() {
|
|
159
|
-
const address = server.address();
|
|
160
|
-
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') {
|
|
161
141
|
throw new Error('address was a string, should not happen');
|
|
162
142
|
}
|
|
163
143
|
return address.port;
|
|
164
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');
|
|
165
162
|
};
|
|
163
|
+
return expressServer;
|
|
166
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;
|
package/dist/logger.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as os from 'node:os';
|
|
2
|
+
import { config } from '@devmoods/config';
|
|
2
3
|
import * as Sentry from '@sentry/node';
|
|
3
|
-
import { config } from './config.js';
|
|
4
4
|
import { useAnonymousSession, useRequestId } from './context.js';
|
|
5
5
|
const LEVELS = {
|
|
6
6
|
TRACE: 10,
|
|
@@ -12,20 +12,26 @@ const LEVELS = {
|
|
|
12
12
|
};
|
|
13
13
|
const LEVELS_TO_NAME = Object.fromEntries(Object.entries(LEVELS).map(([name, level]) => [level, name.toLowerCase()]));
|
|
14
14
|
const _loggers = new Map();
|
|
15
|
-
|
|
15
|
+
const _getGlobalContextFns = [];
|
|
16
16
|
export function setGlobalLoggerContext(getContext) {
|
|
17
|
-
|
|
17
|
+
_getGlobalContextFns.push(getContext);
|
|
18
|
+
}
|
|
19
|
+
export function clearGlobalLoggerContext() {
|
|
20
|
+
_getGlobalContextFns.length = 0;
|
|
18
21
|
}
|
|
19
22
|
function defaultGetContext() {
|
|
20
23
|
const nameArgIndex = process.argv.indexOf('--name');
|
|
24
|
+
const globalContext = _getGlobalContextFns.reduce((acc, fn) => {
|
|
25
|
+
return Object.assign(acc, fn());
|
|
26
|
+
}, {});
|
|
21
27
|
return {
|
|
22
28
|
app: nameArgIndex > -1 ? process.argv.at(nameArgIndex + 1) : undefined,
|
|
23
29
|
requestId: useRequestId(),
|
|
24
30
|
sessionId: useAnonymousSession(),
|
|
25
|
-
...
|
|
31
|
+
...globalContext,
|
|
26
32
|
};
|
|
27
33
|
}
|
|
28
|
-
export function createLogger({ name, getContext, level = config.
|
|
34
|
+
export function createLogger({ name, getContext, level = config.LOG_LEVEL, output = process.stdout, }) {
|
|
29
35
|
if (_loggers.has(name)) {
|
|
30
36
|
throw new Error(`A logger named ${name} already exists`);
|
|
31
37
|
}
|
package/dist/manage.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Command } from 'commander';
|
|
2
2
|
import { type Express } from 'express';
|
|
3
3
|
import { CLI } from './cli.js';
|
|
4
|
-
|
|
5
|
-
export declare function
|
|
6
|
-
export declare function
|
|
7
|
-
export declare function registerDefaultCommands(app: Express | null): void;
|
|
8
|
-
export {};
|
|
4
|
+
export declare function registerManagementCommand(register: (command: Command['command'], cli: CLI) => void, program?: Command): void;
|
|
5
|
+
export declare function runManagementCommands(program?: Command): Promise<void>;
|
|
6
|
+
export declare function registerDefaultCommands(app: Express | null, program?: Command): void;
|