@devmoods/express-extras 0.54.1 → 0.56.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/token.d.ts +1 -1
- package/dist/auth.d.ts +2 -2
- package/dist/auth.js +2 -0
- package/dist/channels.d.ts +3 -2
- package/dist/channels.js +51 -46
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -0
- package/dist/errors.d.ts +2 -1
- package/dist/errors.js +5 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/manage.js +3 -2
- package/dist/middleware.d.ts +3 -3
- package/dist/middleware.js +3 -0
- package/dist/migrate.js +5 -1
- package/dist/postgres.d.ts +1 -1
- package/dist/postgres.js +6 -7
- package/dist/router.d.ts +40 -0
- package/dist/router.js +37 -0
- package/dist/routes.d.ts +14 -0
- package/dist/routes.js +130 -30
- package/dist/sql.js +1 -0
- package/dist/validate.d.ts +30 -18
- package/dist/validate.js +104 -94
- package/dist/vite/ssr.d.ts +1 -1
- package/dist/vite/ssr.js +1 -0
- package/package.json +16 -14
package/dist/auth/token.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export declare function createAccessToken(payload: any, version?: string, secret
|
|
|
19
19
|
* Decode the given JWT token.
|
|
20
20
|
*/
|
|
21
21
|
export declare function decodeAccessToken(token: string, secret?: string): {
|
|
22
|
-
[
|
|
22
|
+
[key: string]: any;
|
|
23
23
|
iss?: string | undefined;
|
|
24
24
|
sub?: string | undefined;
|
|
25
25
|
aud?: string | string[] | undefined;
|
package/dist/auth.d.ts
CHANGED
|
@@ -19,8 +19,8 @@ export interface CreateAuth<UserModel extends AnyUser> {
|
|
|
19
19
|
logout: (req: Request, res: Response) => Promise<void>;
|
|
20
20
|
}
|
|
21
21
|
export declare class RedisTokenStorage extends TokenStorage {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
redis: RedisClient;
|
|
23
|
+
keyPrefix: string;
|
|
24
24
|
constructor(redis: RedisClient, keyPrefix?: string);
|
|
25
25
|
set(token: string, userId: string, expiresIn: number): Promise<void>;
|
|
26
26
|
get(token: string): Promise<string | null>;
|
package/dist/auth.js
CHANGED
|
@@ -16,6 +16,8 @@ export * from './auth/emailAndPassword.js';
|
|
|
16
16
|
export * from './auth/passwordless.js';
|
|
17
17
|
export * from './auth/token.js';
|
|
18
18
|
export class RedisTokenStorage extends TokenStorage {
|
|
19
|
+
redis;
|
|
20
|
+
keyPrefix;
|
|
19
21
|
constructor(redis, keyPrefix = `${config.value.REDIS_KEY_PREFIX}auth:`) {
|
|
20
22
|
super();
|
|
21
23
|
this.redis = redis;
|
package/dist/channels.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type Request } from 'express';
|
|
2
2
|
import { type RedisClient } from './redis.js';
|
|
3
|
+
import { type ExtractParams, type TypedRequestHandler } from './router.js';
|
|
3
4
|
export declare function createEventStream<Channels extends string>(redis: RedisClient): {
|
|
4
|
-
sse: ({ channels, }: {
|
|
5
|
+
sse: <Path extends string>({ channels, }: {
|
|
5
6
|
channels: (req: Request) => string[] | Promise<string[]>;
|
|
6
|
-
}) =>
|
|
7
|
+
}) => TypedRequestHandler<ExtractParams<Path>, any, any, any>;
|
|
7
8
|
publish: (channel: Channels, message: unknown) => Promise<number>;
|
|
8
9
|
};
|
package/dist/channels.js
CHANGED
|
@@ -2,62 +2,67 @@ import {} from 'express';
|
|
|
2
2
|
import { config } from './config.js';
|
|
3
3
|
import { requestContext } from './context.js';
|
|
4
4
|
import { getLogger } from './logger.js';
|
|
5
|
-
import { route } from './middleware.js';
|
|
6
5
|
import {} from './redis.js';
|
|
6
|
+
import {} from './router.js';
|
|
7
7
|
const logger = getLogger();
|
|
8
8
|
export function createEventStream(redis) {
|
|
9
9
|
const keyPrefix = `${config.value.REDIS_KEY_PREFIX}events:`;
|
|
10
10
|
const withKeyPrefix = (key) => `${keyPrefix}${key}`;
|
|
11
11
|
const sse = ({ channels, }) => {
|
|
12
|
-
return
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
res.write('event: heartbeat\n');
|
|
24
|
-
res.write('data: ping\n\n');
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
logger.error(error);
|
|
29
|
-
}
|
|
30
|
-
}, 15000);
|
|
31
|
-
const store = requestContext.getStore();
|
|
32
|
-
const listener = (value) => {
|
|
33
|
-
try {
|
|
34
|
-
requestContext.run(store, () => {
|
|
12
|
+
return async (req, res, next) => {
|
|
13
|
+
try {
|
|
14
|
+
const headers = {
|
|
15
|
+
'Content-Type': 'text/event-stream',
|
|
16
|
+
'Cache-Control': 'no-cache',
|
|
17
|
+
Connection: 'keep-alive',
|
|
18
|
+
};
|
|
19
|
+
res.writeHead(200, headers);
|
|
20
|
+
res.write('retry: 15000\n');
|
|
21
|
+
const heartbeat = setInterval(() => {
|
|
22
|
+
try {
|
|
35
23
|
if (!res.writableEnded) {
|
|
36
|
-
res.write('event:
|
|
37
|
-
res.write(
|
|
24
|
+
res.write('event: heartbeat\n');
|
|
25
|
+
res.write('data: ping\n\n');
|
|
38
26
|
}
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
logger.error(error);
|
|
30
|
+
}
|
|
31
|
+
}, 15000);
|
|
32
|
+
const store = requestContext.getStore();
|
|
33
|
+
const listener = (value) => {
|
|
34
|
+
try {
|
|
35
|
+
requestContext.run(store, () => {
|
|
36
|
+
if (!res.writableEnded) {
|
|
37
|
+
res.write('event: data\n');
|
|
38
|
+
res.write(`data: ${JSON.stringify(value)}\n\n`);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
logger.error(error);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const subscriber = redis.duplicate();
|
|
47
|
+
await subscriber.connect();
|
|
48
|
+
const _channels = await channels(req);
|
|
49
|
+
await subscriber.subscribe(_channels.map(withKeyPrefix), (message) => {
|
|
50
|
+
listener(JSON.parse(message));
|
|
51
|
+
});
|
|
52
|
+
logger.info('event stream subscribe', { channels: _channels });
|
|
53
|
+
res.on('close', () => {
|
|
54
|
+
requestContext.run(store, () => {
|
|
55
|
+
logger.info('event stream close');
|
|
56
|
+
clearInterval(heartbeat);
|
|
57
|
+
subscriber.quit();
|
|
58
|
+
res.end();
|
|
39
59
|
});
|
|
40
|
-
}
|
|
41
|
-
catch (error) {
|
|
42
|
-
logger.error(error);
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
const subscriber = redis.duplicate();
|
|
46
|
-
await subscriber.connect();
|
|
47
|
-
const _channels = await channels(req);
|
|
48
|
-
await subscriber.subscribe(_channels.map(withKeyPrefix), (message) => {
|
|
49
|
-
listener(JSON.parse(message));
|
|
50
|
-
});
|
|
51
|
-
logger.info('event stream subscribe', { channels: _channels });
|
|
52
|
-
res.on('close', () => {
|
|
53
|
-
requestContext.run(store, () => {
|
|
54
|
-
logger.info('event stream close');
|
|
55
|
-
clearInterval(heartbeat);
|
|
56
|
-
subscriber.quit();
|
|
57
|
-
res.end();
|
|
58
60
|
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
next(error);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
61
66
|
};
|
|
62
67
|
const publish = (channel, message) => {
|
|
63
68
|
return redis.publish(withKeyPrefix(channel), JSON.stringify(message));
|
package/dist/cli.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export interface CLIOptions {
|
|
|
3
3
|
printStackTraces?: boolean;
|
|
4
4
|
}
|
|
5
5
|
export declare class CLI {
|
|
6
|
-
|
|
6
|
+
options: CLIOptions;
|
|
7
7
|
constructor(options?: CLIOptions);
|
|
8
8
|
writeLine(message: string, output?: WriteStream): void;
|
|
9
9
|
greenLine(message: string, output?: WriteStream): void;
|
package/dist/cli.js
CHANGED
package/dist/errors.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ErrorHandler } from 'compose-middleware';
|
|
1
2
|
type AnyPayload = Record<string, unknown>;
|
|
2
3
|
export declare class ApiError extends Error {
|
|
3
4
|
status: number | null | undefined;
|
|
@@ -37,5 +38,5 @@ export interface ErrorHandlerOptions {
|
|
|
37
38
|
*
|
|
38
39
|
* Composed together with the Sentry error handler.
|
|
39
40
|
*/
|
|
40
|
-
export declare function errorHandler({ onError }?: ErrorHandlerOptions):
|
|
41
|
+
export declare function errorHandler({ onError }?: ErrorHandlerOptions): ErrorHandler<any, any, void>;
|
|
41
42
|
export {};
|
package/dist/errors.js
CHANGED
|
@@ -6,6 +6,8 @@ import { InvalidToken } from './auth/errors.js';
|
|
|
6
6
|
import { getLogger } from './logger.js';
|
|
7
7
|
import { DoesNotExistError, IntegrityError } from './postgres.js';
|
|
8
8
|
export class ApiError extends Error {
|
|
9
|
+
status;
|
|
10
|
+
payload;
|
|
9
11
|
}
|
|
10
12
|
export const BadRequest = (reason) => makeError(400, 'Invalid Params', { reason });
|
|
11
13
|
export const Forbidden = (detail = '') => makeError(403, detail);
|
|
@@ -88,6 +90,8 @@ export function errorHandler({ onError } = {}) {
|
|
|
88
90
|
return process.env.NODE_ENV === 'production';
|
|
89
91
|
},
|
|
90
92
|
}),
|
|
91
|
-
_errorHandler({
|
|
93
|
+
_errorHandler({
|
|
94
|
+
onError: onError || _defaultErrorHandler,
|
|
95
|
+
}),
|
|
92
96
|
]);
|
|
93
97
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/manage.js
CHANGED
|
@@ -4,7 +4,7 @@ import { hashPassword } from './auth.js';
|
|
|
4
4
|
import { CLI } from './cli.js';
|
|
5
5
|
import { config } from './config.js';
|
|
6
6
|
import { asCli } from './migrate.js';
|
|
7
|
-
import { parseRoutes } from './routes.js';
|
|
7
|
+
import { parseRoutes, toOpenAPISpec } from './routes.js';
|
|
8
8
|
export function registerManagementCommand(register) {
|
|
9
9
|
const cli = new CLI({ printStackTraces: process.argv.includes('--verbose') });
|
|
10
10
|
register(program.command.bind(program), cli);
|
|
@@ -26,7 +26,8 @@ export function registerDefaultCommands(app) {
|
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
28
|
if (options.openapi) {
|
|
29
|
-
|
|
29
|
+
const routes = toOpenAPISpec(app);
|
|
30
|
+
console.log(JSON.stringify(routes, null, 2));
|
|
30
31
|
return;
|
|
31
32
|
}
|
|
32
33
|
const routes = parseRoutes(app);
|
package/dist/middleware.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export declare function useCspNonce(): string;
|
|
|
48
48
|
/**
|
|
49
49
|
* Register middleware that must come before the app routes.
|
|
50
50
|
*/
|
|
51
|
-
export declare function before({ cookies, helmet: useHelmet, json: useJson, csrf: useCsrf, urlencoded: useUrlencoded, allowUnsafeInlineScripts, cspDirectives, }?: BeforeMiddlewareOptions): import("compose-middleware").RequestHandler<
|
|
51
|
+
export declare function before({ cookies, helmet: useHelmet, json: useJson, csrf: useCsrf, urlencoded: useUrlencoded, allowUnsafeInlineScripts, cspDirectives, }?: BeforeMiddlewareOptions): import("compose-middleware").RequestHandler<any, any, void>;
|
|
52
52
|
/**
|
|
53
53
|
* Middleware that sends a 404
|
|
54
54
|
*
|
|
@@ -61,10 +61,10 @@ export declare function notFound(): RequestHandler;
|
|
|
61
61
|
*/
|
|
62
62
|
export declare function after(): RequestHandler;
|
|
63
63
|
type ResponseHeaders = Record<string, string>;
|
|
64
|
-
type AnyResponseBody = Record<string, any> | string;
|
|
64
|
+
type AnyResponseBody = Record<string, any> | Record<string, any>[] | string;
|
|
65
65
|
type HttpResponseCode = number;
|
|
66
66
|
type ResponseWrapper<TResponseBody extends AnyResponseBody> = TResponseBody | TResponseBody[] | HttpResponseCode | [HttpResponseCode, TResponseBody] | [HttpResponseCode, TResponseBody, ResponseHeaders];
|
|
67
67
|
/**
|
|
68
68
|
* Alias of `route()`
|
|
69
69
|
*/
|
|
70
|
-
export declare function asyncMiddleware<RequestBody, RequestQuery extends Record<string, any> = Record<string, any>, ResponseBody extends AnyResponseBody = Record<string, any>, ParamKeys extends string = string>(fn: (req: Request<Record<ParamKeys, string>, ResponseBody, RequestBody, RequestQuery, Record<string, any>>, res: Response<ResponseBody, object>, next: NextFunction) => ResponseWrapper<ResponseBody> | Promise<ResponseWrapper<ResponseBody>>): RequestHandler<Record<
|
|
70
|
+
export declare function asyncMiddleware<RequestBody, RequestQuery extends Record<string, any> = Record<string, any>, ResponseBody extends AnyResponseBody = Record<string, any>, ParamKeys extends string = string>(fn: (req: Request<Record<ParamKeys, string>, ResponseBody, RequestBody, RequestQuery, Record<string, any>>, res: Response<ResponseBody, object>, next: NextFunction) => ResponseWrapper<ResponseBody> | Promise<ResponseWrapper<ResponseBody>>): RequestHandler<Record<ParamKeys, string>, ResponseBody, RequestBody, RequestQuery>;
|
package/dist/middleware.js
CHANGED
|
@@ -151,6 +151,9 @@ export function asyncMiddleware(fn) {
|
|
|
151
151
|
.then(doubleCsrfProtection(req))
|
|
152
152
|
.then(() => fn(req, res, next))
|
|
153
153
|
.then((result) => {
|
|
154
|
+
if (typeof result === 'undefined') {
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
154
157
|
if (Array.isArray(result) &&
|
|
155
158
|
(result.length === 2 || result.length === 3) &&
|
|
156
159
|
typeof result[0] === 'number') {
|
package/dist/migrate.js
CHANGED
|
@@ -55,8 +55,12 @@ export const asCli = (command, cli) => {
|
|
|
55
55
|
}));
|
|
56
56
|
};
|
|
57
57
|
export class Migrator {
|
|
58
|
+
postgres;
|
|
59
|
+
migrationTableName;
|
|
60
|
+
pathToMigrations;
|
|
61
|
+
filenameRegExp = /(\d+)_(.*)\.(js|ts|mjs|mts|sql)/;
|
|
62
|
+
verbose;
|
|
58
63
|
constructor({ postgres = createPostgres({ onLogStatement: () => { } }), pathToMigrations = path.resolve(process.cwd(), 'db', 'migrations'), migrationTableName = 'migrations', verbose = true, } = {}) {
|
|
59
|
-
this.filenameRegExp = /(\d+)_(.*)\.(js|ts|mjs|mts|sql)/;
|
|
60
64
|
this.postgres = postgres;
|
|
61
65
|
this.pathToMigrations = pathToMigrations;
|
|
62
66
|
this.migrationTableName = migrationTableName;
|
package/dist/postgres.d.ts
CHANGED
|
@@ -29,8 +29,8 @@ export interface TransactionContext {
|
|
|
29
29
|
}
|
|
30
30
|
export declare const transactionStorage: AsyncLocalStorage<TransactionContext>;
|
|
31
31
|
export declare class Connection {
|
|
32
|
-
private options;
|
|
33
32
|
client: PoolClient;
|
|
33
|
+
options: ConnectionOptions;
|
|
34
34
|
constructor(client: PoolClient, options: ConnectionOptions);
|
|
35
35
|
private logStatement;
|
|
36
36
|
query<T extends QueryResultRow>(text: string | SqlStatement, values?: Value[]): Promise<QueryResult<T>>;
|
package/dist/postgres.js
CHANGED
|
@@ -26,9 +26,11 @@ export class IntegrityError extends Error {
|
|
|
26
26
|
}
|
|
27
27
|
export const transactionStorage = new AsyncLocalStorage();
|
|
28
28
|
export class Connection {
|
|
29
|
+
client;
|
|
30
|
+
options;
|
|
29
31
|
constructor(client, options) {
|
|
30
|
-
this.options = options;
|
|
31
32
|
this.client = client;
|
|
33
|
+
this.options = options;
|
|
32
34
|
}
|
|
33
35
|
logStatement(text, values, duration) {
|
|
34
36
|
if (this.options.onLogStatement) {
|
|
@@ -80,12 +82,9 @@ export class Connection {
|
|
|
80
82
|
}
|
|
81
83
|
}
|
|
82
84
|
export class Transaction extends Connection {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
this.isCommitted = false;
|
|
87
|
-
this.isRolledBack = false;
|
|
88
|
-
}
|
|
85
|
+
savepoints = [];
|
|
86
|
+
isCommitted = false;
|
|
87
|
+
isRolledBack = false;
|
|
89
88
|
async createSavepoint() {
|
|
90
89
|
const savepointName = `sp_${this.savepoints.length + 1}`;
|
|
91
90
|
await this.query(`SAVEPOINT ${savepointName}`);
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
import { type NextFunction, type Request, type RequestHandler, type Response } from 'express';
|
|
3
|
+
import { type ValidationSpec } from './validate.js';
|
|
4
|
+
type ResponseHeaders = Record<string, string>;
|
|
5
|
+
type HttpResponseCode = number;
|
|
6
|
+
type ResponseWrapper<TResponseBody> = TResponseBody | TResponseBody[] | HttpResponseCode | [HttpResponseCode, TResponseBody] | [HttpResponseCode, TResponseBody, ResponseHeaders];
|
|
7
|
+
type Prettify<T> = {
|
|
8
|
+
[K in keyof T]: T[K];
|
|
9
|
+
} & {};
|
|
10
|
+
export type ExtractParams<Path extends string> = Prettify<Path extends `${string}:${infer Param}/${infer Rest}` ? {
|
|
11
|
+
[K in Param extends `${infer P}?` ? P : Param]: string;
|
|
12
|
+
} & ExtractParams<`/${Rest}`> : Path extends `${string}:${infer Param}` ? {
|
|
13
|
+
[K in Param extends `${infer P}?` ? P : Param]: string;
|
|
14
|
+
} : {}>;
|
|
15
|
+
export type TypedRequestHandler<Params, ResponseBodySchema extends StandardSchemaV1 | undefined, RequestBodySchema extends StandardSchemaV1 | undefined, RequestQuery extends StandardSchemaV1 | undefined> = (req: Request<Params, never, RequestBodySchema extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<RequestBodySchema> : unknown, RequestQuery extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<RequestQuery> : Record<string, string | string[]>>, res: Response<ResponseBodySchema extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<ResponseBodySchema> : unknown>, next: NextFunction) => ResponseWrapper<ResponseBodySchema extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<ResponseBodySchema> : unknown> | Promise<ResponseWrapper<ResponseBodySchema extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<ResponseBodySchema> : unknown>>;
|
|
16
|
+
export declare class TypedRouter {
|
|
17
|
+
#private;
|
|
18
|
+
router: import("express-serve-static-core").Router;
|
|
19
|
+
get<Path extends string, RequestBodySchema extends StandardSchemaV1<any> | undefined = undefined, ResponseBodySchema extends StandardSchemaV1<any> | undefined = undefined, RequestQuerySchema extends StandardSchemaV1<any> | undefined = undefined>(path: Path, validation: ValidationSpec<RequestBodySchema, ResponseBodySchema, RequestQuerySchema>, ...middleware: [
|
|
20
|
+
...RequestHandler[],
|
|
21
|
+
TypedRequestHandler<ExtractParams<Path>, ResponseBodySchema, RequestBodySchema, RequestQuerySchema>
|
|
22
|
+
]): void;
|
|
23
|
+
post<Path extends string, RequestBodySchema extends StandardSchemaV1<any> | undefined = undefined, ResponseBodySchema extends StandardSchemaV1<any> | undefined = undefined, RequestQuerySchema extends StandardSchemaV1<any> | undefined = undefined>(path: Path, validation: ValidationSpec<RequestBodySchema, ResponseBodySchema, RequestQuerySchema>, ...middleware: [
|
|
24
|
+
...RequestHandler[],
|
|
25
|
+
TypedRequestHandler<ExtractParams<Path>, ResponseBodySchema, RequestBodySchema, RequestQuerySchema>
|
|
26
|
+
]): void;
|
|
27
|
+
put<Path extends string, RequestBodySchema extends StandardSchemaV1<any> | undefined = undefined, ResponseBodySchema extends StandardSchemaV1<any> | undefined = undefined, RequestQuerySchema extends StandardSchemaV1<any> | undefined = undefined>(path: Path, validation: ValidationSpec<RequestBodySchema, ResponseBodySchema, RequestQuerySchema>, ...middleware: [
|
|
28
|
+
...RequestHandler[],
|
|
29
|
+
TypedRequestHandler<ExtractParams<Path>, ResponseBodySchema, RequestBodySchema, RequestQuerySchema>
|
|
30
|
+
]): void;
|
|
31
|
+
delete<Path extends string, RequestBodySchema extends StandardSchemaV1<any> | undefined = undefined, ResponseBodySchema extends StandardSchemaV1<any> | undefined = undefined, RequestQuerySchema extends StandardSchemaV1<any> | undefined = undefined>(path: Path, validation: ValidationSpec<RequestBodySchema, ResponseBodySchema, RequestQuerySchema>, ...middleware: [
|
|
32
|
+
...RequestHandler[],
|
|
33
|
+
TypedRequestHandler<ExtractParams<Path>, ResponseBodySchema, RequestBodySchema, RequestQuerySchema>
|
|
34
|
+
]): void;
|
|
35
|
+
use(...middleware: RequestHandler[]): this;
|
|
36
|
+
use(...middleware: [string, ...RequestHandler[]]): this;
|
|
37
|
+
getRouter(): import("express-serve-static-core").Router;
|
|
38
|
+
}
|
|
39
|
+
export declare function createRouter(): TypedRouter;
|
|
40
|
+
export {};
|
package/dist/router.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {} from '@standard-schema/spec';
|
|
2
|
+
import { Router, } from 'express';
|
|
3
|
+
import { asyncMiddleware } from './middleware.js';
|
|
4
|
+
import { validateStandardSchema } from './validate.js';
|
|
5
|
+
export class TypedRouter {
|
|
6
|
+
router = Router();
|
|
7
|
+
get(path, validation, ...middleware) {
|
|
8
|
+
return this.#handle('get', path, validation, ...middleware);
|
|
9
|
+
}
|
|
10
|
+
post(path, validation, ...middleware) {
|
|
11
|
+
return this.#handle('post', path, validation, ...middleware);
|
|
12
|
+
}
|
|
13
|
+
put(path, validation, ...middleware) {
|
|
14
|
+
return this.#handle('put', path, validation, ...middleware);
|
|
15
|
+
}
|
|
16
|
+
delete(path, validation, ...middleware) {
|
|
17
|
+
return this.#handle('delete', path, validation, ...middleware);
|
|
18
|
+
}
|
|
19
|
+
#handle(method, path, validation, ...middleware) {
|
|
20
|
+
const handler = middleware.pop();
|
|
21
|
+
this.router[method](path, validateStandardSchema({
|
|
22
|
+
body: validation.request,
|
|
23
|
+
query: validation.query,
|
|
24
|
+
response: validation.response,
|
|
25
|
+
}), ...middleware, asyncMiddleware(handler));
|
|
26
|
+
}
|
|
27
|
+
use(...middleware) {
|
|
28
|
+
this.router.use(...middleware);
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
getRouter() {
|
|
32
|
+
return this.router;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function createRouter() {
|
|
36
|
+
return new TypedRouter();
|
|
37
|
+
}
|
package/dist/routes.d.ts
CHANGED
|
@@ -14,9 +14,23 @@ export declare function parseRoutes(app: Express): {
|
|
|
14
14
|
} | null;
|
|
15
15
|
query: {} | null;
|
|
16
16
|
body: {} | null;
|
|
17
|
+
response: {} | null;
|
|
17
18
|
}[];
|
|
18
19
|
export interface Key {
|
|
19
20
|
name: string;
|
|
20
21
|
optional: boolean;
|
|
21
22
|
offset: number;
|
|
22
23
|
}
|
|
24
|
+
export declare function toOpenAPISpec(app: Express, info?: {
|
|
25
|
+
title: string;
|
|
26
|
+
version: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
}): {
|
|
29
|
+
openapi: string;
|
|
30
|
+
info: {
|
|
31
|
+
description?: string | undefined;
|
|
32
|
+
title: string;
|
|
33
|
+
version: string;
|
|
34
|
+
};
|
|
35
|
+
paths: Record<string, any>;
|
|
36
|
+
};
|
package/dist/routes.js
CHANGED
|
@@ -4,7 +4,11 @@ export function createDocsRouter(app, extras = {}) {
|
|
|
4
4
|
return route(async () => {
|
|
5
5
|
return {
|
|
6
6
|
...extras,
|
|
7
|
-
|
|
7
|
+
...toOpenAPISpec(app, {
|
|
8
|
+
title: extras.title || 'API Documentation',
|
|
9
|
+
version: extras.version || '1.0.0',
|
|
10
|
+
description: extras.description,
|
|
11
|
+
}),
|
|
8
12
|
};
|
|
9
13
|
});
|
|
10
14
|
}
|
|
@@ -21,6 +25,9 @@ export function parseRoutes(app) {
|
|
|
21
25
|
body: metadata
|
|
22
26
|
?.filter((m) => typeof m === 'object' && m != null && 'bodySchema' in m)
|
|
23
27
|
.at(-1)?.bodySchema ?? null,
|
|
28
|
+
response: metadata
|
|
29
|
+
?.filter((m) => typeof m === 'object' && m != null && 'responseSchema' in m)
|
|
30
|
+
.at(-1)?.responseSchema ?? null,
|
|
24
31
|
};
|
|
25
32
|
});
|
|
26
33
|
}
|
|
@@ -47,39 +54,11 @@ const buildPathParamsSchema = (pathParams) => {
|
|
|
47
54
|
};
|
|
48
55
|
// Fork of express-route-parser
|
|
49
56
|
class ExpressPathParser {
|
|
57
|
+
_appPaths;
|
|
50
58
|
getPaths() {
|
|
51
59
|
return this._appPaths;
|
|
52
60
|
}
|
|
53
61
|
constructor(app) {
|
|
54
|
-
this.traverse = (path, layer, keys) => {
|
|
55
|
-
keys = [...keys, ...layer.keys];
|
|
56
|
-
if (layer.name === 'router' && layer.handle) {
|
|
57
|
-
for (const l of layer.handle.stack) {
|
|
58
|
-
this.traverse(`${path || ''}/${pathRegexParser(layer.regexp, layer.keys)}`, l, keys);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
if (!layer.route || !layer.route.stack.length) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
this._appPaths.push(this.parseRouteLayer(layer, keys, path));
|
|
65
|
-
};
|
|
66
|
-
this.parseRouteLayer = (layer, keys, basePath) => {
|
|
67
|
-
const lastRequestHandler = layer.route.stack[layer.route.stack.length - 1];
|
|
68
|
-
const pathParams = keys.map((key) => {
|
|
69
|
-
return { name: key.name, in: 'path', required: !key.optional };
|
|
70
|
-
});
|
|
71
|
-
const filtered = layer.route.stack.filter((element) => element?.handle?.metadata);
|
|
72
|
-
const path = (basePath + layer.route.path).replace(/\/{2,}/gi, '/');
|
|
73
|
-
if (filtered.length === 0) {
|
|
74
|
-
return { path, pathParams, method: lastRequestHandler.method };
|
|
75
|
-
}
|
|
76
|
-
return {
|
|
77
|
-
path,
|
|
78
|
-
pathParams,
|
|
79
|
-
method: lastRequestHandler.method,
|
|
80
|
-
metadata: filtered.map((f) => f.handle.metadata),
|
|
81
|
-
};
|
|
82
|
-
};
|
|
83
62
|
this._appPaths = [];
|
|
84
63
|
const router = app._router;
|
|
85
64
|
if (router) {
|
|
@@ -88,6 +67,35 @@ class ExpressPathParser {
|
|
|
88
67
|
}
|
|
89
68
|
}
|
|
90
69
|
}
|
|
70
|
+
traverse = (path, layer, keys) => {
|
|
71
|
+
keys = [...keys, ...layer.keys];
|
|
72
|
+
if (layer.name === 'router' && layer.handle) {
|
|
73
|
+
for (const l of layer.handle.stack) {
|
|
74
|
+
this.traverse(`${path || ''}/${pathRegexParser(layer.regexp, layer.keys)}`, l, keys);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!layer.route || !layer.route.stack.length) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this._appPaths.push(this.parseRouteLayer(layer, keys, path));
|
|
81
|
+
};
|
|
82
|
+
parseRouteLayer = (layer, keys, basePath) => {
|
|
83
|
+
const lastRequestHandler = layer.route.stack[layer.route.stack.length - 1];
|
|
84
|
+
const pathParams = keys.map((key) => {
|
|
85
|
+
return { name: key.name, in: 'path', required: !key.optional };
|
|
86
|
+
});
|
|
87
|
+
const filtered = layer.route.stack.filter((element) => element?.handle?.metadata);
|
|
88
|
+
const path = (basePath + layer.route.path).replace(/\/{2,}/gi, '/');
|
|
89
|
+
if (filtered.length === 0) {
|
|
90
|
+
return { path, pathParams, method: lastRequestHandler.method };
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
path,
|
|
94
|
+
pathParams,
|
|
95
|
+
method: lastRequestHandler.method,
|
|
96
|
+
metadata: filtered.map((f) => f.handle.metadata),
|
|
97
|
+
};
|
|
98
|
+
};
|
|
91
99
|
}
|
|
92
100
|
const pathRegexParser = (layerRegexPath, keys) => {
|
|
93
101
|
if (typeof layerRegexPath === 'string') {
|
|
@@ -135,3 +143,95 @@ const mapKeysToPath = (layerRegexPath, keys) => {
|
|
|
135
143
|
.replace(/\\/gi, '')
|
|
136
144
|
.replace(/\/{2,}/gi, '/');
|
|
137
145
|
};
|
|
146
|
+
export function toOpenAPISpec(app, info = {
|
|
147
|
+
title: 'API Documentation',
|
|
148
|
+
version: '1.0.0',
|
|
149
|
+
}) {
|
|
150
|
+
const routes = parseRoutes(app);
|
|
151
|
+
const paths = {};
|
|
152
|
+
for (const routeInfo of routes) {
|
|
153
|
+
const { method, path, params, query, body, response } = routeInfo;
|
|
154
|
+
// Convert Express path params to OpenAPI path params
|
|
155
|
+
const openApiPath = path.replace(/:([^/]+)/g, '{$1}');
|
|
156
|
+
if (!paths[openApiPath]) {
|
|
157
|
+
paths[openApiPath] = {};
|
|
158
|
+
}
|
|
159
|
+
const parameters = [];
|
|
160
|
+
// Add path parameters
|
|
161
|
+
if (params) {
|
|
162
|
+
parameters.push(...Object.keys(params.properties).map((name) => ({
|
|
163
|
+
name,
|
|
164
|
+
in: 'path',
|
|
165
|
+
required: params.required?.includes(name) ?? true,
|
|
166
|
+
schema: params.properties[name],
|
|
167
|
+
})));
|
|
168
|
+
}
|
|
169
|
+
// Add query parameters
|
|
170
|
+
if (query && typeof query === 'object' && 'properties' in query) {
|
|
171
|
+
const querySchema = query;
|
|
172
|
+
parameters.push(...Object.keys(querySchema.properties || {}).map((name) => ({
|
|
173
|
+
name,
|
|
174
|
+
in: 'query',
|
|
175
|
+
required: Array.isArray(querySchema.required) &&
|
|
176
|
+
querySchema.required.includes(name),
|
|
177
|
+
schema: (querySchema.properties || {})[name],
|
|
178
|
+
})));
|
|
179
|
+
}
|
|
180
|
+
paths[openApiPath][method.toLowerCase()] = {
|
|
181
|
+
parameters,
|
|
182
|
+
...(body && {
|
|
183
|
+
requestBody: {
|
|
184
|
+
content: {
|
|
185
|
+
'application/json': {
|
|
186
|
+
schema: body,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
}),
|
|
191
|
+
responses: {
|
|
192
|
+
...(response
|
|
193
|
+
? {
|
|
194
|
+
'200': {
|
|
195
|
+
description: 'Successful response',
|
|
196
|
+
content: {
|
|
197
|
+
'application/json': {
|
|
198
|
+
schema: response,
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
}
|
|
203
|
+
: {
|
|
204
|
+
'200': {
|
|
205
|
+
description: 'Successful response',
|
|
206
|
+
},
|
|
207
|
+
}),
|
|
208
|
+
'400': {
|
|
209
|
+
description: 'Bad Request',
|
|
210
|
+
content: {
|
|
211
|
+
'application/json': {
|
|
212
|
+
schema: {
|
|
213
|
+
type: 'object',
|
|
214
|
+
properties: {
|
|
215
|
+
type: { type: 'string' },
|
|
216
|
+
title: { type: 'string' },
|
|
217
|
+
detail: { type: 'string' },
|
|
218
|
+
status: { type: 'number' },
|
|
219
|
+
},
|
|
220
|
+
required: ['type', 'title', 'status'],
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
openapi: '3.1.0',
|
|
230
|
+
info: {
|
|
231
|
+
title: info.title,
|
|
232
|
+
version: info.version,
|
|
233
|
+
...(info.description && { description: info.description }),
|
|
234
|
+
},
|
|
235
|
+
paths,
|
|
236
|
+
};
|
|
237
|
+
}
|
package/dist/sql.js
CHANGED
package/dist/validate.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import {
|
|
3
|
-
import { type RequestHandler } from 'express';
|
|
1
|
+
import { type StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
import { type NextFunction, type RequestHandler, type Request, type Response } from 'express';
|
|
4
3
|
export type ValidationSchema = {
|
|
5
4
|
type?: 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object' | 'null';
|
|
6
5
|
format?: string;
|
|
@@ -28,27 +27,40 @@ export type ValidationSchema = {
|
|
|
28
27
|
title?: string;
|
|
29
28
|
description?: string;
|
|
30
29
|
} | boolean;
|
|
31
|
-
type
|
|
32
|
-
export declare function validateBody(schema:
|
|
33
|
-
export declare function validateQuery(schema:
|
|
34
|
-
export declare function validateParams(schema:
|
|
30
|
+
type LegacyCompatibleSchema = ValidationSchema | StandardSchemaV1;
|
|
31
|
+
export declare function validateBody(schema: LegacyCompatibleSchema): RequestHandler;
|
|
32
|
+
export declare function validateQuery(schema: LegacyCompatibleSchema): RequestHandler;
|
|
33
|
+
export declare function validateParams(schema: LegacyCompatibleSchema): RequestHandler;
|
|
34
|
+
type ValidationErrors<Field extends string = string> = Record<Field, string[]>;
|
|
35
35
|
export type ValidationResult = {
|
|
36
36
|
isValid: true;
|
|
37
37
|
errors: null;
|
|
38
38
|
} | {
|
|
39
39
|
isValid: false;
|
|
40
|
-
errors:
|
|
41
|
-
[key: string]: any;
|
|
42
|
-
};
|
|
40
|
+
errors: ValidationErrors;
|
|
43
41
|
};
|
|
44
|
-
export
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
export interface ValidationSpec<RequestBodySchema extends StandardSchemaV1<any> | undefined = undefined, ResponseBodySchema extends StandardSchemaV1<any> | undefined = undefined, RequestQuerySchema extends StandardSchemaV1<any> | undefined = undefined> {
|
|
43
|
+
request?: RequestBodySchema;
|
|
44
|
+
response?: ResponseBodySchema;
|
|
45
|
+
query?: RequestQuerySchema;
|
|
46
|
+
}
|
|
47
|
+
export declare function validateSchema(data: unknown, schema: StandardSchemaV1<any>): Promise<ValidationResult>;
|
|
48
|
+
export declare function validateStandardSchema(schemas: {
|
|
49
|
+
query?: StandardSchemaV1<any>;
|
|
50
|
+
body?: StandardSchemaV1<any>;
|
|
51
|
+
params?: StandardSchemaV1<any>;
|
|
52
|
+
response?: StandardSchemaV1<any>;
|
|
53
|
+
}): {
|
|
54
|
+
(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
55
|
+
metadata: {
|
|
56
|
+
bodySchema?: ValidationSchema;
|
|
57
|
+
querySchema?: ValidationSchema;
|
|
58
|
+
paramsSchema?: ValidationSchema;
|
|
59
|
+
responseSchema?: ValidationSchema;
|
|
51
60
|
};
|
|
52
61
|
};
|
|
53
|
-
|
|
62
|
+
type WithJsonSchema<T> = T & {
|
|
63
|
+
toJsonSchema: () => ValidationSchema;
|
|
64
|
+
};
|
|
65
|
+
export declare function jsonSchema(schema: ValidationSchema): WithJsonSchema<StandardSchemaV1<Record<string, any>>>;
|
|
54
66
|
export {};
|
package/dist/validate.js
CHANGED
|
@@ -1,120 +1,130 @@
|
|
|
1
|
+
import { standardValidate, ValidationError } from '@devmoods/fetch';
|
|
2
|
+
import {} from '@standard-schema/spec';
|
|
1
3
|
import _Ajv, {} from 'ajv';
|
|
2
|
-
import { Type } from 'arktype';
|
|
3
4
|
import {} from 'express';
|
|
4
5
|
import { BadRequest } from './errors.js';
|
|
5
6
|
const Ajv = _Ajv;
|
|
6
|
-
|
|
7
|
-
if (schema
|
|
8
|
-
|
|
7
|
+
function toStandardSchema(schema) {
|
|
8
|
+
if (typeof schema === 'boolean' ||
|
|
9
|
+
(typeof schema === 'object' && schema != null && !('~standard' in schema))) {
|
|
10
|
+
return jsonSchema(schema);
|
|
9
11
|
}
|
|
10
12
|
return schema;
|
|
11
|
-
}
|
|
13
|
+
}
|
|
12
14
|
export function validateBody(schema) {
|
|
13
|
-
|
|
14
|
-
const validate = compileValidateSchema(schema);
|
|
15
|
-
const middleware = (req, res, next) => {
|
|
16
|
-
const result = validate(req.body);
|
|
17
|
-
if (!result.isValid) {
|
|
18
|
-
return next(BadRequest({ body: result.errors }));
|
|
19
|
-
}
|
|
20
|
-
next();
|
|
21
|
-
};
|
|
22
|
-
middleware.metadata = { bodySchema: schema };
|
|
23
|
-
return middleware;
|
|
15
|
+
return validateStandardSchema({ body: toStandardSchema(schema) });
|
|
24
16
|
}
|
|
25
17
|
export function validateQuery(schema) {
|
|
26
|
-
|
|
27
|
-
const validate = compileValidateSchema(schema, { coerceTypes: true });
|
|
28
|
-
const middleware = (req, res, next) => {
|
|
29
|
-
const result = validate(req.query);
|
|
30
|
-
if (!result.isValid) {
|
|
31
|
-
return next(BadRequest({ query: result.errors }));
|
|
32
|
-
}
|
|
33
|
-
next();
|
|
34
|
-
};
|
|
35
|
-
middleware.metadata = { querySchema: schema };
|
|
36
|
-
return middleware;
|
|
18
|
+
return validateStandardSchema({ query: toStandardSchema(schema) });
|
|
37
19
|
}
|
|
38
20
|
export function validateParams(schema) {
|
|
39
|
-
|
|
40
|
-
const validate = compileValidateSchema(schema, { coerceTypes: true });
|
|
41
|
-
const middleware = (req, res, next) => {
|
|
42
|
-
const result = validate(req.params);
|
|
43
|
-
if (!result.isValid) {
|
|
44
|
-
return next(BadRequest({ params: result.errors }));
|
|
45
|
-
}
|
|
46
|
-
next();
|
|
47
|
-
};
|
|
48
|
-
middleware.metadata = { paramsSchema: schema };
|
|
49
|
-
return middleware;
|
|
50
|
-
}
|
|
51
|
-
function createAjv(options) {
|
|
52
|
-
const ajv = new Ajv({
|
|
53
|
-
allErrors: true,
|
|
54
|
-
validateFormats: false,
|
|
55
|
-
...options,
|
|
56
|
-
});
|
|
57
|
-
ajv.addKeyword('$enumNames');
|
|
58
|
-
ajv.addKeyword('$bitiforms');
|
|
59
|
-
ajv.addKeyword('$forms');
|
|
60
|
-
return ajv;
|
|
21
|
+
return validateStandardSchema({ params: toStandardSchema(schema) });
|
|
61
22
|
}
|
|
62
|
-
export function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (valid) {
|
|
68
|
-
return {
|
|
69
|
-
isValid: true,
|
|
70
|
-
errors: null,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
const errors = transformAjvErrors(validate.errors);
|
|
74
|
-
validate.errors = null;
|
|
23
|
+
export async function validateSchema(data, schema) {
|
|
24
|
+
try {
|
|
25
|
+
await standardValidate(schema, data);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
75
28
|
return {
|
|
76
29
|
isValid: false,
|
|
77
|
-
errors
|
|
30
|
+
errors: (error instanceof ValidationError
|
|
31
|
+
? error.issues.map((issue) => [
|
|
32
|
+
String(issue.path ?? '/'),
|
|
33
|
+
issue.message,
|
|
34
|
+
])
|
|
35
|
+
: []).reduce((result, item) => {
|
|
36
|
+
const [key, value] = item;
|
|
37
|
+
if (Array.isArray(result[key])) {
|
|
38
|
+
result[key].push(value);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
result[key] = [value];
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}, {}),
|
|
78
45
|
};
|
|
46
|
+
}
|
|
47
|
+
return { isValid: true, errors: null };
|
|
48
|
+
}
|
|
49
|
+
export function validateStandardSchema(schemas) {
|
|
50
|
+
const middleware = async (req, res, next) => {
|
|
51
|
+
try {
|
|
52
|
+
for (const [key, schema] of Object.entries(schemas)) {
|
|
53
|
+
if (key === 'response') {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (schema) {
|
|
57
|
+
const result = await validateSchema(req[key], schema);
|
|
58
|
+
if (!result.isValid) {
|
|
59
|
+
return next(BadRequest({ [key]: result.errors }));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
next();
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
next(error);
|
|
67
|
+
}
|
|
79
68
|
};
|
|
69
|
+
const metadata = {};
|
|
70
|
+
if (schemas.body) {
|
|
71
|
+
metadata.bodySchema = toJsonSchema(schemas.body);
|
|
72
|
+
}
|
|
73
|
+
if (schemas.query) {
|
|
74
|
+
metadata.querySchema = toJsonSchema(schemas.query);
|
|
75
|
+
}
|
|
76
|
+
if (schemas.params) {
|
|
77
|
+
metadata.paramsSchema = toJsonSchema(schemas.params);
|
|
78
|
+
}
|
|
79
|
+
if (schemas.response) {
|
|
80
|
+
metadata.responseSchema = toJsonSchema(schemas.response);
|
|
81
|
+
}
|
|
82
|
+
middleware.metadata = metadata;
|
|
83
|
+
return middleware;
|
|
80
84
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (valid) {
|
|
86
|
-
return {
|
|
87
|
-
isValid: true,
|
|
88
|
-
errors: null,
|
|
89
|
-
};
|
|
85
|
+
function toJsonSchema(standardSchema) {
|
|
86
|
+
if ('toJsonSchema' in standardSchema &&
|
|
87
|
+
typeof standardSchema.toJsonSchema === 'function') {
|
|
88
|
+
return standardSchema.toJsonSchema();
|
|
90
89
|
}
|
|
91
|
-
|
|
92
|
-
ajv.errors = null;
|
|
93
|
-
return {
|
|
94
|
-
isValid: false,
|
|
95
|
-
errors,
|
|
96
|
-
};
|
|
90
|
+
return {};
|
|
97
91
|
}
|
|
98
92
|
function transformAjvErrors(errors) {
|
|
99
93
|
/* c8 ignore next 3 */
|
|
100
94
|
if (errors == null) {
|
|
101
95
|
return {};
|
|
102
96
|
}
|
|
103
|
-
return errors
|
|
104
|
-
|
|
105
|
-
const { instancePath, keyword, params } = error;
|
|
97
|
+
return errors.map((error) => {
|
|
98
|
+
const { instancePath, keyword } = error;
|
|
106
99
|
const property = instancePath || '/';
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
100
|
+
return { path: property, message: keyword };
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
export function jsonSchema(schema) {
|
|
104
|
+
const ajv = new Ajv({
|
|
105
|
+
allErrors: true,
|
|
106
|
+
validateFormats: false,
|
|
107
|
+
});
|
|
108
|
+
ajv.addKeyword('$enumNames');
|
|
109
|
+
ajv.addKeyword('$bitiforms');
|
|
110
|
+
ajv.addKeyword('$forms');
|
|
111
|
+
const validate = ajv.compile(schema);
|
|
112
|
+
return {
|
|
113
|
+
toJsonSchema() {
|
|
114
|
+
return schema;
|
|
115
|
+
},
|
|
116
|
+
'~standard': {
|
|
117
|
+
version: 1,
|
|
118
|
+
vendor: 'devmoods-ajv',
|
|
119
|
+
validate(value) {
|
|
120
|
+
const valid = validate(value);
|
|
121
|
+
if (valid) {
|
|
122
|
+
return { value };
|
|
123
|
+
}
|
|
124
|
+
const issues = transformAjvErrors(validate.errors);
|
|
125
|
+
validate.errors = null;
|
|
126
|
+
return { issues };
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
};
|
|
120
130
|
}
|
package/dist/vite/ssr.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ export interface SsrMiddlewareOptions<InitialData = unknown> {
|
|
|
3
3
|
getInitialData?: (req: Request) => Promise<InitialData>;
|
|
4
4
|
useDevServer?: boolean;
|
|
5
5
|
}
|
|
6
|
-
export declare function createSsrMiddleware<InitialData = unknown>({ root, getInitialData, useDevServer, }?: SsrMiddlewareOptions<InitialData>): Promise<import("compose-middleware").RequestHandler<
|
|
6
|
+
export declare function createSsrMiddleware<InitialData = unknown>({ root, getInitialData, useDevServer, }?: SsrMiddlewareOptions<InitialData>): Promise<import("compose-middleware").RequestHandler<any, any, void>>;
|
package/dist/vite/ssr.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devmoods/express-extras",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.56.0",
|
|
4
4
|
"description": "Kubernetes-ready utilities for rapid Node.js application development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -46,33 +46,35 @@
|
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@devmoods/eslint-config": "^2.6.0",
|
|
48
48
|
"@types/koa-compose": "^3.2.8",
|
|
49
|
-
"@types/node": "22.13.
|
|
49
|
+
"@types/node": "22.13.10",
|
|
50
50
|
"@types/supertest": "^6.0.2",
|
|
51
51
|
"@vitejs/plugin-react": "^4.3.4",
|
|
52
|
-
"@vitest/coverage-v8": "^3.0.
|
|
53
|
-
"arktype": "^2.1.
|
|
52
|
+
"@vitest/coverage-v8": "^3.0.8",
|
|
53
|
+
"arktype": "^2.1.9",
|
|
54
54
|
"cookiejar": "^2.1.4",
|
|
55
|
-
"eslint": "^9.
|
|
56
|
-
"faktory-worker": "^4.
|
|
57
|
-
"prettier": "3.5.
|
|
55
|
+
"eslint": "^9.22.0",
|
|
56
|
+
"faktory-worker": "^4.7.0",
|
|
57
|
+
"prettier": "3.5.3",
|
|
58
58
|
"supertest": "7.0.0",
|
|
59
59
|
"ts-node": "10.9.2",
|
|
60
60
|
"tsx": "^4.19.3",
|
|
61
|
-
"typescript": "5.
|
|
62
|
-
"vite": "^6.
|
|
63
|
-
"vitest": "^3.0.
|
|
61
|
+
"typescript": "5.8.2",
|
|
62
|
+
"vite": "^6.2.1",
|
|
63
|
+
"vitest": "^3.0.8",
|
|
64
64
|
"vitest-fail-on-console": "^0.7.1"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@devmoods/fetch": "^4.0
|
|
68
|
-
"@sentry/node": "^9.
|
|
69
|
-
"@sentry/profiling-node": "^9.
|
|
67
|
+
"@devmoods/fetch": "^4.1.0",
|
|
68
|
+
"@sentry/node": "^9.5.0",
|
|
69
|
+
"@sentry/profiling-node": "^9.5.0",
|
|
70
|
+
"@standard-schema/spec": "^1.0.0",
|
|
70
71
|
"@types/bcrypt": "^5.0.2",
|
|
71
72
|
"@types/cookie-parser": "^1.4.8",
|
|
72
73
|
"@types/express": "^5.0.0",
|
|
73
74
|
"@types/jsonwebtoken": "^9.0.9",
|
|
74
75
|
"@types/nodemailer": "^6.4.17",
|
|
75
76
|
"@types/pg": "^8.11.11",
|
|
77
|
+
"@types/qs": "^6.9.18",
|
|
76
78
|
"@types/superagent": "^8.1.9",
|
|
77
79
|
"@types/uuid": "^10.0.0",
|
|
78
80
|
"ajv": "^8.17.1",
|
|
@@ -87,7 +89,7 @@
|
|
|
87
89
|
"helmet": "^8.0.0",
|
|
88
90
|
"jsonwebtoken": "^9.0.2",
|
|
89
91
|
"nodemailer": "^6.10.0",
|
|
90
|
-
"pg": "^8.
|
|
92
|
+
"pg": "^8.14.0",
|
|
91
93
|
"redis": "^4.7.0",
|
|
92
94
|
"sonda": "^0.7.1",
|
|
93
95
|
"sql-formatter": "^15.4.11",
|