@devmoods/express-extras 0.65.0 → 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/config.d.ts +6 -0
- package/dist/config.js +3 -0
- package/dist/context.d.ts +4 -0
- package/dist/context.js +8 -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/sentry.js +5 -3
- 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/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
|
*
|
package/dist/middleware.js
CHANGED
|
@@ -2,11 +2,11 @@ import * as crypto from 'node:crypto';
|
|
|
2
2
|
import {} from 'node:http';
|
|
3
3
|
import { config } from '@devmoods/config';
|
|
4
4
|
import { startTimer } from '@devmoods/observability';
|
|
5
|
-
import { compose } from 'compose-middleware';
|
|
6
5
|
import cookieParser from 'cookie-parser';
|
|
7
6
|
import { json as jsonParser, urlencoded as urlencodedParser, } from 'express';
|
|
8
7
|
import helmet from 'helmet';
|
|
9
8
|
import {} from './auth.js';
|
|
9
|
+
import { compose } from './composeMiddleware.js';
|
|
10
10
|
import { ANONYMOUS_SESSION_KEY, setRequestContext, useRequestContext, withRequestContext, } from './context.js';
|
|
11
11
|
import { csrf, doubleCsrfProtection, } from './csrf.js';
|
|
12
12
|
import { NotFound } from './errors.js';
|
|
@@ -17,6 +17,7 @@ export { asyncMiddleware as route };
|
|
|
17
17
|
export { compose as composeMiddleware };
|
|
18
18
|
const CSP_NONCE_KEY = 'csp-nonce';
|
|
19
19
|
export function useCspNonce() {
|
|
20
|
+
/* c8 ignore next 1 */
|
|
20
21
|
return useRequestContext(CSP_NONCE_KEY) ?? '__CSP_NOT_SET__';
|
|
21
22
|
}
|
|
22
23
|
/**
|
|
@@ -32,7 +33,7 @@ export function before({ cookies = true, helmet: useHelmet = true, json: useJson
|
|
|
32
33
|
: useCsrf
|
|
33
34
|
? csrf()
|
|
34
35
|
: undefined,
|
|
35
|
-
auth && auth.
|
|
36
|
+
auth && auth.middleware(false),
|
|
36
37
|
logRequestDuration(),
|
|
37
38
|
setCspNonce(),
|
|
38
39
|
useHelmet &&
|
|
@@ -96,6 +97,7 @@ function logRequestDuration() {
|
|
|
96
97
|
path: req.path,
|
|
97
98
|
query: req.query,
|
|
98
99
|
ip: req.ip,
|
|
100
|
+
/* c8 ignore next 5 */
|
|
99
101
|
userAgent: req.headers['user-agent'] ?? 'unknown',
|
|
100
102
|
accept: req.headers['accept'] ?? 'unknown',
|
|
101
103
|
host: req.headers['host'] ?? 'unknown',
|
|
@@ -204,6 +206,11 @@ export function asyncMiddleware(fn) {
|
|
|
204
206
|
res.status(result).end();
|
|
205
207
|
}
|
|
206
208
|
else if (typeof result !== 'undefined') {
|
|
209
|
+
// @ts-expect-error custom value
|
|
210
|
+
const successStatus = req.__successStatus;
|
|
211
|
+
if (successStatus != null) {
|
|
212
|
+
res.status(successStatus);
|
|
213
|
+
}
|
|
207
214
|
res.send(result);
|
|
208
215
|
}
|
|
209
216
|
})
|
|
@@ -221,10 +228,10 @@ export function withTransaction(wrapper) {
|
|
|
221
228
|
res.on('error', reject);
|
|
222
229
|
next();
|
|
223
230
|
});
|
|
231
|
+
/* c8 ignore next 5 */
|
|
224
232
|
});
|
|
225
233
|
}
|
|
226
234
|
catch (error) {
|
|
227
|
-
/* c8 ignore next 2 */
|
|
228
235
|
next(error);
|
|
229
236
|
}
|
|
230
237
|
};
|
package/dist/rate-limit.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { type RequestHandler, type Request } from 'express';
|
|
2
2
|
import { type RedisClient } from './redis.js';
|
|
3
|
+
export interface RateLimitMiddleware extends RequestHandler {
|
|
4
|
+
/**
|
|
5
|
+
* Clear the rate limit for a specific key
|
|
6
|
+
*/
|
|
7
|
+
clear: (key: string) => Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Clear all rate limits
|
|
10
|
+
*/
|
|
11
|
+
clearAll: () => Promise<number>;
|
|
12
|
+
}
|
|
3
13
|
export interface RateLimitOptions {
|
|
4
14
|
/**
|
|
5
15
|
* Maximum number of requests allowed within the window
|
|
@@ -17,7 +27,7 @@ export interface RateLimitOptions {
|
|
|
17
27
|
*/
|
|
18
28
|
getKey?: (req: Request) => string;
|
|
19
29
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
};
|
|
30
|
+
/**
|
|
31
|
+
* Returns a middleware for rate limiting requests.
|
|
32
|
+
*/
|
|
33
|
+
export declare function rateLimit(redis: RedisClient, { max, windowSizeInSeconds, getKey, }?: RateLimitOptions): RateLimitMiddleware;
|
package/dist/rate-limit.js
CHANGED
|
@@ -2,9 +2,14 @@ import { config } from '@devmoods/config';
|
|
|
2
2
|
import {} from 'express';
|
|
3
3
|
import { makeError } from './errors.js';
|
|
4
4
|
import {} from './redis.js';
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Returns a middleware for rate limiting requests.
|
|
7
|
+
*/
|
|
8
|
+
export function rateLimit(redis, { max = 100, windowSizeInSeconds = 60,
|
|
9
|
+
/* c8 ignore next 1 */
|
|
10
|
+
getKey = (req) => req.ip || 'unknown', } = {}) {
|
|
6
11
|
const keyPrefix = `${config.REDIS_KEY_PREFIX}rate-limit:`;
|
|
7
|
-
const
|
|
12
|
+
const middleware = async (req, res, next) => {
|
|
8
13
|
try {
|
|
9
14
|
const key = `${keyPrefix}${getKey(req)}`;
|
|
10
15
|
// Use Redis to track requests
|
|
@@ -51,7 +56,7 @@ export function rateLimit(redis, { max = 100, windowSizeInSeconds = 60, getKey =
|
|
|
51
56
|
} while (cursor !== '0');
|
|
52
57
|
return deletedCount;
|
|
53
58
|
};
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return
|
|
59
|
+
middleware.clear = clear;
|
|
60
|
+
middleware.clearAll = clearAll;
|
|
61
|
+
return middleware;
|
|
57
62
|
}
|
package/dist/redis.d.ts
CHANGED
|
@@ -5,6 +5,3 @@ export interface RedisOptions {
|
|
|
5
5
|
}
|
|
6
6
|
export type RedisClient = ReturnType<typeof createClient>;
|
|
7
7
|
export declare function createRedis({ url, onError, }?: RedisOptions): RedisClient;
|
|
8
|
-
export declare function connectRedis(redis: RedisClient, shouldContinue?: boolean): Promise<void>;
|
|
9
|
-
export declare function stopRedis(redis: RedisClient, timeout?: number): Promise<void>;
|
|
10
|
-
export declare function pingRedis(redis: RedisClient, timeout?: number): Promise<string>;
|
package/dist/redis.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { config } from '@devmoods/config';
|
|
2
2
|
import { createClient } from 'redis';
|
|
3
|
-
import { withTimeout } from './utils.js';
|
|
4
3
|
export function createRedis({ url = config.REDIS_URL, onError, } = {}) {
|
|
5
4
|
const client = createClient({ url });
|
|
6
5
|
// Always attach a listener even if
|
|
@@ -10,25 +9,3 @@ export function createRedis({ url = config.REDIS_URL, onError, } = {}) {
|
|
|
10
9
|
});
|
|
11
10
|
return client;
|
|
12
11
|
}
|
|
13
|
-
export async function connectRedis(redis, shouldContinue = true) {
|
|
14
|
-
try {
|
|
15
|
-
await withTimeout(redis.connect(), 10000);
|
|
16
|
-
}
|
|
17
|
-
catch (error) {
|
|
18
|
-
if (shouldContinue) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
throw new Error('Failed to connect to Redis', { cause: error });
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
export async function stopRedis(redis, timeout = 10000) {
|
|
25
|
-
try {
|
|
26
|
-
return await withTimeout(redis.close(), timeout);
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
return redis.destroy();
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
export function pingRedis(redis, timeout = 1000) {
|
|
33
|
-
return withTimeout(redis.ping(), timeout);
|
|
34
|
-
}
|
package/dist/router.js
CHANGED
|
@@ -22,7 +22,7 @@ export class TypedRouter {
|
|
|
22
22
|
body: validation.request,
|
|
23
23
|
query: validation.query,
|
|
24
24
|
response: validation.response,
|
|
25
|
-
}), ...middleware, asyncMiddleware(handler));
|
|
25
|
+
}, { successStatus: validation.successStatus }), ...middleware, asyncMiddleware(handler));
|
|
26
26
|
return this;
|
|
27
27
|
}
|
|
28
28
|
use(...middleware) {
|
package/dist/routes.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
export declare function createDocsRouter(app:
|
|
3
|
-
export declare function parseRoutes(app:
|
|
1
|
+
import { type Application, type RequestHandler } from 'express';
|
|
2
|
+
export declare function createDocsRouter(app: Application, extras?: Record<string, any>): RequestHandler;
|
|
3
|
+
export declare function parseRoutes(app: Application): {
|
|
4
4
|
method: string;
|
|
5
5
|
path: string;
|
|
6
6
|
params: {
|
|
@@ -15,6 +15,7 @@ export declare function parseRoutes(app: Express): {
|
|
|
15
15
|
query: {} | null;
|
|
16
16
|
body: {} | null;
|
|
17
17
|
response: {} | null;
|
|
18
|
+
successStatus: {} | null;
|
|
18
19
|
}[];
|
|
19
20
|
export interface Key {
|
|
20
21
|
name: string;
|
|
@@ -23,7 +24,7 @@ export interface Key {
|
|
|
23
24
|
}
|
|
24
25
|
export declare function extractTagsFromPath(path: string): string[];
|
|
25
26
|
export declare function convertPathToOperationId(method: string, path: string): string;
|
|
26
|
-
export declare function toOpenAPISpec(app:
|
|
27
|
+
export declare function toOpenAPISpec(app: Application, info?: {
|
|
27
28
|
title: string;
|
|
28
29
|
version: string;
|
|
29
30
|
description?: string;
|
package/dist/routes.js
CHANGED
|
@@ -28,6 +28,9 @@ export function parseRoutes(app) {
|
|
|
28
28
|
response: metadata
|
|
29
29
|
?.filter((m) => typeof m === 'object' && m != null && 'responseSchema' in m)
|
|
30
30
|
.at(-1)?.responseSchema ?? null,
|
|
31
|
+
successStatus: metadata
|
|
32
|
+
?.filter((m) => typeof m === 'object' && m != null && 'successStatus' in m)
|
|
33
|
+
.at(-1)?.successStatus ?? null,
|
|
31
34
|
};
|
|
32
35
|
});
|
|
33
36
|
}
|
|
@@ -35,6 +38,7 @@ const buildPathParamsSchema = (pathParams) => {
|
|
|
35
38
|
const required = [];
|
|
36
39
|
const params = [];
|
|
37
40
|
for (const param of pathParams) {
|
|
41
|
+
/* c8 ignore next 3 */
|
|
38
42
|
if (param.in !== 'path') {
|
|
39
43
|
continue;
|
|
40
44
|
}
|
|
@@ -54,12 +58,12 @@ const buildPathParamsSchema = (pathParams) => {
|
|
|
54
58
|
};
|
|
55
59
|
// Fork of express-route-parser
|
|
56
60
|
class ExpressPathParser {
|
|
57
|
-
|
|
61
|
+
#appPaths;
|
|
58
62
|
getPaths() {
|
|
59
|
-
return this
|
|
63
|
+
return this.#appPaths;
|
|
60
64
|
}
|
|
61
65
|
constructor(app) {
|
|
62
|
-
this
|
|
66
|
+
this.#appPaths = [];
|
|
63
67
|
const router = app._router;
|
|
64
68
|
if (router) {
|
|
65
69
|
for (const layer of router.stack) {
|
|
@@ -77,7 +81,7 @@ class ExpressPathParser {
|
|
|
77
81
|
if (!layer.route || !layer.route.stack.length) {
|
|
78
82
|
return;
|
|
79
83
|
}
|
|
80
|
-
this.
|
|
84
|
+
this.#appPaths.push(this.parseRouteLayer(layer, keys, path));
|
|
81
85
|
};
|
|
82
86
|
parseRouteLayer = (layer, keys, basePath) => {
|
|
83
87
|
const lastRequestHandler = layer.route.stack[layer.route.stack.length - 1];
|
|
@@ -98,19 +102,18 @@ class ExpressPathParser {
|
|
|
98
102
|
};
|
|
99
103
|
}
|
|
100
104
|
const pathRegexParser = (layerRegexPath, keys) => {
|
|
105
|
+
/* c8 ignore next 3 */
|
|
101
106
|
if (typeof layerRegexPath === 'string') {
|
|
102
107
|
return layerRegexPath;
|
|
103
108
|
}
|
|
104
109
|
if (layerRegexPath.fast_slash) {
|
|
105
110
|
return '';
|
|
106
111
|
}
|
|
112
|
+
/* c8 ignore next 3 */
|
|
107
113
|
if (layerRegexPath.fast_star) {
|
|
108
114
|
return '*';
|
|
109
115
|
}
|
|
110
|
-
|
|
111
|
-
if (keys && keys.length) {
|
|
112
|
-
mappedPath = mapKeysToPath(layerRegexPath, keys);
|
|
113
|
-
}
|
|
116
|
+
const mappedPath = mapKeysToPath(layerRegexPath, keys);
|
|
114
117
|
const match = layerRegexPath
|
|
115
118
|
.toString()
|
|
116
119
|
.replace('\\/?', '')
|
|
@@ -121,12 +124,13 @@ const pathRegexParser = (layerRegexPath, keys) => {
|
|
|
121
124
|
}
|
|
122
125
|
if (mappedPath) {
|
|
123
126
|
return mappedPath.slice(1);
|
|
127
|
+
/* c8 ignore next 3 */
|
|
124
128
|
}
|
|
125
129
|
return layerRegexPath.toString();
|
|
126
130
|
};
|
|
127
131
|
const mapKeysToPath = (layerRegexPath, keys) => {
|
|
128
132
|
if (!keys || keys.length === 0) {
|
|
129
|
-
|
|
133
|
+
return '';
|
|
130
134
|
}
|
|
131
135
|
let convertedSubPath = layerRegexPath.toString();
|
|
132
136
|
for (const key of keys) {
|
|
@@ -183,7 +187,7 @@ export function toOpenAPISpec(app, info = {
|
|
|
183
187
|
const paths = {};
|
|
184
188
|
const allTags = new Set();
|
|
185
189
|
for (const routeInfo of routes) {
|
|
186
|
-
const { method, path, params, query, body, response } = routeInfo;
|
|
190
|
+
const { method, path, params, query, body, response, successStatus } = routeInfo;
|
|
187
191
|
if (!path.startsWith('/api/')) {
|
|
188
192
|
continue;
|
|
189
193
|
}
|
|
@@ -202,6 +206,7 @@ export function toOpenAPISpec(app, info = {
|
|
|
202
206
|
parameters.push(...Object.keys(params.properties).map((name) => ({
|
|
203
207
|
name,
|
|
204
208
|
in: 'path',
|
|
209
|
+
/* c8 ignore next 1 */
|
|
205
210
|
required: params.required?.includes(name) ?? true,
|
|
206
211
|
schema: params.properties[name],
|
|
207
212
|
})));
|
|
@@ -209,12 +214,14 @@ export function toOpenAPISpec(app, info = {
|
|
|
209
214
|
// Add query parameters
|
|
210
215
|
if (query && typeof query === 'object' && 'properties' in query) {
|
|
211
216
|
const querySchema = query;
|
|
212
|
-
|
|
217
|
+
/* c8 ignore next 1 */
|
|
218
|
+
const properties = querySchema.properties || {};
|
|
219
|
+
parameters.push(...Object.keys(properties).map((name) => ({
|
|
213
220
|
name,
|
|
214
221
|
in: 'query',
|
|
215
222
|
required: Array.isArray(querySchema.required) &&
|
|
216
223
|
querySchema.required.includes(name),
|
|
217
|
-
schema:
|
|
224
|
+
schema: properties[name],
|
|
218
225
|
})));
|
|
219
226
|
}
|
|
220
227
|
paths[openApiPath][method.toLowerCase()] = {
|
|
@@ -231,22 +238,26 @@ export function toOpenAPISpec(app, info = {
|
|
|
231
238
|
},
|
|
232
239
|
}),
|
|
233
240
|
responses: {
|
|
234
|
-
...(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
'
|
|
240
|
-
|
|
241
|
+
...(() => {
|
|
242
|
+
const code = String(successStatus ?? 200);
|
|
243
|
+
if (response) {
|
|
244
|
+
return {
|
|
245
|
+
[code]: {
|
|
246
|
+
description: 'Successful response',
|
|
247
|
+
content: {
|
|
248
|
+
'application/json': {
|
|
249
|
+
schema: response,
|
|
250
|
+
},
|
|
241
251
|
},
|
|
242
252
|
},
|
|
243
|
-
}
|
|
253
|
+
};
|
|
244
254
|
}
|
|
245
|
-
|
|
246
|
-
|
|
255
|
+
return {
|
|
256
|
+
[code]: {
|
|
247
257
|
description: 'Successful response',
|
|
248
258
|
},
|
|
249
|
-
}
|
|
259
|
+
};
|
|
260
|
+
})(),
|
|
250
261
|
'400': {
|
|
251
262
|
description: 'Bad Request',
|
|
252
263
|
content: {
|
package/dist/sentry.js
CHANGED
|
@@ -4,11 +4,13 @@ import { nodeProfilingIntegration } from '@sentry/profiling-node';
|
|
|
4
4
|
export function sentryOptions(options = {}) {
|
|
5
5
|
return {
|
|
6
6
|
dsn: config.SENTRY_DSN,
|
|
7
|
-
tracesSampleRate:
|
|
8
|
-
profilesSampleRate:
|
|
7
|
+
tracesSampleRate: config.SENTRY_TRACES_SAMPLE_RATE,
|
|
8
|
+
profilesSampleRate: config.SENTRY_PROFILES_SAMPLE_RATE,
|
|
9
9
|
integrations: [
|
|
10
10
|
nodeProfilingIntegration(),
|
|
11
|
-
eventLoopBlockIntegration({
|
|
11
|
+
eventLoopBlockIntegration({
|
|
12
|
+
threshold: config.SENTRY_EVENT_LOOP_THRESHOLD,
|
|
13
|
+
}),
|
|
12
14
|
],
|
|
13
15
|
enableLogs: true,
|
|
14
16
|
...options,
|
package/dist/streams.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {} from 'node:stream';
|
|
2
|
+
export function writeChunk(writable, chunk, state) {
|
|
3
|
+
// If we're already draining, just enqueue.
|
|
4
|
+
if (state.draining) {
|
|
5
|
+
if (state.queue.length >= state.maxQueue) {
|
|
6
|
+
// Drop the oldest chunk to keep memory bounded.
|
|
7
|
+
state.queue.shift();
|
|
8
|
+
}
|
|
9
|
+
state.queue.push(chunk);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (writable.write(chunk)) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
// Buffer is full — start draining
|
|
16
|
+
state.draining = true;
|
|
17
|
+
const flush = () => {
|
|
18
|
+
while (state.queue.length) {
|
|
19
|
+
const next = state.queue.shift();
|
|
20
|
+
if (!writable.write(next)) {
|
|
21
|
+
state.draining = true;
|
|
22
|
+
writable.once('drain', flush);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
state.draining = false;
|
|
27
|
+
};
|
|
28
|
+
writable.once('drain', flush);
|
|
29
|
+
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type EventEmitter } from 'node:events';
|
|
2
1
|
import { withTimeout, TimeoutError, delay } from '@devmoods/fetch';
|
|
3
2
|
export { withTimeout, TimeoutError, delay };
|
|
4
3
|
export declare function readEnv(key: string): string | undefined;
|
|
@@ -10,4 +9,3 @@ export declare namespace readEnv {
|
|
|
10
9
|
declare function readEnvNumber(key: string, defaultValue: number): number;
|
|
11
10
|
declare function readEnvBoolean(key: string, defaultValue: boolean): boolean;
|
|
12
11
|
export declare const envPort: (defaultPort?: number) => number;
|
|
13
|
-
export declare function waitForEvent(emitter: EventEmitter, event: string): Promise<unknown>;
|
package/dist/utils.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import {} from 'node:events';
|
|
2
1
|
import { withTimeout, TimeoutError, delay } from '@devmoods/fetch';
|
|
3
2
|
export { withTimeout, TimeoutError, delay };
|
|
4
3
|
export function readEnv(key, defaultValue) {
|
|
@@ -15,10 +14,3 @@ function readEnvBoolean(key, defaultValue) {
|
|
|
15
14
|
return ['true', '1', 't'].includes(readEnv(key, `${defaultValue}`).toLowerCase());
|
|
16
15
|
}
|
|
17
16
|
export const envPort = (defaultPort = 3000) => readEnv.number('PORT', defaultPort);
|
|
18
|
-
export async function waitForEvent(emitter, event) {
|
|
19
|
-
return new Promise((resolve) => {
|
|
20
|
-
emitter.once(event, (...args) => {
|
|
21
|
-
resolve([args]);
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
}
|
package/dist/validate.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
-
import { type
|
|
2
|
+
import { type RequestHandler } from 'express';
|
|
3
3
|
export type ValidationSchema = {
|
|
4
4
|
type?: 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object' | 'null';
|
|
5
5
|
format?: string;
|
|
@@ -39,29 +39,50 @@ export type ValidationResult = {
|
|
|
39
39
|
isValid: false;
|
|
40
40
|
errors: ValidationErrors;
|
|
41
41
|
};
|
|
42
|
-
export interface ValidationSpec<RequestBodySchema extends StandardSchemaV1
|
|
42
|
+
export interface ValidationSpec<RequestBodySchema extends StandardSchemaV1 | undefined = undefined, ResponseBodySchema extends StandardSchemaV1 | undefined = undefined, RequestQuerySchema extends StandardSchemaV1 | undefined = undefined, RequestParamsSchema extends StandardSchemaV1 | undefined = undefined> {
|
|
43
|
+
/**
|
|
44
|
+
* Standard schema describing the HTTP request body.
|
|
45
|
+
*/
|
|
43
46
|
request?: RequestBodySchema;
|
|
47
|
+
/**
|
|
48
|
+
* Standard schema describing the return HTTP response body.
|
|
49
|
+
*/
|
|
44
50
|
response?: ResponseBodySchema;
|
|
51
|
+
/**
|
|
52
|
+
* Schema describing the shape of the `query` parameters.
|
|
53
|
+
* All values should be strings.
|
|
54
|
+
*/
|
|
45
55
|
query?: RequestQuerySchema;
|
|
56
|
+
/**
|
|
57
|
+
* Schema describing the shape of the `params` parameters.
|
|
58
|
+
*/
|
|
46
59
|
params?: RequestParamsSchema;
|
|
60
|
+
/**
|
|
61
|
+
* Expected success HTTP status code for this route (e.g., 200, 201, 204).
|
|
62
|
+
* Used by documentation generators to infer correct response code.
|
|
63
|
+
*/
|
|
64
|
+
successStatus?: number;
|
|
47
65
|
}
|
|
48
|
-
export declare function validateSchema(data: unknown, schema: StandardSchemaV1
|
|
49
|
-
export
|
|
50
|
-
query?: StandardSchemaV1<any>;
|
|
51
|
-
body?: StandardSchemaV1<any>;
|
|
52
|
-
params?: StandardSchemaV1<any>;
|
|
53
|
-
response?: StandardSchemaV1<any>;
|
|
54
|
-
}): {
|
|
55
|
-
(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
66
|
+
export declare function validateSchema(data: unknown, schema: StandardSchemaV1): Promise<ValidationResult>;
|
|
67
|
+
export interface ValidateStandardSchemaMiddleware extends RequestHandler {
|
|
56
68
|
metadata: {
|
|
57
69
|
bodySchema?: ValidationSchema;
|
|
58
70
|
querySchema?: ValidationSchema;
|
|
59
71
|
paramsSchema?: ValidationSchema;
|
|
60
72
|
responseSchema?: ValidationSchema;
|
|
73
|
+
successStatus?: number;
|
|
61
74
|
};
|
|
62
|
-
}
|
|
75
|
+
}
|
|
76
|
+
export declare function validateStandardSchema(schemas: {
|
|
77
|
+
query?: StandardSchemaV1;
|
|
78
|
+
body?: StandardSchemaV1;
|
|
79
|
+
params?: StandardSchemaV1;
|
|
80
|
+
response?: StandardSchemaV1;
|
|
81
|
+
}, extraMetadata?: {
|
|
82
|
+
successStatus?: number;
|
|
83
|
+
}): ValidateStandardSchemaMiddleware;
|
|
63
84
|
type WithJsonSchema<T> = T & {
|
|
64
85
|
toJsonSchema: () => ValidationSchema;
|
|
65
86
|
};
|
|
66
|
-
export declare function jsonSchema(schema: ValidationSchema): WithJsonSchema<StandardSchemaV1<
|
|
87
|
+
export declare function jsonSchema<Input>(schema: ValidationSchema): WithJsonSchema<StandardSchemaV1<Input>>;
|
|
67
88
|
export {};
|
package/dist/validate.js
CHANGED
|
@@ -47,9 +47,13 @@ export async function validateSchema(data, schema) {
|
|
|
47
47
|
}
|
|
48
48
|
return { isValid: true, errors: null };
|
|
49
49
|
}
|
|
50
|
-
export function validateStandardSchema(schemas) {
|
|
50
|
+
export function validateStandardSchema(schemas, extraMetadata) {
|
|
51
51
|
const middleware = async (req, res, next) => {
|
|
52
52
|
try {
|
|
53
|
+
if (extraMetadata?.successStatus != null) {
|
|
54
|
+
// @ts-expect-error custom value
|
|
55
|
+
req.__successStatus = extraMetadata.successStatus;
|
|
56
|
+
}
|
|
53
57
|
for (const [key, schema] of Object.entries(schemas)) {
|
|
54
58
|
if (key === 'response') {
|
|
55
59
|
continue;
|
|
@@ -81,6 +85,7 @@ export function validateStandardSchema(schemas) {
|
|
|
81
85
|
if (schemas.response) {
|
|
82
86
|
metadata.responseSchema = toJsonSchema(schemas.response);
|
|
83
87
|
}
|
|
88
|
+
Object.assign(metadata, extraMetadata);
|
|
84
89
|
middleware.metadata = metadata;
|
|
85
90
|
Object.defineProperty(middleware, 'name', {
|
|
86
91
|
value: validateStandardSchema.name,
|
package/dist/vite/ssr.d.ts
CHANGED
|
@@ -1,7 +1,26 @@
|
|
|
1
|
+
import { type RequestHandler } from 'express';
|
|
1
2
|
export interface SsrMiddlewareOptions<InitialData = unknown> {
|
|
3
|
+
/**
|
|
4
|
+
* Root directory where Vite will look up files.
|
|
5
|
+
*/
|
|
2
6
|
root?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Function to get the server side data for the request.
|
|
9
|
+
*/
|
|
3
10
|
getInitialData?: (req: Request) => Promise<InitialData>;
|
|
11
|
+
/**
|
|
12
|
+
* Whether or not to use the dev server.
|
|
13
|
+
*
|
|
14
|
+
* Uses `config.VITE_USE_DEV_SERVER` if not provided.
|
|
15
|
+
*/
|
|
4
16
|
useDevServer?: boolean;
|
|
5
17
|
}
|
|
6
|
-
export
|
|
18
|
+
export interface SsrMiddleware extends RequestHandler {
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Returns a middleware for setting up SSR using Vite middleware mode.
|
|
22
|
+
*
|
|
23
|
+
* See more: https://vite.dev/guide/ssr
|
|
24
|
+
*/
|
|
25
|
+
export declare function createSsrMiddleware<InitialData = unknown>({ root, getInitialData, useDevServer, }?: SsrMiddlewareOptions<InitialData>): Promise<SsrMiddleware>;
|
|
7
26
|
export declare function replaceSsrPlaceholders(template: string, values: Record<string, unknown>): string;
|