yedra 0.15.6 → 0.16.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/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/lib.d.ts +9 -8
- package/dist/lib.js +10 -9
- package/dist/routing/app.d.ts +28 -16
- package/dist/routing/app.js +40 -15
- package/dist/routing/path.js +1 -1
- package/dist/routing/rest.d.ts +9 -5
- package/dist/routing/rest.js +14 -6
- package/dist/routing/websocket.js +2 -2
- package/dist/util/docs.d.ts +1 -1
- package/dist/util/docs.js +9 -11
- package/dist/util/security.d.ts +7 -1
- package/dist/util/security.js +6 -1
- package/dist/validation/modifiable.d.ts +31 -1
- package/dist/validation/modifiable.js +83 -3
- package/package.json +39 -28
- package/dist/validation/array.d.ts +0 -33
- package/dist/validation/array.js +0 -82
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as y from './lib.js';
|
|
2
2
|
export { y };
|
|
3
3
|
export { Yedra } from './routing/app.js';
|
|
4
|
-
export {
|
|
4
|
+
export { BadRequestError, ConflictError, ForbiddenError, HttpError, NotFoundError, PaymentRequiredError, UnauthorizedError, } from './routing/errors.js';
|
|
5
|
+
export { Delete, Get, Post, Put } from './routing/rest.js';
|
|
5
6
|
export { Ws } from './routing/websocket.js';
|
|
6
|
-
export { HttpError, BadRequestError, UnauthorizedError, PaymentRequiredError, ForbiddenError, NotFoundError, ConflictError, } from './routing/errors.js';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as y from './lib.js';
|
|
2
2
|
export { y };
|
|
3
3
|
export { Yedra } from './routing/app.js';
|
|
4
|
-
export {
|
|
4
|
+
export { BadRequestError, ConflictError, ForbiddenError, HttpError, NotFoundError, PaymentRequiredError, UnauthorizedError, } from './routing/errors.js';
|
|
5
|
+
export { Delete, Get, Post, Put } from './routing/rest.js';
|
|
5
6
|
export { Ws } from './routing/websocket.js';
|
|
6
|
-
export { HttpError, BadRequestError, UnauthorizedError, PaymentRequiredError, ForbiddenError, NotFoundError, ConflictError, } from './routing/errors.js';
|
package/dist/lib.d.ts
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { BadRequestError, ConflictError, ForbiddenError, HttpError, NotFoundError, PaymentRequiredError, UnauthorizedError, } from './routing/errors.js';
|
|
2
2
|
export { Log } from './routing/log.js';
|
|
3
3
|
export declare const validatePath: (path: string) => void;
|
|
4
4
|
export { parseEnv } from './routing/env.js';
|
|
5
|
-
export {
|
|
5
|
+
export { SecurityScheme } from './util/security.js';
|
|
6
|
+
export { BodyType, type Typeof } from './validation/body.js';
|
|
6
7
|
export { boolean } from './validation/boolean.js';
|
|
7
8
|
export { date } from './validation/date.js';
|
|
9
|
+
export { either } from './validation/either.js';
|
|
8
10
|
export { _enum as enum } from './validation/enum.js';
|
|
9
|
-
export { _null as null } from './validation/null.js';
|
|
10
11
|
export { ValidationError } from './validation/error.js';
|
|
11
|
-
export { number } from './validation/number.js';
|
|
12
12
|
export { integer } from './validation/integer.js';
|
|
13
|
-
export {
|
|
13
|
+
export { array } from './validation/modifiable.js';
|
|
14
|
+
export { _null as null } from './validation/null.js';
|
|
15
|
+
export { number } from './validation/number.js';
|
|
16
|
+
export { laxObject, object } from './validation/object.js';
|
|
17
|
+
export { raw } from './validation/raw.js';
|
|
14
18
|
export { record } from './validation/record.js';
|
|
15
19
|
export { Schema } from './validation/schema.js';
|
|
16
|
-
export { BodyType, type Typeof } from './validation/body.js';
|
|
17
|
-
export { raw } from './validation/raw.js';
|
|
18
20
|
export { stream } from './validation/stream.js';
|
|
19
|
-
export { either } from './validation/either.js';
|
|
20
21
|
export { string } from './validation/string.js';
|
|
21
22
|
export { union } from './validation/union.js';
|
|
22
23
|
export { unknown } from './validation/unknown.js';
|
package/dist/lib.js
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
import { Path } from './routing/path.js';
|
|
2
2
|
// routing
|
|
3
|
-
export {
|
|
3
|
+
export { BadRequestError, ConflictError, ForbiddenError, HttpError, NotFoundError, PaymentRequiredError, UnauthorizedError, } from './routing/errors.js';
|
|
4
4
|
export { Log } from './routing/log.js';
|
|
5
5
|
export const validatePath = (path) => {
|
|
6
6
|
new Path(path);
|
|
7
7
|
};
|
|
8
8
|
export { parseEnv } from './routing/env.js';
|
|
9
|
-
|
|
10
|
-
export {
|
|
9
|
+
export { SecurityScheme } from './util/security.js';
|
|
10
|
+
export { BodyType } from './validation/body.js';
|
|
11
11
|
export { boolean } from './validation/boolean.js';
|
|
12
12
|
export { date } from './validation/date.js';
|
|
13
|
+
export { either } from './validation/either.js';
|
|
13
14
|
export { _enum as enum } from './validation/enum.js';
|
|
14
|
-
export { _null as null } from './validation/null.js';
|
|
15
15
|
export { ValidationError } from './validation/error.js';
|
|
16
|
-
export { number } from './validation/number.js';
|
|
17
16
|
export { integer } from './validation/integer.js';
|
|
18
|
-
|
|
17
|
+
// validation
|
|
18
|
+
export { array } from './validation/modifiable.js';
|
|
19
|
+
export { _null as null } from './validation/null.js';
|
|
20
|
+
export { number } from './validation/number.js';
|
|
21
|
+
export { laxObject, object } from './validation/object.js';
|
|
22
|
+
export { raw } from './validation/raw.js';
|
|
19
23
|
export { record } from './validation/record.js';
|
|
20
24
|
export { Schema } from './validation/schema.js';
|
|
21
|
-
export { BodyType } from './validation/body.js';
|
|
22
|
-
export { raw } from './validation/raw.js';
|
|
23
25
|
export { stream } from './validation/stream.js';
|
|
24
|
-
export { either } from './validation/either.js';
|
|
25
26
|
export { string } from './validation/string.js';
|
|
26
27
|
export { union } from './validation/union.js';
|
|
27
28
|
export { unknown } from './validation/unknown.js';
|
package/dist/routing/app.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { IncomingMessage, Server, ServerResponse } from 'node:http';
|
|
2
2
|
import { WebSocketServer } from 'ws';
|
|
3
3
|
import { Counter } from '../util/counter.js';
|
|
4
|
-
import type { SecurityScheme } from '../util/security.js';
|
|
5
4
|
import { RestEndpoint } from './rest.js';
|
|
6
5
|
import { WsEndpoint } from './websocket.js';
|
|
7
6
|
declare class Context {
|
|
@@ -23,27 +22,35 @@ type ServeConfig = {
|
|
|
23
22
|
dir: string;
|
|
24
23
|
fallback?: string | ServeFallback;
|
|
25
24
|
};
|
|
25
|
+
type DocsData = {
|
|
26
|
+
/**
|
|
27
|
+
* The title of your API.
|
|
28
|
+
*/
|
|
29
|
+
title: string;
|
|
30
|
+
/**
|
|
31
|
+
* The description of your API.
|
|
32
|
+
*/
|
|
33
|
+
description: string;
|
|
34
|
+
/**
|
|
35
|
+
* The current version of your API.
|
|
36
|
+
*/
|
|
37
|
+
version: string;
|
|
38
|
+
/**
|
|
39
|
+
* The list of servers your API is reachable under.
|
|
40
|
+
*/
|
|
41
|
+
servers?: {
|
|
42
|
+
description: string;
|
|
43
|
+
url: string;
|
|
44
|
+
}[];
|
|
45
|
+
};
|
|
26
46
|
type ConnectMiddleware = (req: IncomingMessage, res: ServerResponse, next: () => void) => void;
|
|
27
47
|
export declare class Yedra {
|
|
28
48
|
private restRoutes;
|
|
29
49
|
private wsRoutes;
|
|
30
50
|
private requestData;
|
|
51
|
+
private generatedDocs;
|
|
31
52
|
use(path: string, endpoint: RestEndpoint | WsEndpoint | Yedra): Yedra;
|
|
32
|
-
|
|
33
|
-
* Generate OpenAPI documentation for the app.
|
|
34
|
-
*/
|
|
35
|
-
docs(options: {
|
|
36
|
-
info: {
|
|
37
|
-
title: string;
|
|
38
|
-
description: string;
|
|
39
|
-
version: string;
|
|
40
|
-
};
|
|
41
|
-
security?: Record<string, SecurityScheme>;
|
|
42
|
-
servers: {
|
|
43
|
-
description: string;
|
|
44
|
-
url: string;
|
|
45
|
-
}[];
|
|
46
|
-
}): object;
|
|
53
|
+
private generateDocs;
|
|
47
54
|
private static loadServe;
|
|
48
55
|
private performRequest;
|
|
49
56
|
private middlewareNext;
|
|
@@ -57,6 +64,11 @@ export declare class Yedra {
|
|
|
57
64
|
path: string;
|
|
58
65
|
get?: () => Promise<string> | string;
|
|
59
66
|
};
|
|
67
|
+
/**
|
|
68
|
+
* Configuration for the `/openapi.json` endpoint, which generates
|
|
69
|
+
* OpenAPI documentation.
|
|
70
|
+
*/
|
|
71
|
+
docs?: DocsData;
|
|
60
72
|
serve?: ServeConfig;
|
|
61
73
|
/**
|
|
62
74
|
* Prevents all normal output from Yedra. Mostly useful for tests.
|
package/dist/routing/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
2
2
|
import { createServer as createHttpServer } from 'node:http';
|
|
3
3
|
import { createServer as createHttpsServer } from 'node:https';
|
|
4
4
|
import { extname, join } from 'node:path';
|
|
@@ -43,7 +43,10 @@ export class Yedra {
|
|
|
43
43
|
if (endpoint instanceof Yedra) {
|
|
44
44
|
for (const route of endpoint.restRoutes) {
|
|
45
45
|
const newPath = route.path.withPrefix(path);
|
|
46
|
-
this.restRoutes.push({
|
|
46
|
+
this.restRoutes.push({
|
|
47
|
+
path: newPath,
|
|
48
|
+
endpoint: route.endpoint,
|
|
49
|
+
});
|
|
47
50
|
}
|
|
48
51
|
for (const route of endpoint.wsRoutes) {
|
|
49
52
|
const newPath = route.path.withPrefix(path);
|
|
@@ -61,10 +64,9 @@ export class Yedra {
|
|
|
61
64
|
}
|
|
62
65
|
return this;
|
|
63
66
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
docs(options) {
|
|
67
|
+
generateDocs(options) {
|
|
68
|
+
// this set will be filled with the security schemes from all endpoints
|
|
69
|
+
const securitySchemes = new Set();
|
|
68
70
|
const paths = {};
|
|
69
71
|
for (const route of this.restRoutes) {
|
|
70
72
|
if (route.endpoint.isHidden()) {
|
|
@@ -74,18 +76,25 @@ export class Yedra {
|
|
|
74
76
|
const path = route.path.toString();
|
|
75
77
|
const methods = paths[path] ?? {};
|
|
76
78
|
methods[route.endpoint.method.toLowerCase()] =
|
|
77
|
-
route.endpoint.documentation(path,
|
|
79
|
+
route.endpoint.documentation(path, securitySchemes);
|
|
78
80
|
paths[path] = methods;
|
|
79
81
|
}
|
|
80
|
-
|
|
82
|
+
this.generatedDocs = JSON.stringify({
|
|
81
83
|
openapi: '3.0.2',
|
|
82
|
-
info:
|
|
84
|
+
info: {
|
|
85
|
+
title: options?.title ?? 'Yedra API',
|
|
86
|
+
description: options?.description ??
|
|
87
|
+
'This is an OpenAPI documentation generated automatically by Yedra.',
|
|
88
|
+
version: options?.version ?? '0.1.0',
|
|
89
|
+
},
|
|
83
90
|
components: {
|
|
84
|
-
securitySchemes:
|
|
91
|
+
securitySchemes: Object.fromEntries(securitySchemes
|
|
92
|
+
.values()
|
|
93
|
+
.map((scheme) => [scheme.name, scheme.scheme])),
|
|
85
94
|
},
|
|
86
|
-
servers: options
|
|
95
|
+
servers: options?.servers ?? [],
|
|
87
96
|
paths,
|
|
88
|
-
};
|
|
97
|
+
});
|
|
89
98
|
}
|
|
90
99
|
static async loadServe(config) {
|
|
91
100
|
if (config === undefined) {
|
|
@@ -137,6 +146,19 @@ export class Yedra {
|
|
|
137
146
|
req.method !== 'DELETE') {
|
|
138
147
|
return Yedra.errorResponse(405, `Method \`${req.method}\` not allowed.`);
|
|
139
148
|
}
|
|
149
|
+
if (req.method === 'GET' && req.url.pathname === '/openapi.json') {
|
|
150
|
+
if (this.generatedDocs === undefined) {
|
|
151
|
+
console.error('Docs were not generated correctly.');
|
|
152
|
+
return Yedra.errorResponse(500, 'Internal Server Error');
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
status: 200,
|
|
156
|
+
body: Buffer.from(this.generatedDocs ?? '{}', 'utf-8'),
|
|
157
|
+
headers: {
|
|
158
|
+
'content-type': 'application/json',
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
140
162
|
const match = this.matchRestRoute(req.url.pathname, req.method);
|
|
141
163
|
if (!match.result) {
|
|
142
164
|
// no matching route found
|
|
@@ -155,7 +177,9 @@ export class Yedra {
|
|
|
155
177
|
}
|
|
156
178
|
if (serveData.fallback !== undefined) {
|
|
157
179
|
try {
|
|
158
|
-
const response = await serveData.fallback({
|
|
180
|
+
const response = await serveData.fallback({
|
|
181
|
+
href: req.url.href,
|
|
182
|
+
});
|
|
159
183
|
return {
|
|
160
184
|
status: response.status ?? 200,
|
|
161
185
|
body: isUint8Array(response.body)
|
|
@@ -211,6 +235,7 @@ export class Yedra {
|
|
|
211
235
|
}
|
|
212
236
|
async listen(port, options) {
|
|
213
237
|
const serveData = await Yedra.loadServe(options?.serve);
|
|
238
|
+
this.generateDocs(options?.docs);
|
|
214
239
|
const server = options?.tls === undefined
|
|
215
240
|
? createHttpServer()
|
|
216
241
|
: createHttpsServer({
|
|
@@ -320,7 +345,7 @@ export class Yedra {
|
|
|
320
345
|
}
|
|
321
346
|
matchRestRoute(url, method) {
|
|
322
347
|
let invalidMethod = false;
|
|
323
|
-
let result
|
|
348
|
+
let result;
|
|
324
349
|
for (const route of this.restRoutes) {
|
|
325
350
|
const match = route.path.match(url);
|
|
326
351
|
if (match === undefined) {
|
|
@@ -340,7 +365,7 @@ export class Yedra {
|
|
|
340
365
|
return { invalidMethod, result };
|
|
341
366
|
}
|
|
342
367
|
matchWsRoute(url) {
|
|
343
|
-
let result
|
|
368
|
+
let result;
|
|
344
369
|
for (const route of this.wsRoutes) {
|
|
345
370
|
const match = route.path.match(url);
|
|
346
371
|
if (match === undefined) {
|
package/dist/routing/path.js
CHANGED
|
@@ -19,7 +19,7 @@ export class Path {
|
|
|
19
19
|
.substring(1)
|
|
20
20
|
.split('/')
|
|
21
21
|
.filter((segment) => segment !== '');
|
|
22
|
-
const invalidSegment = this.expected.find((part) => part.match(/^((:?[A-Za-z0-9
|
|
22
|
+
const invalidSegment = this.expected.find((part) => part.match(/^((:?[A-Za-z0-9\-.]+\??)|\*)$/) === null);
|
|
23
23
|
if (invalidSegment) {
|
|
24
24
|
throw new Error(`API path ${path} is invalid: Segment ${invalidSegment} does not match regex /^((:?[A-Za-z0-9-\.]+\\??)|\\*)$/.`);
|
|
25
25
|
}
|
package/dist/routing/rest.d.ts
CHANGED
|
@@ -25,10 +25,9 @@ type EndpointOptions<Params extends Record<string, Schema<unknown>>, Query exten
|
|
|
25
25
|
summary: string;
|
|
26
26
|
description?: string;
|
|
27
27
|
/**
|
|
28
|
-
* List of security
|
|
29
|
-
* Security definitions need to be included in the `app.docs()` call.
|
|
28
|
+
* List of security schemes that apply to this endpoint.
|
|
30
29
|
*/
|
|
31
|
-
security?:
|
|
30
|
+
security?: SecurityScheme[];
|
|
32
31
|
/**
|
|
33
32
|
* Whether this endpoint should be excluded from the documentation.
|
|
34
33
|
* Default is false.
|
|
@@ -55,8 +54,13 @@ export declare abstract class RestEndpoint {
|
|
|
55
54
|
headers?: Record<string, string>;
|
|
56
55
|
}>;
|
|
57
56
|
abstract isHidden(): boolean;
|
|
58
|
-
abstract documentation(path: string, securitySchemes:
|
|
57
|
+
abstract documentation(path: string, securitySchemes: Set<SecurityScheme>): object;
|
|
59
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* This class implements all REST endpoints in yedra. Its parent class,
|
|
61
|
+
* `RestEndpoint`, is not abstract because there are multiple implementations,
|
|
62
|
+
* but so that we can hide all the generic parameters of this class.
|
|
63
|
+
*/
|
|
60
64
|
declare class ConcreteRestEndpoint<Params extends Record<string, Schema<unknown>>, Query extends Record<string, Schema<unknown>>, Headers extends Record<string, Schema<unknown>>, Req extends BodyType<unknown>, Res extends BodyType<unknown>> extends RestEndpoint {
|
|
61
65
|
private _method;
|
|
62
66
|
private options;
|
|
@@ -77,7 +81,7 @@ declare class ConcreteRestEndpoint<Params extends Record<string, Schema<unknown>
|
|
|
77
81
|
headers?: Record<string, string>;
|
|
78
82
|
}>;
|
|
79
83
|
isHidden(): boolean;
|
|
80
|
-
documentation(path: string, securitySchemes:
|
|
84
|
+
documentation(path: string, securitySchemes: Set<SecurityScheme>): object;
|
|
81
85
|
}
|
|
82
86
|
export declare class Get<Params extends Record<string, Schema<unknown>>, Query extends Record<string, Schema<unknown>>, Headers extends Record<string, Schema<unknown>>, Res extends BodyType<unknown>> extends ConcreteRestEndpoint<Params, Query, Headers, NoneBody, Res> {
|
|
83
87
|
constructor(options: Omit<EndpointOptions<Params, Query, Headers, NoneBody, Res>, 'req'>);
|
package/dist/routing/rest.js
CHANGED
|
@@ -5,6 +5,11 @@ import { laxObject, object } from '../validation/object.js';
|
|
|
5
5
|
import { BadRequestError } from './errors.js';
|
|
6
6
|
export class RestEndpoint {
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* This class implements all REST endpoints in yedra. Its parent class,
|
|
10
|
+
* `RestEndpoint`, is not abstract because there are multiple implementations,
|
|
11
|
+
* but so that we can hide all the generic parameters of this class.
|
|
12
|
+
*/
|
|
8
13
|
class ConcreteRestEndpoint extends RestEndpoint {
|
|
9
14
|
constructor(method, options) {
|
|
10
15
|
super();
|
|
@@ -92,19 +97,22 @@ class ConcreteRestEndpoint extends RestEndpoint {
|
|
|
92
97
|
return this.options.hidden ?? false;
|
|
93
98
|
}
|
|
94
99
|
documentation(path, securitySchemes) {
|
|
100
|
+
const security = this.options.security ?? [];
|
|
101
|
+
for (const scheme of security) {
|
|
102
|
+
// add all our security schemes to the global list of schemes
|
|
103
|
+
securitySchemes.add(scheme);
|
|
104
|
+
}
|
|
95
105
|
const parameters = [
|
|
96
|
-
...paramDocs(this.options.params, 'path',
|
|
97
|
-
...paramDocs(this.options.query, 'query',
|
|
98
|
-
...paramDocs(this.options.headers, 'header',
|
|
106
|
+
...paramDocs(this.options.params, 'path', security),
|
|
107
|
+
...paramDocs(this.options.query, 'query', security),
|
|
108
|
+
...paramDocs(this.options.headers, 'header', security),
|
|
99
109
|
];
|
|
100
110
|
return {
|
|
101
111
|
tags: [this.options.category],
|
|
102
112
|
summary: this.options.summary,
|
|
103
113
|
description: this.options.description,
|
|
104
114
|
operationId: `${path.substring(1).replaceAll('/', '_')}_${this.method.toLowerCase()}`,
|
|
105
|
-
security:
|
|
106
|
-
? this.options.security.map((security) => ({ [security]: [] }))
|
|
107
|
-
: [],
|
|
115
|
+
security: security.map((security) => ({ [security.name]: [] })),
|
|
108
116
|
parameters,
|
|
109
117
|
requestBody: this.options.req instanceof NoneBody
|
|
110
118
|
? undefined
|
|
@@ -89,8 +89,8 @@ export class Ws extends WsEndpoint {
|
|
|
89
89
|
}
|
|
90
90
|
documentation() {
|
|
91
91
|
const parameters = [
|
|
92
|
-
...paramDocs(this.options.params, 'path', []
|
|
93
|
-
...paramDocs(this.options.query, 'query', []
|
|
92
|
+
...paramDocs(this.options.params, 'path', []),
|
|
93
|
+
...paramDocs(this.options.query, 'query', []),
|
|
94
94
|
];
|
|
95
95
|
return {
|
|
96
96
|
tags: [this.options.category],
|
package/dist/util/docs.d.ts
CHANGED
|
@@ -6,4 +6,4 @@ import type { SecurityScheme } from './security.js';
|
|
|
6
6
|
* @param position - The position of the parameter, e.g. `path`, `query`, `header`. This is passed directly to OpenAPI.
|
|
7
7
|
* @returns A list of parameter documentations in OpenAPI format.
|
|
8
8
|
*/
|
|
9
|
-
export declare const paramDocs: <Params extends Record<string, Schema<unknown>>>(params: Params, position: "path" | "query" | "header", security:
|
|
9
|
+
export declare const paramDocs: <Params extends Record<string, Schema<unknown>>>(params: Params, position: "path" | "query" | "header", security: SecurityScheme[]) => object[];
|
package/dist/util/docs.js
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* @param position - The position of the parameter, e.g. `path`, `query`, `header`. This is passed directly to OpenAPI.
|
|
5
5
|
* @returns A list of parameter documentations in OpenAPI format.
|
|
6
6
|
*/
|
|
7
|
-
export const paramDocs = (params, position, security
|
|
7
|
+
export const paramDocs = (params, position, security) => {
|
|
8
8
|
const result = [];
|
|
9
9
|
for (const name in params) {
|
|
10
|
-
if (isAuthToken(name, position, security
|
|
10
|
+
if (isAuthToken(name, position, security)) {
|
|
11
11
|
// don't include auth tokens in the documentation, they're already
|
|
12
12
|
// handled by the separate authentication feature of OpenAPI
|
|
13
13
|
continue;
|
|
@@ -32,16 +32,13 @@ export const paramDocs = (params, position, security, securitySchemes) => {
|
|
|
32
32
|
* @param securitySchemes - The security scheme definitions.
|
|
33
33
|
* @returns Whether the parameter is an auth token.
|
|
34
34
|
*/
|
|
35
|
-
const isAuthToken = (paramName, position,
|
|
35
|
+
const isAuthToken = (paramName, position, securitySchemes) => {
|
|
36
36
|
if (position === 'path') {
|
|
37
|
-
// parameters cannot be auth tokens
|
|
37
|
+
// path parameters cannot be auth tokens
|
|
38
38
|
return false;
|
|
39
39
|
}
|
|
40
|
-
for (const
|
|
41
|
-
if (
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
if (securityScheme.type === 'http') {
|
|
40
|
+
for (const scheme of securitySchemes) {
|
|
41
|
+
if (scheme.scheme.type === 'http') {
|
|
45
42
|
if (paramName.toLowerCase() === 'authorization' &&
|
|
46
43
|
position === 'header') {
|
|
47
44
|
// http auth token has to be in authorization header
|
|
@@ -49,8 +46,9 @@ const isAuthToken = (paramName, position, security, securitySchemes) => {
|
|
|
49
46
|
}
|
|
50
47
|
}
|
|
51
48
|
else {
|
|
52
|
-
if (
|
|
53
|
-
|
|
49
|
+
if (scheme.scheme.in === position &&
|
|
50
|
+
paramName.toLowerCase() === scheme.scheme.name.toLowerCase()) {
|
|
51
|
+
// API key has to have the correct name and be in the correct position
|
|
54
52
|
return true;
|
|
55
53
|
}
|
|
56
54
|
}
|
package/dist/util/security.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
export
|
|
1
|
+
export declare class SecurityScheme {
|
|
2
|
+
name: string;
|
|
3
|
+
scheme: SecuritySchemeData;
|
|
4
|
+
constructor(name: string, scheme: SecuritySchemeData);
|
|
5
|
+
}
|
|
6
|
+
type SecuritySchemeData = {
|
|
2
7
|
type: 'http';
|
|
3
8
|
scheme: 'basic' | 'bearer';
|
|
4
9
|
} | {
|
|
@@ -6,3 +11,4 @@ export type SecurityScheme = {
|
|
|
6
11
|
in: 'header' | 'query' | 'cookie';
|
|
7
12
|
name: string;
|
|
8
13
|
};
|
|
14
|
+
export {};
|
package/dist/util/security.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Typeof } from './body.js';
|
|
2
2
|
import { DocSchema } from './doc.js';
|
|
3
3
|
import { Schema } from './schema.js';
|
|
4
4
|
export declare abstract class ModifiableSchema<T> extends Schema<T> {
|
|
@@ -27,4 +27,34 @@ declare class OptionalSchema<T> extends Schema<T | undefined> {
|
|
|
27
27
|
documentation(): object;
|
|
28
28
|
isOptional(): boolean;
|
|
29
29
|
}
|
|
30
|
+
export declare class ArraySchema<ItemSchema extends Schema<unknown>> extends ModifiableSchema<Typeof<ItemSchema>[]> {
|
|
31
|
+
private readonly itemSchema;
|
|
32
|
+
private readonly minItems?;
|
|
33
|
+
private readonly maxItems?;
|
|
34
|
+
constructor(itemSchema: ItemSchema, minItems?: number, maxItems?: number);
|
|
35
|
+
/**
|
|
36
|
+
* Set the minimum number of items for arrays.
|
|
37
|
+
* @param items - The minimum number of items.
|
|
38
|
+
*/
|
|
39
|
+
min(items: number): ArraySchema<ItemSchema>;
|
|
40
|
+
/**
|
|
41
|
+
* Set the maximum number of items for arrays.
|
|
42
|
+
* @param items - The maximum number of items.
|
|
43
|
+
*/
|
|
44
|
+
max(items: number): ArraySchema<ItemSchema>;
|
|
45
|
+
/**
|
|
46
|
+
* Set the exact number of items for arrays.
|
|
47
|
+
* This is equivalent to calling both min and max.
|
|
48
|
+
* @param items - The number of items.
|
|
49
|
+
*/
|
|
50
|
+
length(items: number): ArraySchema<ItemSchema>;
|
|
51
|
+
parse(obj: unknown): Typeof<ItemSchema>[];
|
|
52
|
+
documentation(): object;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A schema matching arrays of the provided item type.
|
|
56
|
+
* @param itemSchema - The schema for array items.
|
|
57
|
+
* @deprecated Use the .array() method instead.
|
|
58
|
+
*/
|
|
59
|
+
export declare const array: <ItemSchema extends Schema<unknown>>(itemSchema: ItemSchema) => ArraySchema<ItemSchema>;
|
|
30
60
|
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DocSchema } from './doc.js';
|
|
2
|
+
import { Issue, ValidationError } from './error.js';
|
|
2
3
|
import { Schema } from './schema.js';
|
|
3
4
|
export class ModifiableSchema extends Schema {
|
|
4
5
|
/**
|
|
@@ -11,7 +12,7 @@ export class ModifiableSchema extends Schema {
|
|
|
11
12
|
return new DocSchema(this, description, example);
|
|
12
13
|
}
|
|
13
14
|
array() {
|
|
14
|
-
return new
|
|
15
|
+
return new ArraySchema(this);
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
class OptionalSchema extends Schema {
|
|
@@ -31,7 +32,7 @@ class OptionalSchema extends Schema {
|
|
|
31
32
|
return new DocSchema(this, description, example);
|
|
32
33
|
}
|
|
33
34
|
array() {
|
|
34
|
-
return new
|
|
35
|
+
return new ArraySchema(this);
|
|
35
36
|
}
|
|
36
37
|
parse(obj) {
|
|
37
38
|
if (obj === undefined || obj === null) {
|
|
@@ -46,4 +47,83 @@ class OptionalSchema extends Schema {
|
|
|
46
47
|
return true;
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
|
-
|
|
50
|
+
export class ArraySchema extends ModifiableSchema {
|
|
51
|
+
constructor(itemSchema, minItems, maxItems) {
|
|
52
|
+
super();
|
|
53
|
+
this.itemSchema = itemSchema;
|
|
54
|
+
this.minItems = minItems;
|
|
55
|
+
this.maxItems = maxItems;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Set the minimum number of items for arrays.
|
|
59
|
+
* @param items - The minimum number of items.
|
|
60
|
+
*/
|
|
61
|
+
min(items) {
|
|
62
|
+
return new ArraySchema(this.itemSchema, items, this.maxItems);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Set the maximum number of items for arrays.
|
|
66
|
+
* @param items - The maximum number of items.
|
|
67
|
+
*/
|
|
68
|
+
max(items) {
|
|
69
|
+
return new ArraySchema(this.itemSchema, this.minItems, items);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Set the exact number of items for arrays.
|
|
73
|
+
* This is equivalent to calling both min and max.
|
|
74
|
+
* @param items - The number of items.
|
|
75
|
+
*/
|
|
76
|
+
length(items) {
|
|
77
|
+
return new ArraySchema(this.itemSchema, items, items);
|
|
78
|
+
}
|
|
79
|
+
parse(obj) {
|
|
80
|
+
if (!Array.isArray(obj)) {
|
|
81
|
+
throw new ValidationError([
|
|
82
|
+
new Issue([], `Expected array but got ${typeof obj}`),
|
|
83
|
+
]);
|
|
84
|
+
}
|
|
85
|
+
if (this.minItems && obj.length < this.minItems) {
|
|
86
|
+
throw new ValidationError([
|
|
87
|
+
new Issue([], `Must have at least ${this.minItems} items, but has ${obj.length}`),
|
|
88
|
+
]);
|
|
89
|
+
}
|
|
90
|
+
if (this.maxItems && obj.length > this.maxItems) {
|
|
91
|
+
throw new ValidationError([
|
|
92
|
+
new Issue([], `Must have at most ${this.maxItems} items, but has ${obj.length}`),
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
95
|
+
const elems = [];
|
|
96
|
+
const issues = [];
|
|
97
|
+
for (let i = 0; i < obj.length; ++i) {
|
|
98
|
+
try {
|
|
99
|
+
elems.push(this.itemSchema.parse(obj[i]));
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error instanceof ValidationError) {
|
|
103
|
+
issues.push(...error.withPrefix(i.toString()));
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (issues.length > 0) {
|
|
111
|
+
throw new ValidationError(issues);
|
|
112
|
+
}
|
|
113
|
+
return elems;
|
|
114
|
+
}
|
|
115
|
+
documentation() {
|
|
116
|
+
return {
|
|
117
|
+
type: 'array',
|
|
118
|
+
items: this.itemSchema.documentation(),
|
|
119
|
+
minItems: this.minItems,
|
|
120
|
+
maxItems: this.maxItems,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* A schema matching arrays of the provided item type.
|
|
126
|
+
* @param itemSchema - The schema for array items.
|
|
127
|
+
* @deprecated Use the .array() method instead.
|
|
128
|
+
*/
|
|
129
|
+
export const array = (itemSchema) => new ArraySchema(itemSchema);
|
package/package.json
CHANGED
|
@@ -1,30 +1,41 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
2
|
+
"name": "yedra",
|
|
3
|
+
"version": "0.16.0",
|
|
4
|
+
"repository": "github:0codekit/yedra",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"devDependencies": {
|
|
7
|
+
"@biomejs/biome": "^2.0.6",
|
|
8
|
+
"@types/bun": "^1.2.17",
|
|
9
|
+
"@types/node": "^24.0.7",
|
|
10
|
+
"@types/uuid": "^10.0.0",
|
|
11
|
+
"@types/ws": "^8.18.1",
|
|
12
|
+
"typescript": "^5.8.3"
|
|
13
|
+
},
|
|
14
|
+
"bugs": "https://github.com/0codekit/yedra/issues",
|
|
15
|
+
"contributors": [
|
|
16
|
+
"Justus Zorn <jzorn@wemakefuture.com>"
|
|
17
|
+
],
|
|
18
|
+
"description": "A TypeScript web framework with OpenAPI generation.",
|
|
19
|
+
"keywords": [
|
|
20
|
+
"typescript",
|
|
21
|
+
"web",
|
|
22
|
+
"http",
|
|
23
|
+
"schema",
|
|
24
|
+
"validation",
|
|
25
|
+
"openapi"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"files": [
|
|
29
|
+
"./dist/**"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"check": "biome check src/*"
|
|
33
|
+
},
|
|
34
|
+
"types": "dist/index.d.ts",
|
|
35
|
+
"type": "module",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"mime": "^4.0.7",
|
|
38
|
+
"uuid": "^11.1.0",
|
|
39
|
+
"ws": "^8.18.3"
|
|
40
|
+
}
|
|
30
41
|
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { Typeof } from './body.js';
|
|
2
|
-
import { ModifiableSchema } from './modifiable.js';
|
|
3
|
-
import type { Schema } from './schema.js';
|
|
4
|
-
export declare class ArraySchema<ItemSchema extends Schema<unknown>> extends ModifiableSchema<Typeof<ItemSchema>[]> {
|
|
5
|
-
private readonly itemSchema;
|
|
6
|
-
private readonly minItems?;
|
|
7
|
-
private readonly maxItems?;
|
|
8
|
-
constructor(itemSchema: ItemSchema, minItems?: number, maxItems?: number);
|
|
9
|
-
/**
|
|
10
|
-
* Set the minimum number of items for arrays.
|
|
11
|
-
* @param items - The minimum number of items.
|
|
12
|
-
*/
|
|
13
|
-
min(items: number): ArraySchema<ItemSchema>;
|
|
14
|
-
/**
|
|
15
|
-
* Set the maximum number of items for arrays.
|
|
16
|
-
* @param items - The maximum number of items.
|
|
17
|
-
*/
|
|
18
|
-
max(items: number): ArraySchema<ItemSchema>;
|
|
19
|
-
/**
|
|
20
|
-
* Set the exact number of items for arrays.
|
|
21
|
-
* This is equivalent to calling both min and max.
|
|
22
|
-
* @param items - The number of items.
|
|
23
|
-
*/
|
|
24
|
-
length(items: number): ArraySchema<ItemSchema>;
|
|
25
|
-
parse(obj: unknown): Typeof<ItemSchema>[];
|
|
26
|
-
documentation(): object;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* A schema matching arrays of the provided item type.
|
|
30
|
-
* @param itemSchema - The schema for array items.
|
|
31
|
-
* @deprecated Use the .array() method instead.
|
|
32
|
-
*/
|
|
33
|
-
export declare const array: <ItemSchema extends Schema<unknown>>(itemSchema: ItemSchema) => ArraySchema<ItemSchema>;
|
package/dist/validation/array.js
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { Issue, ValidationError } from './error.js';
|
|
2
|
-
import { ModifiableSchema } from './modifiable.js';
|
|
3
|
-
export class ArraySchema extends ModifiableSchema {
|
|
4
|
-
constructor(itemSchema, minItems, maxItems) {
|
|
5
|
-
super();
|
|
6
|
-
this.itemSchema = itemSchema;
|
|
7
|
-
this.minItems = minItems;
|
|
8
|
-
this.maxItems = maxItems;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Set the minimum number of items for arrays.
|
|
12
|
-
* @param items - The minimum number of items.
|
|
13
|
-
*/
|
|
14
|
-
min(items) {
|
|
15
|
-
return new ArraySchema(this.itemSchema, items, this.maxItems);
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Set the maximum number of items for arrays.
|
|
19
|
-
* @param items - The maximum number of items.
|
|
20
|
-
*/
|
|
21
|
-
max(items) {
|
|
22
|
-
return new ArraySchema(this.itemSchema, this.minItems, items);
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Set the exact number of items for arrays.
|
|
26
|
-
* This is equivalent to calling both min and max.
|
|
27
|
-
* @param items - The number of items.
|
|
28
|
-
*/
|
|
29
|
-
length(items) {
|
|
30
|
-
return new ArraySchema(this.itemSchema, items, items);
|
|
31
|
-
}
|
|
32
|
-
parse(obj) {
|
|
33
|
-
if (!Array.isArray(obj)) {
|
|
34
|
-
throw new ValidationError([
|
|
35
|
-
new Issue([], `Expected array but got ${typeof obj}`),
|
|
36
|
-
]);
|
|
37
|
-
}
|
|
38
|
-
if (this.minItems && obj.length < this.minItems) {
|
|
39
|
-
throw new ValidationError([
|
|
40
|
-
new Issue([], `Must have at least ${this.minItems} items, but has ${obj.length}`),
|
|
41
|
-
]);
|
|
42
|
-
}
|
|
43
|
-
if (this.maxItems && obj.length > this.maxItems) {
|
|
44
|
-
throw new ValidationError([
|
|
45
|
-
new Issue([], `Must have at most ${this.maxItems} items, but has ${obj.length}`),
|
|
46
|
-
]);
|
|
47
|
-
}
|
|
48
|
-
const elems = [];
|
|
49
|
-
const issues = [];
|
|
50
|
-
for (let i = 0; i < obj.length; ++i) {
|
|
51
|
-
try {
|
|
52
|
-
elems.push(this.itemSchema.parse(obj[i]));
|
|
53
|
-
}
|
|
54
|
-
catch (error) {
|
|
55
|
-
if (error instanceof ValidationError) {
|
|
56
|
-
issues.push(...error.withPrefix(i.toString()));
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
throw error;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (issues.length > 0) {
|
|
64
|
-
throw new ValidationError(issues);
|
|
65
|
-
}
|
|
66
|
-
return elems;
|
|
67
|
-
}
|
|
68
|
-
documentation() {
|
|
69
|
-
return {
|
|
70
|
-
type: 'array',
|
|
71
|
-
items: this.itemSchema.documentation(),
|
|
72
|
-
minItems: this.minItems,
|
|
73
|
-
maxItems: this.maxItems,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* A schema matching arrays of the provided item type.
|
|
79
|
-
* @param itemSchema - The schema for array items.
|
|
80
|
-
* @deprecated Use the .array() method instead.
|
|
81
|
-
*/
|
|
82
|
-
export const array = (itemSchema) => new ArraySchema(itemSchema);
|