@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.
Files changed (75) 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 +9 -5
  13. package/dist/channels.js +115 -23
  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 +5 -4
  28. package/dist/listen.js +47 -45
  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 +3 -0
  38. package/dist/redis.js +25 -2
  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 +5 -1
  43. package/dist/slack.js +2 -2
  44. package/dist/utils.d.ts +2 -14
  45. package/dist/utils.js +2 -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 +6 -0
  55. package/dist/vitest/index.js +25 -0
  56. package/dist/vitest/matchers.d.ts +11 -0
  57. package/dist/vitest/matchers.js +65 -0
  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 +38 -43
  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/vitest.d.ts +0 -1
  73. package/dist/vite/vitest.js +0 -1
  74. package/dist/vite.d.ts +0 -1
  75. package/dist/vite.js +0 -1
package/dist/config.d.ts CHANGED
@@ -1,66 +1,66 @@
1
- export declare const config: {
2
- value: {
3
- CI: boolean;
4
- PORT: number;
5
- PUBLIC_URL: string;
6
- AUTH_TOKEN_EXPIRES_IN: number;
7
- AUTH_TEMP_TOKEN_EXPIRES_IN: number;
8
- AUTH_OTP_EXPIRES_IN: number;
9
- AUTH_SECRET: string;
10
- AUTH_COOKIE_SAME_SITE: string;
11
- REDIS_URL: string;
12
- REDIS_KEY_PREFIX: string;
13
- POSTGRES_URL: string;
14
- SENTRY_DSN: string;
15
- LOG_LEVEL: string;
16
- LOG_SQL: boolean;
17
- NODE_ENV: string;
18
- CSRF_SECRET: string;
19
- READINESS_PERIOD_MS: number;
20
- GRACE_PERIOD_MS: number;
21
- SLACK_WEBHOOK_URL: string;
22
- VITE_USE_DEV_SERVER: boolean;
23
- /**
24
- * Email configuration
25
- */
26
- EMAIL_HOST: string;
27
- EMAIL_PORT: string;
28
- EMAIL_USER: string;
29
- EMAIL_PASS: string;
30
- EMAIL_SENDER_NAME: string;
31
- EMAIL_SENDER_ADDRESS: string;
32
- };
33
- };
34
- export declare function extendConfig<T extends Record<string, unknown>>(appConfig: T): {
35
- value: {
36
- CI: boolean;
37
- PORT: number;
38
- PUBLIC_URL: string;
39
- AUTH_TOKEN_EXPIRES_IN: number;
40
- AUTH_TEMP_TOKEN_EXPIRES_IN: number;
41
- AUTH_OTP_EXPIRES_IN: number;
42
- AUTH_SECRET: string;
43
- AUTH_COOKIE_SAME_SITE: string;
44
- REDIS_URL: string;
45
- REDIS_KEY_PREFIX: string;
46
- POSTGRES_URL: string;
47
- SENTRY_DSN: string;
48
- LOG_LEVEL: string;
49
- LOG_SQL: boolean;
50
- NODE_ENV: string;
51
- CSRF_SECRET: string;
52
- READINESS_PERIOD_MS: number;
53
- GRACE_PERIOD_MS: number;
54
- SLACK_WEBHOOK_URL: string;
55
- VITE_USE_DEV_SERVER: boolean;
56
- /**
57
- * Email configuration
58
- */
59
- EMAIL_HOST: string;
60
- EMAIL_PORT: string;
61
- EMAIL_USER: string;
62
- EMAIL_PASS: string;
63
- EMAIL_SENDER_NAME: string;
64
- EMAIL_SENDER_ADDRESS: string;
65
- } & T;
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 { 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
@@ -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
- export declare function createExpressServer(app: Application, options?: ExpressServerOptions): {
13
+ interface ExpressServer extends StoppableServer {
13
14
  start(): Promise<void>;
14
- stop(): Promise<boolean>;
15
+ port: number;
15
16
  terminate(): Promise<void>;
16
- readonly port: number;
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, 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,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(READINESS_PERIOD + 1000);
90
- await server.stop();
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', { duration: timer.getDuration() });
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?', validateQuery({
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
- return {
136
- start() {
137
- const port = app.get('port') ?? envPort(0);
138
- return new Promise((resolve) => {
139
- server.listen(port, () => {
140
- logger.info('app listening', {
141
- port: this.port,
142
- duration: appTimer.getDuration(),
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;