@devmoods/express-extras 0.65.1 → 0.66.1

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 (49) hide show
  1. package/dist/auth/emailAndPassword.js +4 -4
  2. package/dist/auth/errors.d.ts +2 -2
  3. package/dist/auth/errors.js +4 -4
  4. package/dist/auth/google.js +2 -2
  5. package/dist/auth/passwordless.js +1 -1
  6. package/dist/auth/token.js +3 -1
  7. package/dist/auth/tokenStorage.d.ts +16 -0
  8. package/dist/auth/tokenStorage.js +44 -0
  9. package/dist/auth/types.d.ts +1 -3
  10. package/dist/auth.d.ts +7 -15
  11. package/dist/auth.js +29 -41
  12. package/dist/channels.d.ts +19 -1
  13. package/dist/channels.js +23 -34
  14. package/dist/composeMiddleware.d.ts +15 -0
  15. package/dist/composeMiddleware.js +63 -0
  16. package/dist/csrf.d.ts +1 -0
  17. package/dist/csrf.js +6 -2
  18. package/dist/errors.d.ts +7 -7
  19. package/dist/errors.js +11 -11
  20. package/dist/faktory.d.ts +13 -11
  21. package/dist/faktory.js +6 -6
  22. package/dist/listen.d.ts +18 -9
  23. package/dist/listen.js +65 -38
  24. package/dist/logger.d.ts +23 -4
  25. package/dist/logger.js +65 -45
  26. package/dist/manage.d.ts +2 -2
  27. package/dist/manage.js +12 -12
  28. package/dist/middleware.d.ts +4 -2
  29. package/dist/middleware.js +10 -3
  30. package/dist/rate-limit.d.ts +14 -4
  31. package/dist/rate-limit.js +10 -5
  32. package/dist/redis.d.ts +0 -3
  33. package/dist/redis.js +0 -23
  34. package/dist/router.js +1 -1
  35. package/dist/routes.d.ts +5 -4
  36. package/dist/routes.js +34 -23
  37. package/dist/streams.d.ts +7 -0
  38. package/dist/streams.js +29 -0
  39. package/dist/utils.d.ts +0 -2
  40. package/dist/utils.js +0 -8
  41. package/dist/validate.d.ts +33 -12
  42. package/dist/validate.js +6 -1
  43. package/dist/vite/defineViteConfig.js +1 -0
  44. package/dist/vite/ssr.d.ts +20 -1
  45. package/dist/vite/ssr.js +6 -1
  46. package/dist/vitest/matchers.d.ts +4 -0
  47. package/dist/vitest/matchers.js +17 -2
  48. package/dist/vitest/testClient.d.ts +1 -0
  49. package/package.json +18 -20
package/dist/errors.js CHANGED
@@ -1,24 +1,24 @@
1
1
  import { STATUS_CODES } from 'node:http';
2
2
  import { DoesNotExistError, IntegrityError } from '@devmoods/postgres';
3
3
  import * as Sentry from '@sentry/node';
4
- import { errors } from 'compose-middleware';
5
4
  import {} from 'express';
6
5
  import { InvalidToken } from './auth/errors.js';
6
+ import { errors } from './composeMiddleware.js';
7
7
  import { getLogger } from './logger.js';
8
8
  export class ApiError extends Error {
9
9
  status;
10
10
  payload;
11
11
  }
12
- export const BadRequest = (reason) => makeError(400, 'Invalid Params', { reason });
13
- export const Forbidden = (detail = '') => makeError(403, detail);
14
- export const NotFound = (detail = 'Could not find resource') => makeError(404, detail);
15
- export const Unauthorized = (detail = 'Unauthorized') => makeError(401, detail);
16
- export const Conflict = (detail = '') => makeError(409, detail);
12
+ export const BadRequest = (reason, options) => makeError(400, 'Invalid Params', { reason }, options);
13
+ export const Forbidden = (detail = '', options) => makeError(403, detail, {}, options);
14
+ export const NotFound = (detail = 'Could not find resource', options) => makeError(404, detail, {}, options);
15
+ export const Unauthorized = (detail = 'Unauthorized', options) => makeError(401, detail, {}, options);
16
+ export const Conflict = (detail = '', options) => makeError(409, detail, {}, options);
17
17
  /**
18
18
  * Create a new ApiError instance
19
19
  */
20
- export function makeError(status, detail, payload) {
21
- const error = new ApiError(detail || STATUS_CODES[status]);
20
+ export function makeError(status, detail, payload, options) {
21
+ const error = new ApiError(detail || STATUS_CODES[status], options);
22
22
  error.status = status;
23
23
  error.payload = payload;
24
24
  return error;
@@ -46,13 +46,13 @@ export function mapToProblem(error) {
46
46
  }
47
47
  function convertToAPIError(error) {
48
48
  if (error instanceof IntegrityError) {
49
- return Conflict(error.message);
49
+ return Conflict(error.message, { cause: error });
50
50
  }
51
51
  if (error instanceof DoesNotExistError) {
52
- return NotFound(error.message);
52
+ return NotFound(error.message, { cause: error });
53
53
  }
54
54
  if (error instanceof InvalidToken) {
55
- return Unauthorized(error.message);
55
+ return Unauthorized(error.message, { cause: error });
56
56
  }
57
57
  return error;
58
58
  }
package/dist/faktory.d.ts CHANGED
@@ -1,33 +1,35 @@
1
1
  import { create, type WorkerOptions } from 'faktory-worker';
2
- export interface JobsOptions {
3
- queueName: string;
2
+ export interface JobsOptions<QueueName extends string> {
3
+ queues: QueueName[];
4
4
  faktory?: typeof create;
5
5
  }
6
6
  export type AnyJobContext = Record<string, any>;
7
- export interface JobOptions {
7
+ export interface JobOptions<QueueName extends string> {
8
8
  retry?: number;
9
9
  priority?: number;
10
10
  at?: Date | string;
11
+ queue?: QueueName;
11
12
  }
12
- export declare function createJobs<GlobalContext extends AnyJobContext>({ queueName, faktory: createFactory, }: JobsOptions): {
13
- job: <Arguments extends Record<string, any>, Fn extends (input: Arguments, ctx: GlobalContext) => undefined | Promise<any>>(optionsOrFn: JobOptions | Fn, fnOrUndefined?: Fn) => {
13
+ type JobFn<Arguments, Context extends AnyJobContext> = (input: Arguments, ctx: Context) => undefined | Promise<any>;
14
+ export declare function createJobs<QueueName extends string, GlobalContext extends AnyJobContext>({ queues, faktory: createFactory }: JobsOptions<QueueName>): {
15
+ job: <Arguments>(optionsOrFn: JobOptions<QueueName> | JobFn<Arguments, GlobalContext>, fnOrUndefined?: JobFn<Arguments, GlobalContext>) => {
14
16
  $job: boolean;
15
17
  jobName: string;
16
- options: JobOptions;
17
- delay(args?: Parameters<Fn>[0]): Promise<`${string}-${string}-${string}-${string}-${string}`>;
18
- execute(args: Parameters<Fn>[0], context: GlobalContext): Promise<any>;
18
+ options: JobOptions<QueueName>;
19
+ delay(args?: Parameters<JobFn<Arguments, GlobalContext>>[0]): Promise<`${string}-${string}-${string}-${string}-${string}`>;
20
+ execute(args: Parameters<JobFn<Arguments, GlobalContext>>[0], context: GlobalContext): Promise<any>;
19
21
  };
20
- queueName: string;
22
+ queues: QueueName[];
21
23
  };
22
24
  export type Job = ReturnType<ReturnType<typeof createJobs>['job']>;
23
25
  export interface FaktoryWorkerOptions<GlobalContext> {
24
26
  jobs: unknown[];
25
- queueName: string;
27
+ queues: string[];
26
28
  faktory?: typeof create;
27
29
  jobContext?: GlobalContext;
28
30
  }
29
31
  export { createFaktoryWorker as createJobWorker };
30
- export declare function createFaktoryWorker<GlobalContext>({ queueName, jobs, faktory: createFactory, jobContext, }: FaktoryWorkerOptions<GlobalContext>): Promise<{
32
+ export declare function createFaktoryWorker<GlobalContext>({ queues, jobs, faktory: createFactory, jobContext, }: FaktoryWorkerOptions<GlobalContext>): Promise<{
31
33
  start(workerOptions?: WorkerOptions): Promise<void>;
32
34
  stop(): Promise<void>;
33
35
  }>;
package/dist/faktory.js CHANGED
@@ -4,7 +4,7 @@ import { create } from 'faktory-worker';
4
4
  import { JOB_ID_CONTEXT_KEY, requestContext, setRequestContext, } from './context.js';
5
5
  import { getLogger } from './logger.js';
6
6
  const logger = getLogger('faktory');
7
- export function createJobs({ queueName, faktory: createFactory = create, }) {
7
+ export function createJobs({ queues, faktory: createFactory = create }) {
8
8
  const faktory = createFactory();
9
9
  function job(optionsOrFn, fnOrUndefined) {
10
10
  const fn = typeof optionsOrFn === 'function' ? optionsOrFn : fnOrUndefined;
@@ -26,7 +26,7 @@ export function createJobs({ queueName, faktory: createFactory = create, }) {
26
26
  const client = await faktory.connect();
27
27
  const j = client.job(jobName, args);
28
28
  j.jid = jid;
29
- j.queue = queueName;
29
+ j.queue = options.queue || queues[0];
30
30
  Object.assign(j, options);
31
31
  await j.push();
32
32
  await client.close();
@@ -53,10 +53,10 @@ export function createJobs({ queueName, faktory: createFactory = create, }) {
53
53
  },
54
54
  };
55
55
  }
56
- return { job, queueName };
56
+ return { job, queues };
57
57
  }
58
58
  export { createFaktoryWorker as createJobWorker };
59
- export async function createFaktoryWorker({ queueName, jobs, faktory: createFactory = create, jobContext, }) {
59
+ export async function createFaktoryWorker({ queues, jobs, faktory: createFactory = create, jobContext, }) {
60
60
  const faktory = createFactory();
61
61
  const filter = (j) => !!Object.prototype.hasOwnProperty.call(j, '$job');
62
62
  const availableJobs = jobs.filter(filter);
@@ -69,7 +69,7 @@ export async function createFaktoryWorker({ queueName, jobs, faktory: createFact
69
69
  async start(workerOptions = {}) {
70
70
  try {
71
71
  logger.info('faktory started', {
72
- queueName,
72
+ queues,
73
73
  availableJobs: availableJobs.map((j) => j.jobName),
74
74
  });
75
75
  faktory.use(async (ctx, next) => {
@@ -91,7 +91,7 @@ export async function createFaktoryWorker({ queueName, jobs, faktory: createFact
91
91
  });
92
92
  });
93
93
  await faktory.work({
94
- queues: [queueName],
94
+ queues,
95
95
  ...workerOptions,
96
96
  });
97
97
  }
package/dist/listen.d.ts CHANGED
@@ -1,19 +1,28 @@
1
1
  import { type Server } from 'node:http';
2
2
  import { type Application } from 'express';
3
- interface StoppableServer extends Server {
4
- inspect(): Record<string, any>;
5
- stop(): Promise<boolean>;
6
- }
7
- export declare function stoppable(server: Server, grace?: number): StoppableServer;
8
- interface ExpressServerOptions {
3
+ export declare function stoppable(server: Server, gracePeriodMs?: number): {
4
+ start: (port?: number) => Promise<void>;
5
+ stop: () => Promise<boolean>;
6
+ inspect: () => {
7
+ sockets: number;
8
+ requests: number;
9
+ };
10
+ readonly port: number;
11
+ };
12
+ export interface ExpressServerOptions {
13
+ trustProxy?: boolean | number | string;
14
+ onStart?(): Promise<void> | void;
9
15
  onShutdown?(): Promise<void> | void;
10
16
  beforeShutdown?(): Promise<void> | void;
11
17
  healthCheck?(type?: 'live' | 'ready'): Promise<unknown>;
18
+ gracePeriodMs?: number;
12
19
  }
13
- interface ExpressServer extends StoppableServer {
20
+ export interface EnhancedApplication extends Application {
14
21
  start(): Promise<void>;
22
+ stop(): Promise<boolean>;
15
23
  port: number;
16
24
  terminate(): Promise<void>;
25
+ server: Server;
17
26
  }
18
- export declare function createExpressServer(app: Application, options?: ExpressServerOptions): ExpressServer;
19
- export {};
27
+ export declare const impress: (options?: ExpressServerOptions) => EnhancedApplication;
28
+ export declare function createExpressServer(app: Application, options?: ExpressServerOptions): EnhancedApplication;
package/dist/listen.js CHANGED
@@ -4,14 +4,14 @@ import {} from 'node:net';
4
4
  import { config } from '@devmoods/config';
5
5
  import { delay } from '@devmoods/fetch';
6
6
  import { startTimer } from '@devmoods/observability';
7
- import {} from 'express';
7
+ import express, {} from 'express';
8
8
  import { BadRequest } from './errors.js';
9
9
  import { getLogger } from './logger.js';
10
- import { route } from './middleware.js';
10
+ import { before, route } from './middleware.js';
11
11
  import { envPort } from './utils.js';
12
12
  const logger = getLogger('http');
13
13
  const appTimer = startTimer();
14
- export function stoppable(server, grace = Infinity) {
14
+ export function stoppable(server, gracePeriodMs = Infinity) {
15
15
  let stopped = false;
16
16
  let gracefully = true;
17
17
  const requestsPerSocket = new Map();
@@ -20,9 +20,12 @@ export function stoppable(server, grace = Infinity) {
20
20
  socket.once('close', () => requestsPerSocket.delete(socket));
21
21
  };
22
22
  const onRequest = (req, res) => {
23
- requestsPerSocket.set(req.socket, requestsPerSocket.get(req.socket) + 1);
23
+ requestsPerSocket.set(req.socket,
24
+ /* c8 ignore next 1 */
25
+ (requestsPerSocket.get(req.socket) ?? 0) + 1);
24
26
  res.once('finish', () => {
25
- const pending = requestsPerSocket.get(req.socket) - 1;
27
+ /* c8 ignore next 1 */
28
+ const pending = Math.max(0, (requestsPerSocket.get(req.socket) ?? 0) - 1);
26
29
  requestsPerSocket.set(req.socket, pending);
27
30
  if (stopped && pending === 0) {
28
31
  req.socket.end();
@@ -33,8 +36,8 @@ export function stoppable(server, grace = Infinity) {
33
36
  return new Promise((resolve, reject) => {
34
37
  setImmediate(() => {
35
38
  stopped = true;
36
- if (grace < Infinity) {
37
- setTimeout(destroyAll, grace).unref();
39
+ if (gracePeriodMs < Infinity) {
40
+ setTimeout(destroyAll, gracePeriodMs).unref();
38
41
  }
39
42
  server.close((e) => {
40
43
  if (e) {
@@ -63,18 +66,49 @@ export function stoppable(server, grace = Infinity) {
63
66
  }
64
67
  server.on('connection', onConnection);
65
68
  server.on('request', onRequest);
66
- // @ts-expect-error attach stop
67
- server.stop = stop;
68
- // @ts-expect-error attach inspect
69
- server.inspect = () => {
69
+ const getPort = () => {
70
+ const address = server.address();
71
+ if (address == null) {
72
+ throw new Error('address is null, server not listening?');
73
+ }
74
+ /* c8 ignore next 3 */
75
+ if (typeof address === 'string') {
76
+ throw new Error('address was a string, should not happen');
77
+ }
78
+ return address.port;
79
+ };
80
+ const start = (port = 0) => {
81
+ return new Promise((resolve) => {
82
+ server.listen(port, () => {
83
+ resolve();
84
+ });
85
+ });
86
+ };
87
+ const inspect = () => {
70
88
  return {
71
89
  sockets: requestsPerSocket.size,
90
+ requests: Array.from(requestsPerSocket.values()).reduce((acc, curr) => acc + curr, 0),
72
91
  };
73
92
  };
74
- return server;
93
+ return {
94
+ start,
95
+ stop,
96
+ inspect,
97
+ get port() {
98
+ return getPort();
99
+ },
100
+ };
75
101
  }
102
+ export const impress = (options = {}) => {
103
+ return createExpressServer(express(), options);
104
+ };
76
105
  export function createExpressServer(app, options = {}) {
77
- const server = stoppable(http.createServer(app), config.GRACE_PERIOD_MS);
106
+ const server = http.createServer(app);
107
+ const ctl = stoppable(server, options.gracePeriodMs ?? config.GRACE_PERIOD_MS);
108
+ const trustProxy = options.trustProxy ?? true;
109
+ if (trustProxy) {
110
+ app.set('trust proxy', trustProxy);
111
+ }
78
112
  let stopping = false;
79
113
  async function onSignal(signal) {
80
114
  if (stopping) {
@@ -86,7 +120,7 @@ export function createExpressServer(app, options = {}) {
86
120
  logger.info(`app shutting down due to ${signal}`);
87
121
  await delay(config.READINESS_PERIOD_MS + 1000);
88
122
  await options.beforeShutdown?.();
89
- const gracefully = await server.stop();
123
+ const gracefully = await ctl.stop();
90
124
  await options.onShutdown?.();
91
125
  logger.info('app shut down', {
92
126
  duration: timer.getDuration(),
@@ -106,7 +140,7 @@ export function createExpressServer(app, options = {}) {
106
140
  }
107
141
  const healthTypes = ['live', 'ready'];
108
142
  if (!app.get('__healthAttached')) {
109
- app.get('/health/:type?', route(async (req) => {
143
+ app.get('/health/:type?', before(), route(async (req) => {
110
144
  const timestamp = new Date().toISOString();
111
145
  const validateType = (type) => {
112
146
  return healthTypes.includes(type);
@@ -116,7 +150,7 @@ export function createExpressServer(app, options = {}) {
116
150
  throw BadRequest('Invalid health type');
117
151
  }
118
152
  try {
119
- if (stopping) {
153
+ if (stopping && type === 'ready') {
120
154
  throw new Error('The server is shutting down');
121
155
  }
122
156
  const meta = await options.healthCheck?.(type);
@@ -129,36 +163,29 @@ export function createExpressServer(app, options = {}) {
129
163
  }));
130
164
  app.set('__healthAttached', true);
131
165
  }
132
- const expressServer = server;
133
- Object.defineProperty(expressServer, 'port', {
166
+ const enhanced = app;
167
+ enhanced.server = server;
168
+ Object.defineProperty(enhanced, 'port', {
134
169
  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') {
141
- throw new Error('address was a string, should not happen');
142
- }
143
- return address.port;
170
+ return ctl.port;
144
171
  },
145
172
  enumerable: true,
146
173
  configurable: true,
147
174
  });
148
- expressServer.start = function start() {
175
+ enhanced.start = async function start() {
149
176
  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
- });
177
+ await options.onStart?.();
178
+ await ctl.start(port);
179
+ logger.info('app listening', {
180
+ port: ctl.port,
181
+ duration: appTimer.getDuration(),
158
182
  });
159
183
  };
160
- expressServer.terminate = function terminate() {
184
+ enhanced.stop = () => {
185
+ return ctl.stop();
186
+ };
187
+ enhanced.terminate = function terminate() {
161
188
  return onSignal('SIGTERM');
162
189
  };
163
- return expressServer;
190
+ return enhanced;
164
191
  }
package/dist/logger.d.ts CHANGED
@@ -1,6 +1,19 @@
1
+ import { type Writable } from 'node:stream';
1
2
  export type LoggerContext = Record<string, any>;
2
- export interface LoggerOutputWriter {
3
- write(chunk: any): boolean;
3
+ export interface LoggerAdapter {
4
+ captureError(error: Error | null, attributes?: Record<string, any>): void;
5
+ write(chunk: Record<string, any>): boolean;
6
+ }
7
+ export declare class JsonStreamAdapter implements LoggerAdapter {
8
+ #private;
9
+ constructor(stream: Writable);
10
+ captureError(error: Error, attributes?: Record<string, any>): void;
11
+ write(log: Record<string, any>): boolean;
12
+ }
13
+ export declare class SentryLoggerAdapter implements LoggerAdapter {
14
+ constructor();
15
+ captureError(error: Error | null, attributes?: Record<string, any>): void;
16
+ write(log: Record<string, any>): boolean;
4
17
  }
5
18
  export interface LoggerOptions {
6
19
  /**
@@ -11,13 +24,19 @@ export interface LoggerOptions {
11
24
  * Context to attach to each log line
12
25
  */
13
26
  getContext?: () => LoggerContext;
27
+ /**
28
+ * Default log level. Log lines below this level will be ignored.
29
+ */
14
30
  level?: string;
15
- output?: LoggerOutputWriter;
31
+ /**
32
+ * Adapters to write logs to. By default it logs JSON to stdout.
33
+ */
34
+ outputs?: LoggerAdapter[];
16
35
  }
17
36
  export declare function setGlobalLoggerContext(getContext: () => LoggerContext): void;
18
37
  export declare function clearGlobalLoggerContext(): void;
19
38
  export type Logger = ReturnType<typeof createLogger>;
20
- export declare function createLogger({ name, getContext, level, output, }: LoggerOptions): {
39
+ export declare function createLogger({ name, getContext, level, outputs, }: LoggerOptions): {
21
40
  debug(message: string, context?: LoggerContext): void;
22
41
  info(message: string, context?: LoggerContext): void;
23
42
  warn(message: string, context?: LoggerContext): void;
package/dist/logger.js CHANGED
@@ -1,7 +1,44 @@
1
1
  import * as os from 'node:os';
2
+ import {} from 'node:stream';
2
3
  import { config } from '@devmoods/config';
3
4
  import * as Sentry from '@sentry/node';
4
- import { useAnonymousSession, useRequestId } from './context.js';
5
+ export class JsonStreamAdapter {
6
+ #stream;
7
+ constructor(stream) {
8
+ this.#stream = stream;
9
+ }
10
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
11
+ captureError(error, attributes) { }
12
+ write(log) {
13
+ const chunk = `${JSON.stringify(log)}\n`;
14
+ return this.#stream.write(chunk);
15
+ }
16
+ }
17
+ export class SentryLoggerAdapter {
18
+ constructor() { }
19
+ captureError(error, attributes) {
20
+ /* c8 ignore start */
21
+ if (error == null) {
22
+ return;
23
+ }
24
+ if (Sentry.isInitialized()) {
25
+ Sentry.captureException(error, {
26
+ tags: attributes,
27
+ });
28
+ }
29
+ /* c8 ignore end */
30
+ }
31
+ write(log) {
32
+ const { message, ...attributes } = log;
33
+ /* c8 ignore start */
34
+ if (Sentry.isInitialized()) {
35
+ const fn = log.level;
36
+ Sentry.logger[fn](message, attributes);
37
+ }
38
+ /* c8 ignore end */
39
+ return true;
40
+ }
41
+ }
5
42
  const LEVELS = {
6
43
  TRACE: 10,
7
44
  DEBUG: 20,
@@ -19,62 +56,49 @@ export function setGlobalLoggerContext(getContext) {
19
56
  export function clearGlobalLoggerContext() {
20
57
  _getGlobalContextFns.length = 0;
21
58
  }
22
- function defaultGetContext() {
23
- const nameArgIndex = process.argv.indexOf('--name');
24
- const globalContext = _getGlobalContextFns.reduce((acc, fn) => {
25
- return Object.assign(acc, fn());
26
- }, {});
27
- return {
28
- app: nameArgIndex > -1 ? process.argv.at(nameArgIndex + 1) : undefined,
29
- requestId: useRequestId(),
30
- sessionId: useAnonymousSession(),
31
- ...globalContext,
59
+ const nameArgIndex = process.argv.indexOf('--name');
60
+ /* c8 ignore next 1 */
61
+ const app = nameArgIndex > -1 ? process.argv.at(nameArgIndex + 1) : undefined;
62
+ const hostname = os.hostname();
63
+ const pid = process.pid;
64
+ const env = process.env.NODE_ENV;
65
+ function baseContext() {
66
+ const globalContext = {
67
+ app,
68
+ hostname,
69
+ pid,
70
+ env,
32
71
  };
72
+ for (const fn of _getGlobalContextFns) {
73
+ Object.assign(globalContext, fn());
74
+ }
75
+ return globalContext;
33
76
  }
34
- export function createLogger({ name, getContext, level = config.LOG_LEVEL, output = process.stdout, }) {
77
+ export function createLogger({ name, getContext, level = config.LOG_LEVEL, outputs = [new JsonStreamAdapter(process.stdout), new SentryLoggerAdapter()], }) {
35
78
  if (_loggers.has(name)) {
36
79
  throw new Error(`A logger named ${name} already exists`);
37
80
  }
38
- const hostname = os.hostname();
39
- const pid = process.pid;
40
- const env = process.env.NODE_ENV;
41
81
  const logLevelName = level.toUpperCase();
42
82
  const logLevel = logLevelName in LEVELS
43
83
  ? LEVELS[logLevelName]
44
84
  : LEVELS.INFO;
45
85
  function getFinalContext(context) {
46
- return {
47
- ...defaultGetContext(),
48
- ...getContext?.(),
49
- ...context,
50
- };
86
+ return Object.assign(baseContext(), getContext?.(), context);
51
87
  }
52
- const sentryMapping = {
53
- [LEVELS.TRACE]: 'trace',
54
- [LEVELS.DEBUG]: 'debug',
55
- [LEVELS.INFO]: 'info',
56
- [LEVELS.WARN]: 'warn',
57
- [LEVELS.ERROR]: 'error',
58
- [LEVELS.FATAL]: 'fatal',
59
- };
60
88
  // eslint-disable-next-line @typescript-eslint/no-shadow
61
89
  function log(level, message, context) {
90
+ if (level < logLevel) {
91
+ return;
92
+ }
62
93
  const attributes = {
63
94
  level: LEVELS_TO_NAME[level],
64
- env,
65
- hostname,
66
- pid,
67
95
  name,
68
96
  ...context,
69
97
  time: new Date().toISOString(),
70
98
  v: 1,
71
99
  };
72
- if (level >= logLevel) {
73
- output.write(`${JSON.stringify({ message, ...attributes })}\n`);
74
- if (Sentry.isInitialized()) {
75
- const fn = sentryMapping[level];
76
- Sentry.logger[fn](message, attributes);
77
- }
100
+ for (const output of outputs) {
101
+ output.write({ message, ...attributes });
78
102
  }
79
103
  }
80
104
  const api = {
@@ -89,10 +113,8 @@ export function createLogger({ name, getContext, level = config.LOG_LEVEL, outpu
89
113
  },
90
114
  error(error, message, context = {}) {
91
115
  const finalContext = getFinalContext(context);
92
- if (Sentry.isInitialized()) {
93
- Sentry.captureException(error, {
94
- tags: finalContext,
95
- });
116
+ for (const output of outputs) {
117
+ output.captureError(error, finalContext);
96
118
  }
97
119
  return log(LEVELS.ERROR, message || error?.message || '', {
98
120
  ...finalContext,
@@ -101,10 +123,8 @@ export function createLogger({ name, getContext, level = config.LOG_LEVEL, outpu
101
123
  },
102
124
  fatal(error, message, context = {}) {
103
125
  const finalContext = getFinalContext(context);
104
- if (Sentry.isInitialized()) {
105
- Sentry.captureException(error, {
106
- tags: finalContext,
107
- });
126
+ for (const output of outputs) {
127
+ output.captureError(error, finalContext);
108
128
  }
109
129
  return log(LEVELS.FATAL, message || error?.message || '', {
110
130
  ...finalContext,
package/dist/manage.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Command } from 'commander';
2
- import { type Express } from 'express';
2
+ import { type Application } from 'express';
3
3
  import { CLI } from './cli.js';
4
4
  export declare function registerManagementCommand(register: (command: Command['command'], cli: CLI) => void, program?: Command): void;
5
5
  export declare function runManagementCommands(program?: Command): Promise<void>;
6
- export declare function registerDefaultCommands(app: Express | null, program?: Command): void;
6
+ export declare function registerDefaultCommands(app: Application | null, program?: Command): void;
package/dist/manage.js CHANGED
@@ -33,11 +33,13 @@ export function registerDefaultCommands(app, program = defaultProgram) {
33
33
  }
34
34
  if (options.openapi) {
35
35
  const routes = toOpenAPISpec(app);
36
+ // eslint-disable-next-line no-console
36
37
  console.log(JSON.stringify(routes, null, 2));
37
38
  return;
38
39
  }
39
40
  const routes = parseRoutes(app);
40
41
  if (options.json) {
42
+ // eslint-disable-next-line no-console
41
43
  console.log(JSON.stringify(routes, null, 2));
42
44
  return;
43
45
  }
@@ -58,6 +60,7 @@ export function registerDefaultCommands(app, program = defaultProgram) {
58
60
  .option('--json', 'Output JSON')
59
61
  .action(cli.wrap(async (options) => {
60
62
  if (options.json) {
63
+ // eslint-disable-next-line no-console
61
64
  console.log(JSON.stringify(config, null, 2));
62
65
  return;
63
66
  }
@@ -76,10 +79,11 @@ export function registerDefaultCommands(app, program = defaultProgram) {
76
79
  path: openApiFile,
77
80
  },
78
81
  output: {
79
- format: false,
82
+ format: null,
80
83
  path: options.output,
81
- lint: false,
84
+ lint: null,
82
85
  tsConfigPath: resolve('./tsconfig.json'),
86
+ importFileExtension: '.js',
83
87
  },
84
88
  plugins: [
85
89
  {
@@ -89,6 +93,7 @@ export function registerDefaultCommands(app, program = defaultProgram) {
89
93
  {
90
94
  asClass: true,
91
95
  classStructure: 'off',
96
+ /* c8 ignore next 1 */
92
97
  classNameBuilder: (name) => `${name}Service`,
93
98
  name: '@hey-api/sdk',
94
99
  },
@@ -101,16 +106,11 @@ export function registerDefaultCommands(app, program = defaultProgram) {
101
106
  .option('--output <string>', 'Output file path', 'src/types/db.gen.ts')
102
107
  .option('--connection <string>', 'Database connection string')
103
108
  .action(cli.wrap(async (options) => {
104
- try {
105
- await generateDatabaseTypes({
106
- outputPath: options.output,
107
- connectionString: options.connection || config.POSTGRES_URL,
108
- });
109
- cli.writeLine(`✅ Database types generated successfully at: ${options.output}\n`);
110
- }
111
- catch (error) {
112
- cli.writeLine(`❌ Failed to generate database types: ${error}\n`);
113
- }
109
+ await generateDatabaseTypes({
110
+ outputPath: options.output,
111
+ connectionString: options.connection || config.POSTGRES_URL,
112
+ });
113
+ cli.writeLine(`Database types generated successfully at: ${options.output}\n`);
114
114
  }));
115
115
  }, program);
116
116
  }
@@ -1,7 +1,7 @@
1
1
  import { type ServerResponse, type IncomingMessage } from 'node:http';
2
- import { compose } from 'compose-middleware';
3
2
  import { type NextFunction, type RequestHandler, type Request, type Response, json as jsonParser, urlencoded as urlencodedParser } from 'express';
4
3
  import { type createAuth } from './auth.js';
4
+ import { compose } from './composeMiddleware.js';
5
5
  import { type CsrfMiddlewareOptions } from './csrf.js';
6
6
  export { urlencodedParser, jsonParser };
7
7
  export { asyncMiddleware as route };
@@ -50,10 +50,12 @@ export interface BeforeMiddlewareOptions {
50
50
  auth?: ReturnType<typeof createAuth>;
51
51
  }
52
52
  export declare function useCspNonce(): string;
53
+ export interface BeforeMiddleware extends RequestHandler {
54
+ }
53
55
  /**
54
56
  * Register middleware that must come before the app routes.
55
57
  */
56
- export declare function before({ cookies, helmet: useHelmet, json: useJson, csrf: useCsrf, urlencoded: useUrlencoded, allowUnsafeInlineScripts, cspDirectives, auth, }?: BeforeMiddlewareOptions): import("compose-middleware").RequestHandler<any, any, void>;
58
+ export declare function before({ cookies, helmet: useHelmet, json: useJson, csrf: useCsrf, urlencoded: useUrlencoded, allowUnsafeInlineScripts, cspDirectives, auth, }?: BeforeMiddlewareOptions): BeforeMiddleware;
57
59
  /**
58
60
  * Middleware that sends a 404
59
61
  *