yedra 0.13.15 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +1 -3
  2. package/dist/lib.d.ts +2 -2
  3. package/dist/lib.js +2 -2
  4. package/dist/routing/app.d.ts +23 -26
  5. package/dist/routing/app.js +126 -90
  6. package/dist/routing/path.js +4 -1
  7. package/dist/routing/rest.d.ts +23 -2
  8. package/dist/routing/rest.js +11 -20
  9. package/dist/util/counter.d.ts +7 -0
  10. package/dist/util/counter.js +24 -0
  11. package/dist/util/stream.d.ts +2 -0
  12. package/dist/util/stream.js +7 -0
  13. package/dist/validation/array.d.ts +2 -2
  14. package/dist/validation/array.js +5 -4
  15. package/dist/validation/body.d.ts +4 -3
  16. package/dist/validation/boolean.js +1 -1
  17. package/dist/validation/date.js +1 -1
  18. package/dist/validation/either.d.ts +2 -1
  19. package/dist/validation/either.js +2 -2
  20. package/dist/validation/enum.js +2 -3
  21. package/dist/validation/error.d.ts +3 -9
  22. package/dist/validation/error.js +9 -30
  23. package/dist/validation/integer.js +4 -4
  24. package/dist/validation/modifiable.d.ts +7 -0
  25. package/dist/validation/modifiable.js +15 -0
  26. package/dist/validation/none.d.ts +2 -1
  27. package/dist/validation/none.js +2 -2
  28. package/dist/validation/number.js +4 -4
  29. package/dist/validation/object.d.ts +3 -1
  30. package/dist/validation/object.js +15 -5
  31. package/dist/validation/raw.d.ts +4 -3
  32. package/dist/validation/raw.js +5 -4
  33. package/dist/validation/record.js +7 -2
  34. package/dist/validation/schema.d.ts +2 -1
  35. package/dist/validation/schema.js +9 -4
  36. package/dist/validation/stream.d.ts +13 -0
  37. package/dist/validation/stream.js +26 -0
  38. package/dist/validation/string.js +3 -3
  39. package/dist/validation/uuid.js +1 -1
  40. package/package.json +1 -1
  41. package/dist/validation/intersection.d.ts +0 -15
  42. package/dist/validation/intersection.js +0 -51
package/README.md CHANGED
@@ -111,9 +111,7 @@ export const loginEndpoint = new Post({
111
111
  password: y.string(),
112
112
  }),
113
113
  res: y.object({
114
- token: y.string().doc({
115
- description: "The session token.",
116
- }),
114
+ token: y.string().describe("The session token."),
117
115
  }),
118
116
  async do(req) {
119
117
  if (await isValid(req.body.username, req.body.password)) {
package/dist/lib.d.ts CHANGED
@@ -7,14 +7,14 @@ export { boolean } from './validation/boolean.js';
7
7
  export { date } from './validation/date.js';
8
8
  export { _enum as enum } from './validation/enum.js';
9
9
  export { ValidationError } from './validation/error.js';
10
- export { intersection } from './validation/intersection.js';
11
10
  export { number } from './validation/number.js';
12
11
  export { integer } from './validation/integer.js';
13
- export { object } from './validation/object.js';
12
+ export { object, laxObject } from './validation/object.js';
14
13
  export { record } from './validation/record.js';
15
14
  export { Schema } from './validation/schema.js';
16
15
  export { BodyType, type Typeof } from './validation/body.js';
17
16
  export { raw } from './validation/raw.js';
17
+ export { stream } from './validation/stream.js';
18
18
  export { either } from './validation/either.js';
19
19
  export { string } from './validation/string.js';
20
20
  export { union } from './validation/union.js';
package/dist/lib.js CHANGED
@@ -12,14 +12,14 @@ export { boolean } from './validation/boolean.js';
12
12
  export { date } from './validation/date.js';
13
13
  export { _enum as enum } from './validation/enum.js';
14
14
  export { ValidationError } from './validation/error.js';
15
- export { intersection } from './validation/intersection.js';
16
15
  export { number } from './validation/number.js';
17
16
  export { integer } from './validation/integer.js';
18
- export { object } from './validation/object.js';
17
+ export { object, laxObject } from './validation/object.js';
19
18
  export { record } from './validation/record.js';
20
19
  export { Schema } from './validation/schema.js';
21
20
  export { BodyType } from './validation/body.js';
22
21
  export { raw } from './validation/raw.js';
22
+ export { stream } from './validation/stream.js';
23
23
  export { either } from './validation/either.js';
24
24
  export { string } from './validation/string.js';
25
25
  export { union } from './validation/union.js';
@@ -1,39 +1,21 @@
1
- import { type Server } from 'node:http';
2
- import { URL } from 'node:url';
1
+ import type { Server } from 'node:http';
2
+ import { WebSocketServer } from 'ws';
3
+ import { Counter } from '../util/counter.js';
3
4
  import type { SecurityScheme } from '../util/security.js';
4
5
  import { RestEndpoint } from './rest.js';
5
6
  import { WsEndpoint } from './websocket.js';
6
7
  declare class Context {
7
- private server;
8
- constructor(server: Server);
8
+ private readonly server;
9
+ private readonly wss;
10
+ private readonly counter;
11
+ constructor(server: Server, wss: WebSocketServer, counter: Counter);
9
12
  stop(): Promise<void>;
10
13
  }
11
- type MetricsConfig = {
12
- port: number;
13
- path: string;
14
- get?: () => Promise<string>;
15
- };
16
14
  export declare class Yedra {
17
15
  private restRoutes;
18
16
  private wsRoutes;
19
- private staticFiles;
20
17
  private requestData;
21
- private readonly metricsEndpoint;
22
- constructor(options?: {
23
- metrics: MetricsConfig;
24
- });
25
18
  use(path: string, endpoint: RestEndpoint | WsEndpoint | Yedra): Yedra;
26
- static(dir: string, fallback?: string): Promise<void>;
27
- /**
28
- * Handle an HTTP request.
29
- * @param req - The HTTP request.
30
- * @returns The HTTP response.
31
- */
32
- fetch(url: URL | string, options?: {
33
- method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
34
- body?: string | Buffer;
35
- headers?: Record<string, string>;
36
- }): Promise<Response>;
37
19
  /**
38
20
  * Generate OpenAPI documentation for the app.
39
21
  */
@@ -49,12 +31,27 @@ export declare class Yedra {
49
31
  url: string;
50
32
  }[];
51
33
  }): object;
34
+ private static loadStatic;
35
+ private performRequest;
52
36
  listen(port: number, options?: {
53
37
  tls?: {
54
38
  key: string;
55
39
  cert: string;
56
40
  };
57
- }): Context;
41
+ metrics?: {
42
+ port: number;
43
+ path: string;
44
+ get?: () => Promise<string> | string;
45
+ };
46
+ static?: {
47
+ dir: string;
48
+ fallback?: string;
49
+ };
50
+ /**
51
+ * Prevents all normal output from Yedra. Mostly useful for tests.
52
+ */
53
+ quiet?: boolean;
54
+ }): Promise<Context>;
58
55
  private static errorResponse;
59
56
  private matchRestRoute;
60
57
  private matchWsRoute;
@@ -5,27 +5,38 @@ import { extname, join } from 'node:path';
5
5
  import { URL } from 'node:url';
6
6
  import mime from 'mime';
7
7
  import { WebSocketServer } from 'ws';
8
+ import { Counter } from '../util/counter.js';
8
9
  import { HttpError } from './errors.js';
9
10
  import { Path } from './path.js';
10
11
  import { RestEndpoint } from './rest.js';
11
12
  import { WsEndpoint } from './websocket.js';
12
13
  class Context {
13
- constructor(server) {
14
+ constructor(server, wss, counter) {
14
15
  this.server = server;
16
+ this.wss = wss;
17
+ this.counter = counter;
15
18
  }
16
19
  stop() {
17
20
  return new Promise((resolve) => {
18
- this.server.close(() => resolve());
21
+ // don't accept any new connections
22
+ this.wss.close();
23
+ this.server.close();
24
+ for (const client of this.wss.clients) {
25
+ // send shutdown message to all WebSocket clients
26
+ client.close(1000, 'Server Shutdown');
27
+ }
28
+ // wait until all connections are done
29
+ this.counter.wait().then(() => {
30
+ resolve();
31
+ });
19
32
  });
20
33
  }
21
34
  }
22
35
  export class Yedra {
23
- constructor(options) {
36
+ constructor() {
24
37
  this.restRoutes = [];
25
38
  this.wsRoutes = [];
26
- this.staticFiles = new Map();
27
39
  this.requestData = {};
28
- this.metricsEndpoint = options?.metrics;
29
40
  }
30
41
  use(path, endpoint) {
31
42
  if (endpoint instanceof Yedra) {
@@ -49,62 +60,94 @@ export class Yedra {
49
60
  }
50
61
  return this;
51
62
  }
52
- async static(dir, fallback) {
53
- const files = await readdir(dir, { recursive: true });
63
+ /**
64
+ * Generate OpenAPI documentation for the app.
65
+ */
66
+ docs(options) {
67
+ const paths = {};
68
+ for (const route of this.restRoutes) {
69
+ const path = route.path.toString();
70
+ const methods = paths[path] ?? {};
71
+ methods[route.endpoint.method.toLowerCase()] =
72
+ route.endpoint.documentation(options.security ?? {});
73
+ paths[path] = methods;
74
+ }
75
+ return {
76
+ openapi: '3.0.2',
77
+ info: options.info,
78
+ components: {
79
+ securitySchemes: options.security,
80
+ },
81
+ servers: options.servers,
82
+ paths,
83
+ };
84
+ }
85
+ static async loadStatic(options) {
86
+ if (options === undefined) {
87
+ return new Map();
88
+ }
89
+ const staticFiles = new Map();
90
+ const files = await readdir(options.dir, { recursive: true });
54
91
  await Promise.all(files.map(async (file) => {
55
- const absolute = join(dir, file);
92
+ const absolute = join(options.dir, file);
56
93
  if (!(await stat(absolute)).isFile()) {
57
94
  return;
58
95
  }
59
96
  const data = await readFile(absolute);
60
- this.staticFiles.set(`/${file}`, {
97
+ staticFiles.set(`/${file}`, {
61
98
  data,
62
99
  mime: mime.getType(extname(file)) ?? 'application/octet-stream',
63
100
  });
64
101
  }));
65
- if (fallback) {
66
- const data = await readFile(fallback);
67
- this.staticFiles.set('__fallback', {
102
+ if (options.fallback) {
103
+ const data = await readFile(options.fallback);
104
+ staticFiles.set('__fallback', {
68
105
  data,
69
- mime: mime.getType(extname(fallback)) ?? 'application/octet-stream',
106
+ mime: mime.getType(extname(options.fallback)) ?? 'application/octet-stream',
70
107
  });
71
108
  }
109
+ return staticFiles;
72
110
  }
73
- /**
74
- * Handle an HTTP request.
75
- * @param req - The HTTP request.
76
- * @returns The HTTP response.
77
- */
78
- async fetch(url, options) {
79
- const parsedUrl = typeof url === 'string' ? new URL(url, 'http://localhost') : url;
80
- const method = options?.method ?? 'GET';
81
- if (method !== 'GET' &&
82
- method !== 'POST' &&
83
- method !== 'PUT' &&
84
- method !== 'DELETE') {
85
- return Yedra.errorResponse(405, `Method '${method}' not allowed.`);
111
+ async performRequest(staticFiles, req) {
112
+ if (req.method !== 'GET' &&
113
+ req.method !== 'POST' &&
114
+ req.method !== 'PUT' &&
115
+ req.method !== 'DELETE') {
116
+ return Yedra.errorResponse(405, `Method \`${req.method}\` not allowed.`);
86
117
  }
87
- const match = this.matchRestRoute(parsedUrl.pathname, method);
118
+ const match = this.matchRestRoute(req.url.pathname, req.method);
88
119
  if (!match.result) {
89
- if (method === 'GET') {
90
- // try returning a static file
91
- const staticFile = this.staticFiles.get(parsedUrl.pathname) ??
92
- this.staticFiles.get('__fallback');
120
+ // no matching route found
121
+ if (req.method === 'GET') {
122
+ // look for a static file
123
+ const staticFile = staticFiles.get(req.url.pathname) ?? staticFiles.get('__fallback');
93
124
  if (staticFile !== undefined) {
94
- return new Response(staticFile.data, {
125
+ return {
126
+ status: 200,
127
+ body: staticFile.data,
95
128
  headers: {
96
129
  'content-type': staticFile.mime,
97
130
  },
98
- });
131
+ };
99
132
  }
100
133
  }
101
134
  if (match.invalidMethod) {
102
- return Yedra.errorResponse(405, `Method '${method}' not allowed for path '${parsedUrl.pathname}'.`);
135
+ // we found a route, but it did not match the request method
136
+ return Yedra.errorResponse(405, `Method ${req.method} not allowed for path \`${req.url.pathname}\`.`);
103
137
  }
104
- return Yedra.errorResponse(404, `Path '${parsedUrl.pathname}' not found.`);
138
+ return Yedra.errorResponse(404, `Path \`${req.url.pathname}\` not found.`);
105
139
  }
106
140
  try {
107
- return await match.result.endpoint.handle(parsedUrl.pathname, options?.body ?? '', match.result.params, Object.fromEntries(parsedUrl.searchParams), options?.headers ?? {});
141
+ return await match.result.endpoint.handle({
142
+ url: req.url.pathname,
143
+ body: req.body,
144
+ params: match.result.params,
145
+ query: Object.fromEntries(req.url.searchParams),
146
+ headers: Object.fromEntries(Object.entries(req.headers).map(([key, value]) => [
147
+ key,
148
+ Array.isArray(value) ? value.join(',') : (value ?? ''),
149
+ ])),
150
+ });
108
151
  }
109
152
  catch (error) {
110
153
  if (error instanceof HttpError) {
@@ -114,58 +157,45 @@ export class Yedra {
114
157
  return Yedra.errorResponse(500, 'Internal Server Error.');
115
158
  }
116
159
  }
117
- /**
118
- * Generate OpenAPI documentation for the app.
119
- */
120
- docs(options) {
121
- const paths = {};
122
- for (const route of this.restRoutes) {
123
- const path = route.path.toString();
124
- const methods = paths[path] ?? {};
125
- methods[route.endpoint.method.toLowerCase()] =
126
- route.endpoint.documentation(options.security ?? {});
127
- paths[path] = methods;
128
- }
129
- return {
130
- openapi: '3.0.2',
131
- info: options.info,
132
- components: {
133
- securitySchemes: options.security,
134
- },
135
- servers: options.servers,
136
- paths,
137
- };
138
- }
139
- listen(port, options) {
160
+ async listen(port, options) {
161
+ const staticFiles = await Yedra.loadStatic(options?.static);
140
162
  const server = options?.tls === undefined
141
163
  ? createHttpServer()
142
164
  : createHttpsServer({
143
165
  key: options.tls.key,
144
166
  cert: options.tls.cert,
145
167
  });
146
- server.on('request', (req, res) => {
168
+ const counter = new Counter();
169
+ server.on('request', async (req, res) => {
170
+ counter.increment();
147
171
  const url = new URL(req.url, 'http://localhost');
148
172
  const begin = Date.now();
149
- const chunks = [];
150
- req.on('data', (chunk) => {
151
- chunks.push(chunk);
152
- });
153
- req.on('end', async () => {
154
- const body = chunks.length > 0 ? Buffer.concat(chunks) : undefined;
155
- const response = await this.fetch(url, {
156
- method: req.method,
157
- body: req.method === 'POST' || req.method === 'PUT' ? body : undefined,
158
- headers: Object.fromEntries(Object.entries(req.headers).map(([key, value]) => [
159
- key,
160
- Array.isArray(value) ? value.join(',') : (value ?? ''),
161
- ])),
162
- });
163
- res.writeHead(response.status, Object.fromEntries(response.headers));
164
- res.end(Buffer.from(await response.arrayBuffer()));
165
- const duration = Date.now() - begin;
166
- console.log(`${req.method} ${url.pathname} -> ${response.status} (${duration}ms)`);
167
- this.track(req.method, response.status, duration / 1000);
173
+ const response = await this.performRequest(staticFiles, {
174
+ method: req.method ?? 'GET',
175
+ url,
176
+ body: req,
177
+ headers: req.headers,
168
178
  });
179
+ const status = response.status ?? 200;
180
+ res.writeHead(status, response.headers);
181
+ if (response.body instanceof ReadableStream) {
182
+ for await (const chunk of response.body) {
183
+ res.write(chunk);
184
+ }
185
+ }
186
+ else {
187
+ const buffer = response.body instanceof Uint8Array
188
+ ? response.body
189
+ : JSON.stringify(response.body);
190
+ res.write(buffer);
191
+ }
192
+ res.end();
193
+ const duration = Date.now() - begin;
194
+ if (options?.quiet !== true) {
195
+ console.log(`${req.method} ${url.pathname} -> ${status} (${duration}ms)`);
196
+ }
197
+ this.track(req.method, status, duration / 1000);
198
+ counter.decrement();
169
199
  });
170
200
  const wss = new WebSocketServer({ server });
171
201
  wss.on('connection', async (ws, req) => {
@@ -183,15 +213,18 @@ export class Yedra {
183
213
  ws.close(4000 + error.status, error.message);
184
214
  }
185
215
  else {
186
- ws.close(1011, 'Internal Server Error');
216
+ console.error(error);
217
+ ws.close(1011, 'Internal Error');
187
218
  }
188
219
  }
189
220
  });
190
221
  server.listen(port, () => {
191
- console.log(`yedra listening on http://localhost:${port}`);
222
+ if (options?.quiet !== true) {
223
+ console.log(`yedra listening on http://localhost:${port}`);
224
+ }
192
225
  });
193
- const metricsEndpoint = this.metricsEndpoint;
194
- if (metricsEndpoint !== undefined) {
226
+ if (options?.metrics !== undefined) {
227
+ const metricsEndpoint = options.metrics;
195
228
  const metricsServer = createHttpServer();
196
229
  metricsServer.on('request', async (req, res) => {
197
230
  if (req.method === 'GET' && req.url === metricsEndpoint.path) {
@@ -208,18 +241,21 @@ export class Yedra {
208
241
  }
209
242
  });
210
243
  metricsServer.listen(metricsEndpoint.port, () => {
211
- console.log(`yedra metrics on http://localhost:${metricsEndpoint.port}${metricsEndpoint.path}`);
244
+ if (options.quiet !== true) {
245
+ console.log(`yedra metrics on http://localhost:${metricsEndpoint.port}${metricsEndpoint.path}`);
246
+ }
212
247
  });
213
248
  }
214
- return new Context(server);
249
+ return new Context(server, wss, counter);
215
250
  }
216
251
  static errorResponse(status, errorMessage) {
217
- return Response.json({
218
- status,
219
- errorMessage,
220
- }, {
252
+ return {
221
253
  status,
222
- });
254
+ body: {
255
+ status,
256
+ errorMessage,
257
+ },
258
+ };
223
259
  }
224
260
  matchRestRoute(url, method) {
225
261
  let invalidMethod = false;
@@ -58,7 +58,10 @@ export class Path {
58
58
  */
59
59
  match(path) {
60
60
  const params = {};
61
- const actual = path.substring(1).split('/');
61
+ const actual = path
62
+ .substring(1)
63
+ .split('/')
64
+ .filter((segment) => segment !== '');
62
65
  if (this.expected.length < actual.length && !this.expected.includes('*')) {
63
66
  // path cannot be longer than expected, unless it contains a wildcard
64
67
  return undefined;
@@ -1,3 +1,4 @@
1
+ import type { Readable } from 'node:stream';
1
2
  import type { SecurityScheme } from '../util/security.js';
2
3
  import type { BodyType, Typeof } from '../validation/body.js';
3
4
  import { NoneBody } from '../validation/none.js';
@@ -33,7 +34,17 @@ type EndpointOptions<Params extends Record<string, Schema<unknown>>, Query exten
33
34
  };
34
35
  export declare abstract class RestEndpoint {
35
36
  abstract get method(): 'GET' | 'POST' | 'PUT' | 'DELETE';
36
- abstract handle(pathname: string, body: string | Buffer, params: Record<string, string>, query: Record<string, string>, headers: Record<string, string>): Promise<Response>;
37
+ abstract handle(req: {
38
+ url: string;
39
+ body: Readable;
40
+ params: Record<string, string>;
41
+ query: Record<string, string>;
42
+ headers: Record<string, string>;
43
+ }): Promise<{
44
+ status?: number;
45
+ body: unknown;
46
+ headers?: Record<string, string>;
47
+ }>;
37
48
  abstract documentation(securitySchemes: Record<string, SecurityScheme>): object;
38
49
  }
39
50
  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 {
@@ -44,7 +55,17 @@ declare class ConcreteRestEndpoint<Params extends Record<string, Schema<unknown>
44
55
  private headersSchema;
45
56
  constructor(method: 'GET' | 'POST' | 'PUT' | 'DELETE', options: EndpointOptions<Params, Query, Headers, Req, Res>);
46
57
  get method(): 'GET' | 'POST' | 'PUT' | 'DELETE';
47
- handle(url: string, body: string | Buffer, params: Record<string, string>, query: Record<string, string>, headers: Record<string, string>): Promise<Response>;
58
+ handle(req: {
59
+ url: string;
60
+ body: Readable;
61
+ params: Record<string, string>;
62
+ query: Record<string, string>;
63
+ headers: Record<string, string>;
64
+ }): Promise<{
65
+ status?: number;
66
+ body: unknown;
67
+ headers?: Record<string, string>;
68
+ }>;
48
69
  documentation(securitySchemes: Record<string, SecurityScheme>): object;
49
70
  }
50
71
  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> {
@@ -1,7 +1,7 @@
1
1
  import { paramDocs } from '../util/docs.js';
2
2
  import { Issue, ValidationError } from '../validation/error.js';
3
3
  import { NoneBody, none } from '../validation/none.js';
4
- import { object } from '../validation/object.js';
4
+ import { laxObject, object } from '../validation/object.js';
5
5
  import { BadRequestError } from './errors.js';
6
6
  export class RestEndpoint {
7
7
  }
@@ -12,23 +12,24 @@ class ConcreteRestEndpoint extends RestEndpoint {
12
12
  this.options = options;
13
13
  this.paramsSchema = object(options.params);
14
14
  this.querySchema = object(options.query);
15
- this.headersSchema = object(options.headers);
15
+ // headers need to be lax, since there are lots of them
16
+ this.headersSchema = laxObject(options.headers);
16
17
  }
17
18
  get method() {
18
19
  return this._method;
19
20
  }
20
- async handle(url, body, params, query, headers) {
21
+ async handle(req) {
21
22
  let parsedBody;
22
23
  let parsedParams;
23
24
  let parsedQuery;
24
25
  let parsedHeaders;
25
26
  const issues = [];
26
27
  try {
27
- parsedBody = this.options.req.deserialize(typeof body === 'string' ? Buffer.from(body) : body, headers['content-type'] ?? 'application/octet-stream');
28
+ parsedBody = await this.options.req.deserialize(req.body, req.headers['content-type'] ?? 'application/octet-stream');
28
29
  }
29
30
  catch (error) {
30
31
  if (error instanceof SyntaxError) {
31
- issues.push(new Issue('invalidSyntax', ['body'], 'JSON', error.message));
32
+ issues.push(new Issue(['body'], error.message));
32
33
  }
33
34
  else if (error instanceof ValidationError) {
34
35
  issues.push(...error.withPrefix('body'));
@@ -38,7 +39,7 @@ class ConcreteRestEndpoint extends RestEndpoint {
38
39
  }
39
40
  }
40
41
  try {
41
- parsedParams = this.paramsSchema.parse(params);
42
+ parsedParams = this.paramsSchema.parse(req.params);
42
43
  }
43
44
  catch (error) {
44
45
  if (error instanceof ValidationError) {
@@ -49,7 +50,7 @@ class ConcreteRestEndpoint extends RestEndpoint {
49
50
  }
50
51
  }
51
52
  try {
52
- parsedQuery = this.querySchema.parse(query);
53
+ parsedQuery = this.querySchema.parse(req.query);
53
54
  }
54
55
  catch (error) {
55
56
  if (error instanceof ValidationError) {
@@ -60,7 +61,7 @@ class ConcreteRestEndpoint extends RestEndpoint {
60
61
  }
61
62
  }
62
63
  try {
63
- parsedHeaders = this.headersSchema.parse(headers);
64
+ parsedHeaders = this.headersSchema.parse(req.headers);
64
65
  }
65
66
  catch (error) {
66
67
  if (error instanceof ValidationError) {
@@ -74,8 +75,8 @@ class ConcreteRestEndpoint extends RestEndpoint {
74
75
  const error = new ValidationError(issues);
75
76
  throw new BadRequestError(error.format());
76
77
  }
77
- const response = await this.options.do({
78
- url,
78
+ return await this.options.do({
79
+ url: req.url,
79
80
  // biome-ignore lint/style/noNonNullAssertion: this is required to convince TypeScript that this is initialized
80
81
  params: parsedParams,
81
82
  // biome-ignore lint/style/noNonNullAssertion: this is required to convince TypeScript that this is initialized
@@ -85,16 +86,6 @@ class ConcreteRestEndpoint extends RestEndpoint {
85
86
  // biome-ignore lint/style/noNonNullAssertion: this is required to convince TypeScript that this is initialized
86
87
  body: parsedBody,
87
88
  });
88
- if (response.body instanceof Uint8Array) {
89
- return new Response(response.body, {
90
- status: response.status,
91
- headers: response.headers,
92
- });
93
- }
94
- return Response.json(response.body, {
95
- status: response.status,
96
- headers: response.headers,
97
- });
98
89
  }
99
90
  documentation(securitySchemes) {
100
91
  const parameters = [
@@ -0,0 +1,7 @@
1
+ export declare class Counter {
2
+ private count;
3
+ private resolve;
4
+ wait(): Promise<void>;
5
+ increment(): void;
6
+ decrement(): void;
7
+ }
@@ -0,0 +1,24 @@
1
+ export class Counter {
2
+ constructor() {
3
+ this.count = 0;
4
+ }
5
+ wait() {
6
+ return new Promise((resolve) => {
7
+ if (this.count === 0) {
8
+ resolve();
9
+ }
10
+ else {
11
+ this.resolve = resolve;
12
+ }
13
+ });
14
+ }
15
+ increment() {
16
+ this.count += 1;
17
+ }
18
+ decrement() {
19
+ this.count -= 1;
20
+ if (this.count === 0 && this.resolve !== undefined) {
21
+ this.resolve();
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,2 @@
1
+ import type { Readable } from 'node:stream';
2
+ export declare const readableToBuffer: (stream: Readable) => Promise<Buffer>;
@@ -0,0 +1,7 @@
1
+ export const readableToBuffer = async (stream) => {
2
+ const chunks = [];
3
+ for await (const chunk of stream) {
4
+ chunks.push(chunk);
5
+ }
6
+ return Buffer.concat(chunks);
7
+ };
@@ -1,7 +1,7 @@
1
1
  import type { Typeof } from './body.js';
2
2
  import { ModifiableSchema } from './modifiable.js';
3
3
  import type { Schema } from './schema.js';
4
- declare class ArraySchema<ItemSchema extends Schema<unknown>> extends ModifiableSchema<Typeof<ItemSchema>[]> {
4
+ export declare class ArraySchema<ItemSchema extends Schema<unknown>> extends ModifiableSchema<Typeof<ItemSchema>[]> {
5
5
  private readonly itemSchema;
6
6
  private readonly minItems?;
7
7
  private readonly maxItems?;
@@ -28,6 +28,6 @@ declare class ArraySchema<ItemSchema extends Schema<unknown>> extends Modifiable
28
28
  /**
29
29
  * A schema matching arrays of the provided item type.
30
30
  * @param itemSchema - The schema for array items.
31
+ * @deprecated Use the .array() method instead.
31
32
  */
32
33
  export declare const array: <ItemSchema extends Schema<unknown>>(itemSchema: ItemSchema) => ArraySchema<ItemSchema>;
33
- export {};