@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.
- package/dist/auth/emailAndPassword.js +4 -4
- package/dist/auth/errors.d.ts +2 -2
- package/dist/auth/errors.js +4 -4
- package/dist/auth/google.js +2 -2
- package/dist/auth/passwordless.js +1 -1
- package/dist/auth/token.js +3 -1
- package/dist/auth/tokenStorage.d.ts +16 -0
- package/dist/auth/tokenStorage.js +44 -0
- package/dist/auth/types.d.ts +1 -3
- package/dist/auth.d.ts +7 -15
- package/dist/auth.js +29 -41
- package/dist/channels.d.ts +19 -1
- package/dist/channels.js +23 -34
- package/dist/composeMiddleware.d.ts +15 -0
- package/dist/composeMiddleware.js +63 -0
- package/dist/csrf.d.ts +1 -0
- package/dist/csrf.js +6 -2
- package/dist/errors.d.ts +7 -7
- package/dist/errors.js +11 -11
- package/dist/faktory.d.ts +13 -11
- package/dist/faktory.js +6 -6
- package/dist/listen.d.ts +18 -9
- package/dist/listen.js +64 -38
- package/dist/logger.d.ts +23 -4
- package/dist/logger.js +65 -45
- package/dist/manage.d.ts +2 -2
- package/dist/manage.js +12 -12
- package/dist/middleware.d.ts +4 -2
- package/dist/middleware.js +10 -3
- package/dist/rate-limit.d.ts +14 -4
- package/dist/rate-limit.js +10 -5
- package/dist/redis.d.ts +0 -3
- package/dist/redis.js +0 -23
- package/dist/router.js +1 -1
- package/dist/routes.d.ts +5 -4
- package/dist/routes.js +34 -23
- package/dist/streams.d.ts +7 -0
- package/dist/streams.js +29 -0
- package/dist/utils.d.ts +0 -2
- package/dist/utils.js +0 -8
- package/dist/validate.d.ts +33 -12
- package/dist/validate.js +6 -1
- package/dist/vite/defineViteConfig.js +1 -0
- package/dist/vite/ssr.d.ts +20 -1
- package/dist/vite/ssr.js +6 -1
- package/dist/vitest/matchers.d.ts +4 -0
- package/dist/vitest/matchers.js +17 -2
- package/dist/vitest/testClient.d.ts +1 -0
- 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
|
-
|
|
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
|
-
|
|
13
|
-
|
|
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<
|
|
18
|
-
execute(args: Parameters<
|
|
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
|
-
|
|
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
|
-
|
|
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>({
|
|
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({
|
|
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 =
|
|
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,
|
|
56
|
+
return { job, queues };
|
|
57
57
|
}
|
|
58
58
|
export { createFaktoryWorker as createJobWorker };
|
|
59
|
-
export async function createFaktoryWorker({
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
stop()
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
23
|
+
requestsPerSocket.set(req.socket,
|
|
24
|
+
/* c8 ignore next 1 */
|
|
25
|
+
(requestsPerSocket.get(req.socket) ?? 0) + 1);
|
|
24
26
|
res.once('finish', () => {
|
|
25
|
-
|
|
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 (
|
|
37
|
-
setTimeout(destroyAll,
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
133
|
-
|
|
165
|
+
const enhanced = app;
|
|
166
|
+
enhanced.server = server;
|
|
167
|
+
Object.defineProperty(enhanced, 'port', {
|
|
134
168
|
get() {
|
|
135
|
-
|
|
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
|
-
|
|
174
|
+
enhanced.start = async function start() {
|
|
149
175
|
const port = app.get('port') ?? envPort(0);
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
183
|
+
enhanced.stop = () => {
|
|
184
|
+
return ctl.stop();
|
|
185
|
+
};
|
|
186
|
+
enhanced.terminate = function terminate() {
|
|
161
187
|
return onSignal('SIGTERM');
|
|
162
188
|
};
|
|
163
|
-
return
|
|
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
|
|
3
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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,
|
|
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
|
-
|
|
73
|
-
output.write(
|
|
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
|
-
|
|
93
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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
|
|
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:
|
|
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:
|
|
82
|
+
format: null,
|
|
80
83
|
path: options.output,
|
|
81
|
-
lint:
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
}
|
package/dist/middleware.d.ts
CHANGED
|
@@ -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):
|
|
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
|
*
|