@devmoods/express-extras 0.65.1 → 0.66.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 (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 +64 -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,48 @@ 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
+ if (options.trustProxy ?? true) {
109
+ app.set('trust proxy', options.trustProxy);
110
+ }
78
111
  let stopping = false;
79
112
  async function onSignal(signal) {
80
113
  if (stopping) {
@@ -86,7 +119,7 @@ export function createExpressServer(app, options = {}) {
86
119
  logger.info(`app shutting down due to ${signal}`);
87
120
  await delay(config.READINESS_PERIOD_MS + 1000);
88
121
  await options.beforeShutdown?.();
89
- const gracefully = await server.stop();
122
+ const gracefully = await ctl.stop();
90
123
  await options.onShutdown?.();
91
124
  logger.info('app shut down', {
92
125
  duration: timer.getDuration(),
@@ -106,7 +139,7 @@ export function createExpressServer(app, options = {}) {
106
139
  }
107
140
  const healthTypes = ['live', 'ready'];
108
141
  if (!app.get('__healthAttached')) {
109
- app.get('/health/:type?', route(async (req) => {
142
+ app.get('/health/:type?', before(), route(async (req) => {
110
143
  const timestamp = new Date().toISOString();
111
144
  const validateType = (type) => {
112
145
  return healthTypes.includes(type);
@@ -116,7 +149,7 @@ export function createExpressServer(app, options = {}) {
116
149
  throw BadRequest('Invalid health type');
117
150
  }
118
151
  try {
119
- if (stopping) {
152
+ if (stopping && type === 'ready') {
120
153
  throw new Error('The server is shutting down');
121
154
  }
122
155
  const meta = await options.healthCheck?.(type);
@@ -129,36 +162,29 @@ export function createExpressServer(app, options = {}) {
129
162
  }));
130
163
  app.set('__healthAttached', true);
131
164
  }
132
- const expressServer = server;
133
- Object.defineProperty(expressServer, 'port', {
165
+ const enhanced = app;
166
+ enhanced.server = server;
167
+ Object.defineProperty(enhanced, 'port', {
134
168
  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;
169
+ return ctl.port;
144
170
  },
145
171
  enumerable: true,
146
172
  configurable: true,
147
173
  });
148
- expressServer.start = function start() {
174
+ enhanced.start = async function start() {
149
175
  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
- });
176
+ await options.onStart?.();
177
+ await ctl.start(port);
178
+ logger.info('app listening', {
179
+ port: ctl.port,
180
+ duration: appTimer.getDuration(),
158
181
  });
159
182
  };
160
- expressServer.terminate = function terminate() {
183
+ enhanced.stop = () => {
184
+ return ctl.stop();
185
+ };
186
+ enhanced.terminate = function terminate() {
161
187
  return onSignal('SIGTERM');
162
188
  };
163
- return expressServer;
189
+ return enhanced;
164
190
  }
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
  *