@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.
Files changed (73) hide show
  1. package/README.md +0 -111
  2. package/dist/auth/emailAndPassword.js +2 -1
  3. package/dist/auth/google.d.ts +4 -4
  4. package/dist/auth/google.js +14 -11
  5. package/dist/auth/passwordless.js +2 -1
  6. package/dist/auth/token.js +6 -6
  7. package/dist/auth/types.d.ts +1 -1
  8. package/dist/auth.d.ts +1 -1
  9. package/dist/auth.js +17 -10
  10. package/dist/cache.d.ts +3 -0
  11. package/dist/cache.js +3 -2
  12. package/dist/channels.d.ts +6 -5
  13. package/dist/channels.js +9 -4
  14. package/dist/cli.js +1 -1
  15. package/dist/config.d.ts +66 -66
  16. package/dist/config.js +35 -36
  17. package/dist/context.d.ts +5 -2
  18. package/dist/context.js +10 -9
  19. package/dist/csrf.js +6 -6
  20. package/dist/email.d.ts +1 -1
  21. package/dist/email.js +3 -3
  22. package/dist/errors.js +1 -1
  23. package/dist/faktory.d.ts +1 -1
  24. package/dist/faktory.js +5 -15
  25. package/dist/index.d.ts +2 -2
  26. package/dist/index.js +4 -3
  27. package/dist/listen.d.ts +4 -4
  28. package/dist/listen.js +41 -43
  29. package/dist/logger.d.ts +1 -0
  30. package/dist/logger.js +11 -5
  31. package/dist/manage.d.ts +4 -6
  32. package/dist/manage.js +41 -37
  33. package/dist/middleware.d.ts +3 -0
  34. package/dist/middleware.js +24 -7
  35. package/dist/rate-limit.d.ts +1 -0
  36. package/dist/rate-limit.js +25 -3
  37. package/dist/redis.d.ts +1 -1
  38. package/dist/redis.js +4 -4
  39. package/dist/sentry.js +7 -3
  40. package/dist/setupTests.global.d.ts +2 -0
  41. package/dist/setupTests.global.js +4 -0
  42. package/dist/setupTests.js +3 -0
  43. package/dist/slack.js +2 -2
  44. package/dist/utils.d.ts +0 -13
  45. package/dist/utils.js +0 -21
  46. package/dist/validate.js +7 -4
  47. package/dist/vite/__tests__/fixtures/src/entry-server.d.ts +5 -0
  48. package/dist/vite/__tests__/fixtures/src/entry-server.js +15 -0
  49. package/dist/vite/defineViteConfig.js +11 -1
  50. package/dist/vite/index.d.ts +1 -0
  51. package/dist/vite/index.js +3 -0
  52. package/dist/vite/ssr.d.ts +1 -0
  53. package/dist/vite/ssr.js +9 -4
  54. package/dist/vitest/index.d.ts +5 -0
  55. package/dist/vitest/index.js +24 -0
  56. package/dist/vitest/matchers.d.ts +1 -1
  57. package/dist/vitest/matchers.js +6 -2
  58. package/dist/vitest/testClient.d.ts +30 -0
  59. package/dist/vitest/testClient.js +98 -0
  60. package/dist/webapi.js +4 -0
  61. package/package.json +31 -42
  62. package/dist/__tests__/__migrations__/1_a.d.ts +0 -3
  63. package/dist/__tests__/__migrations__/1_a.js +0 -9
  64. package/dist/migrate.d.ts +0 -47
  65. package/dist/migrate.js +0 -255
  66. package/dist/postgres-ts-generator.d.ts +0 -11
  67. package/dist/postgres-ts-generator.js +0 -114
  68. package/dist/postgres.d.ts +0 -71
  69. package/dist/postgres.js +0 -261
  70. package/dist/sql.d.ts +0 -33
  71. package/dist/sql.js +0 -135
  72. package/dist/vite.d.ts +0 -1
  73. package/dist/vite.js +0 -1
package/dist/config.js CHANGED
@@ -1,39 +1,38 @@
1
- import { readEnv } from './utils.js';
2
- function lazy(fn) {
3
- return { value: fn() };
4
- }
5
- const baseConfig = {
6
- CI: readEnv.boolean('CI', false),
7
- PORT: readEnv.number('PORT', 0),
8
- PUBLIC_URL: readEnv('PUBLIC_URL', ''),
9
- AUTH_TOKEN_EXPIRES_IN: readEnv.number('AUTH_TOKEN_EXPIRES', 3600 * 24 * 30), // 30 days
10
- AUTH_TEMP_TOKEN_EXPIRES_IN: readEnv.number('AUTH_TEMP_TOKEN_EXPIRES', 3600),
11
- AUTH_OTP_EXPIRES_IN: readEnv.number('AUTH_OTP_EXPIRES', 5 * 60),
12
- AUTH_SECRET: readEnv('AUTH_JWT_SECRET', ''),
13
- AUTH_COOKIE_SAME_SITE: readEnv('AUTH_COOKIE_SAME_SITE', 'strict'),
14
- REDIS_URL: readEnv('REDIS_URL', ''),
15
- REDIS_KEY_PREFIX: readEnv('REDIS_KEY_PREFIX', ''),
16
- POSTGRES_URL: readEnv('POSTGRES_URL', ''),
17
- SENTRY_DSN: readEnv('SENTRY_DSN', ''),
18
- LOG_LEVEL: readEnv('LOG_LEVEL', 'INFO'),
19
- LOG_SQL: readEnv.boolean('LOG_SQL', false),
20
- NODE_ENV: readEnv('NODE_ENV', 'development'),
21
- CSRF_SECRET: readEnv('CSRF_SECRET', ''),
22
- READINESS_PERIOD_MS: readEnv.number('READINESS_PERIOD_MS', 0),
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: readEnv('EMAIL_HOST', ''),
30
- EMAIL_PORT: readEnv('EMAIL_PORT', '587'),
31
- EMAIL_USER: readEnv('EMAIL_USER', ''),
32
- EMAIL_PASS: readEnv('EMAIL_PASS', ''),
33
- EMAIL_SENDER_NAME: readEnv('EMAIL_SENDER_NAME', ''),
34
- EMAIL_SENDER_ADDRESS: readEnv('EMAIL_SENDER_ADDRESS', ''),
35
- };
36
- export const config = lazy(() => baseConfig);
37
- export function extendConfig(appConfig) {
38
- return lazy(() => Object.assign(baseConfig, appConfig));
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 useContext<T>(key: string): T | undefined;
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 setContext<T>(key: string, value: T): void;
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 REQUEST_ID = 'requestId';
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
- const store = requestContext.getStore();
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 useContext(key) {
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 setContext(key, value) {
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 useContext(REQUEST_ID);
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 useContext(ANONYMOUS_SESSION_KEY);
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 { config } from './config.js';
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.value.NODE_ENV === 'production' && !config.value.CI
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.value.CSRF_SECRET;
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
- setContext(CSRF_CONTEXT_KEY, options);
34
+ setRequestContext(CSRF_CONTEXT_KEY, options);
35
35
  }
36
36
  function useCsrfOptions() {
37
- return (useContext(CSRF_CONTEXT_KEY) || {
37
+ return (useRequestContext(CSRF_CONTEXT_KEY) || {
38
38
  exempt: true,
39
39
  cookieName: defaultCookieName(),
40
40
  sessionIdentifier: '',
package/dist/email.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as nodemailer from 'nodemailer';
2
2
  export interface EmailOptions {
3
3
  host?: string;
4
- port?: string;
4
+ port?: number;
5
5
  user?: string;
6
6
  pass?: string;
7
7
  senderName?: string;
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.value.EMAIL_HOST, port = config.value.EMAIL_PORT, user = config.value.EMAIL_USER, pass = config.value.EMAIL_PASS, senderName = config.value.EMAIL_SENDER_NAME, senderAddress = config.value.EMAIL_SENDER_ADDRESS, onEmailSent, } = {}) {
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: parseInt(port || '587', 10),
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 declare function useJobId(): string | undefined;
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
- const context = new AsyncLocalStorage();
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 context.run(new Map(), async () => {
90
- const store = context.getStore();
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 next 100 */
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
- export declare function createExpressServer(app: Application, options?: ExpressServerOptions): {
13
+ interface ExpressServer extends StoppableServer {
14
14
  start(): Promise<void>;
15
- stop(): Promise<boolean>;
15
+ port: number;
16
16
  terminate(): Promise<void>;
17
- readonly port: number;
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, startTimer } from './utils.js';
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), GRACE_PERIOD);
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(READINESS_PERIOD + 1000);
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?', validateQuery({
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
- return {
140
- start() {
141
- const port = app.get('port') ?? envPort(0);
142
- return new Promise((resolve) => {
143
- server.listen(port, () => {
144
- logger.info('app listening', {
145
- port: this.port,
146
- duration: appTimer.getDuration(),
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
- let _globalGetContext;
15
+ const _getGlobalContextFns = [];
16
16
  export function setGlobalLoggerContext(getContext) {
17
- _globalGetContext = getContext;
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
- ..._globalGetContext?.(),
31
+ ...globalContext,
26
32
  };
27
33
  }
28
- export function createLogger({ name, getContext, level = config.value.LOG_LEVEL, output = process.stdout, }) {
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 { program } from 'commander';
1
+ import { Command } from 'commander';
2
2
  import { type Express } from 'express';
3
3
  import { CLI } from './cli.js';
4
- type Command = typeof program.command;
5
- export declare function registerManagementCommand(register: (command: Command, cli: CLI) => void): void;
6
- export declare function runManagementCommands(): Promise<void>;
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;