yedra 0.17.13 → 0.18.1

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.
@@ -146,7 +146,7 @@ class BuiltApp {
146
146
  }
147
147
  catch (error) {
148
148
  if (error instanceof HttpError) {
149
- return BuiltApp.errorResponse(error.status, error.message);
149
+ return BuiltApp.errorResponse(error.status, error.message, error.code);
150
150
  }
151
151
  console.error(error);
152
152
  return BuiltApp.errorResponse(500, 'Internal Server Error.');
@@ -173,18 +173,29 @@ class BuiltApp {
173
173
  }
174
174
  catch (error) {
175
175
  if (error instanceof HttpError) {
176
- return BuiltApp.errorResponse(error.status, error.message);
176
+ return BuiltApp.errorResponse(error.status, error.message, error.code);
177
177
  }
178
178
  console.error(error);
179
179
  return BuiltApp.errorResponse(500, 'Internal Server Error.');
180
180
  }
181
181
  }
182
- static errorResponse(status, errorMessage) {
182
+ static errorResponse(status, errorMessage, code) {
183
+ const defaultCodes = new Map([
184
+ [400, 'bad_request'],
185
+ [401, 'unauthorized'],
186
+ [402, 'payment_required'],
187
+ [403, 'forbidden'],
188
+ [404, 'not_found'],
189
+ [405, 'method_not_allowed'],
190
+ [409, 'conflict'],
191
+ [500, 'internal_server_error'],
192
+ ]);
183
193
  return {
184
194
  status,
185
195
  body: {
186
196
  status,
187
197
  errorMessage,
198
+ code: code ?? defaultCodes.get(status) ?? 'unknown_error',
188
199
  },
189
200
  };
190
201
  }
@@ -3,47 +3,48 @@
3
3
  */
4
4
  export declare class HttpError extends Error {
5
5
  readonly status: number;
6
- constructor(status: number, message: string);
6
+ readonly code: string | undefined;
7
+ constructor(status: number, message: string, code?: string);
7
8
  }
8
9
  /**
9
10
  * Indicates a malformed request.
10
11
  * Corresponds to HTTP status code 400 Bad Request.
11
12
  */
12
13
  export declare class BadRequestError extends HttpError {
13
- constructor(message: string);
14
+ constructor(message: string, code?: string);
14
15
  }
15
16
  /**
16
17
  * Indicates missing or invalid credentials.
17
18
  * Corresponds to HTTP status code 401 Unauthorized.
18
19
  */
19
20
  export declare class UnauthorizedError extends HttpError {
20
- constructor(message: string);
21
+ constructor(message: string, code?: string);
21
22
  }
22
23
  /**
23
24
  * Indicates that some kind of payment is required.
24
25
  * Corresponds to HTTP status code 402 Payment Required.
25
26
  */
26
27
  export declare class PaymentRequiredError extends HttpError {
27
- constructor(message: string);
28
+ constructor(message: string, code?: string);
28
29
  }
29
30
  /**
30
31
  * Indicates that the user is not allowed to do something.
31
32
  * Corresponds to HTTP status code 403 Forbidden.
32
33
  */
33
34
  export declare class ForbiddenError extends HttpError {
34
- constructor(message: string);
35
+ constructor(message: string, code?: string);
35
36
  }
36
37
  /**
37
38
  * Indicates that the requested resource does not exist.
38
39
  * Corresponds to HTTP status code 404 Not Found.
39
40
  */
40
41
  export declare class NotFoundError extends HttpError {
41
- constructor(message: string);
42
+ constructor(message: string, code?: string);
42
43
  }
43
44
  /**
44
45
  * Indicates that the action conflicts with the current state.
45
46
  * Corresponds to HTTP status code 409 Conflict.
46
47
  */
47
48
  export declare class ConflictError extends HttpError {
48
- constructor(message: string);
49
+ constructor(message: string, code?: string);
49
50
  }
@@ -2,9 +2,10 @@
2
2
  * Base class for errors that will be handled as HTTP status codes.
3
3
  */
4
4
  export class HttpError extends Error {
5
- constructor(status, message) {
5
+ constructor(status, message, code) {
6
6
  super(message);
7
7
  this.status = status;
8
+ this.code = code;
8
9
  }
9
10
  }
10
11
  /**
@@ -12,8 +13,8 @@ export class HttpError extends Error {
12
13
  * Corresponds to HTTP status code 400 Bad Request.
13
14
  */
14
15
  export class BadRequestError extends HttpError {
15
- constructor(message) {
16
- super(400, message);
16
+ constructor(message, code) {
17
+ super(400, message, code);
17
18
  }
18
19
  }
19
20
  /**
@@ -21,8 +22,8 @@ export class BadRequestError extends HttpError {
21
22
  * Corresponds to HTTP status code 401 Unauthorized.
22
23
  */
23
24
  export class UnauthorizedError extends HttpError {
24
- constructor(message) {
25
- super(401, message);
25
+ constructor(message, code) {
26
+ super(401, message, code);
26
27
  }
27
28
  }
28
29
  /**
@@ -30,8 +31,8 @@ export class UnauthorizedError extends HttpError {
30
31
  * Corresponds to HTTP status code 402 Payment Required.
31
32
  */
32
33
  export class PaymentRequiredError extends HttpError {
33
- constructor(message) {
34
- super(402, message);
34
+ constructor(message, code) {
35
+ super(402, message, code);
35
36
  }
36
37
  }
37
38
  /**
@@ -39,8 +40,8 @@ export class PaymentRequiredError extends HttpError {
39
40
  * Corresponds to HTTP status code 403 Forbidden.
40
41
  */
41
42
  export class ForbiddenError extends HttpError {
42
- constructor(message) {
43
- super(403, message);
43
+ constructor(message, code) {
44
+ super(403, message, code);
44
45
  }
45
46
  }
46
47
  /**
@@ -48,8 +49,8 @@ export class ForbiddenError extends HttpError {
48
49
  * Corresponds to HTTP status code 404 Not Found.
49
50
  */
50
51
  export class NotFoundError extends HttpError {
51
- constructor(message) {
52
- super(404, message);
52
+ constructor(message, code) {
53
+ super(404, message, code);
53
54
  }
54
55
  }
55
56
  /**
@@ -57,7 +58,7 @@ export class NotFoundError extends HttpError {
57
58
  * Corresponds to HTTP status code 409 Conflict.
58
59
  */
59
60
  export class ConflictError extends HttpError {
60
- constructor(message) {
61
- super(409, message);
61
+ constructor(message, code) {
62
+ super(409, message, code);
62
63
  }
63
64
  }
@@ -10,6 +10,11 @@ type ReqObject<Params, Query, Headers, Body> = {
10
10
  query: Query;
11
11
  headers: Headers;
12
12
  body: Body;
13
+ /**
14
+ * The raw request data. If the request body is
15
+ * streamed, this will be an empty buffer.
16
+ */
17
+ raw: Buffer<ArrayBuffer>;
13
18
  };
14
19
  type ResObject<Body> = Promise<{
15
20
  status?: number;
@@ -26,12 +26,15 @@ class ConcreteRestEndpoint extends RestEndpoint {
26
26
  }
27
27
  async handle(req) {
28
28
  let parsedBody;
29
+ let rawBody;
29
30
  let parsedParams;
30
31
  let parsedQuery;
31
32
  let parsedHeaders;
32
33
  const issues = [];
33
34
  try {
34
- parsedBody = await this.options.req.deserialize(req.body, req.headers['content-type'] ?? 'application/octet-stream');
35
+ const result = await this.options.req.deserialize(req.body, req.headers['content-type'] ?? 'application/octet-stream');
36
+ parsedBody = result.parsed;
37
+ rawBody = result.raw;
35
38
  }
36
39
  catch (error) {
37
40
  if (error instanceof SyntaxError) {
@@ -91,6 +94,8 @@ class ConcreteRestEndpoint extends RestEndpoint {
91
94
  headers: parsedHeaders,
92
95
  // biome-ignore lint/style/noNonNullAssertion: this is required to convince TypeScript that this is initialized
93
96
  body: parsedBody,
97
+ // biome-ignore lint/style/noNonNullAssertion: this is required to convince TypeScript that this is initialized
98
+ raw: rawBody,
94
99
  });
95
100
  }
96
101
  isHidden() {
@@ -17,7 +17,10 @@ export declare abstract class BodyType<Provides, Accepts> {
17
17
  * @param stream - The raw stream.
18
18
  * @param contentType - The content type.
19
19
  */
20
- abstract deserialize(stream: Readable, contentType: string): Promise<Provides>;
20
+ abstract deserialize(stream: Readable, contentType: string): Promise<{
21
+ parsed: Provides;
22
+ raw: Buffer<ArrayBuffer>;
23
+ }>;
21
24
  /**
22
25
  * Generate OpenAPI docs for this body.
23
26
  */
@@ -3,7 +3,10 @@ import { BodyType, type Typeof, type TypeofAccepts, type TypeofProvides } from '
3
3
  declare class EitherBody<T extends [...BodyType<unknown, unknown>[]]> extends BodyType<TypeofProvides<T[number]>, TypeofAccepts<T[number]>> {
4
4
  private options;
5
5
  constructor(options: T);
6
- deserialize(stream: Readable, contentType: string): Promise<Typeof<T[number]>>;
6
+ deserialize(stream: Readable, contentType: string): Promise<{
7
+ parsed: Typeof<T[number]>;
8
+ raw: Buffer<ArrayBuffer>;
9
+ }>;
7
10
  bodyDocs(): object;
8
11
  }
9
12
  /**
@@ -1,7 +1,10 @@
1
1
  import type { Readable } from 'node:stream';
2
2
  import { BodyType } from './body.js';
3
3
  export declare class NoneBody extends BodyType<undefined, undefined> {
4
- deserialize(_stream: Readable, _contentType: string): Promise<undefined>;
4
+ deserialize(_stream: Readable, _contentType: string): Promise<{
5
+ parsed: undefined;
6
+ raw: Buffer<ArrayBuffer>;
7
+ }>;
5
8
  bodyDocs(): object;
6
9
  }
7
10
  /**
@@ -1,7 +1,7 @@
1
1
  import { BodyType } from './body.js';
2
2
  export class NoneBody extends BodyType {
3
3
  deserialize(_stream, _contentType) {
4
- return Promise.resolve(undefined);
4
+ return Promise.resolve({ parsed: undefined, raw: Buffer.from('') });
5
5
  }
6
6
  bodyDocs() {
7
7
  // TODO
@@ -3,7 +3,10 @@ import { BodyType } from './body.js';
3
3
  declare class RawBody extends BodyType<Buffer<ArrayBuffer>, Buffer<ArrayBufferLike>> {
4
4
  private contentType;
5
5
  constructor(contentType: string);
6
- deserialize(stream: Readable, _contentType: string): Promise<Buffer<ArrayBuffer>>;
6
+ deserialize(stream: Readable, _contentType: string): Promise<{
7
+ parsed: Buffer<ArrayBuffer>;
8
+ raw: Buffer<ArrayBuffer>;
9
+ }>;
7
10
  bodyDocs(): object;
8
11
  }
9
12
  /**
@@ -6,7 +6,11 @@ class RawBody extends BodyType {
6
6
  this.contentType = contentType;
7
7
  }
8
8
  async deserialize(stream, _contentType) {
9
- return await readableToBuffer(stream);
9
+ const buffer = await readableToBuffer(stream);
10
+ return {
11
+ parsed: buffer,
12
+ raw: buffer,
13
+ };
10
14
  }
11
15
  bodyDocs() {
12
16
  return {
@@ -4,7 +4,10 @@ import { BodyType } from './body.js';
4
4
  * The base class for all schemas.
5
5
  */
6
6
  export declare abstract class Schema<T> extends BodyType<T, T> {
7
- deserialize(stream: Readable, contentType: string): Promise<T>;
7
+ deserialize(stream: Readable, contentType: string): Promise<{
8
+ parsed: T;
9
+ raw: Buffer<ArrayBuffer>;
10
+ }>;
8
11
  bodyDocs(): object;
9
12
  /**
10
13
  * Parse the object with this schema. This throws a
@@ -8,7 +8,7 @@ export class Schema extends BodyType {
8
8
  async deserialize(stream, contentType) {
9
9
  const buffer = await readableToBuffer(stream);
10
10
  if (buffer.length === 0) {
11
- return this.parse({});
11
+ return { parsed: this.parse({}), raw: buffer };
12
12
  }
13
13
  if (contentType !== 'application/json') {
14
14
  throw new ValidationError([
@@ -16,7 +16,7 @@ export class Schema extends BodyType {
16
16
  ]);
17
17
  }
18
18
  const data = JSON.parse(Buffer.from(buffer).toString('utf8'));
19
- return this.parse(data);
19
+ return { parsed: this.parse(data), raw: buffer };
20
20
  }
21
21
  bodyDocs() {
22
22
  return {
@@ -3,7 +3,10 @@ import { BodyType } from './body.js';
3
3
  declare class StreamBody extends BodyType<ReadableStream, ReadableStream> {
4
4
  private readonly contentType;
5
5
  constructor(contentType: string);
6
- deserialize(stream: Readable, _contentType: string): Promise<ReadableStream>;
6
+ deserialize(stream: Readable, _contentType: string): Promise<{
7
+ parsed: ReadableStream;
8
+ raw: Buffer<ArrayBuffer>;
9
+ }>;
7
10
  bodyDocs(): object;
8
11
  }
9
12
  /**
@@ -5,14 +5,17 @@ class StreamBody extends BodyType {
5
5
  this.contentType = contentType;
6
6
  }
7
7
  deserialize(stream, _contentType) {
8
- return Promise.resolve(new ReadableStream({
9
- async start(controller) {
10
- for await (const chunk of stream) {
11
- controller.enqueue(chunk);
12
- }
13
- controller.close();
14
- },
15
- }));
8
+ return Promise.resolve({
9
+ parsed: new ReadableStream({
10
+ async start(controller) {
11
+ for await (const chunk of stream) {
12
+ controller.enqueue(chunk);
13
+ }
14
+ controller.close();
15
+ },
16
+ }),
17
+ raw: Buffer.from(''),
18
+ });
16
19
  }
17
20
  bodyDocs() {
18
21
  return {
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "yedra",
3
- "version": "0.17.13",
3
+ "version": "0.18.1",
4
4
  "repository": "github:0codekit/yedra",
5
5
  "main": "dist/index.js",
6
6
  "devDependencies": {
7
- "@biomejs/biome": "^2.2.4",
8
- "@types/bun": "^1.2.23",
9
- "@types/node": "^24.6.1",
7
+ "@biomejs/biome": "^2.3.3",
8
+ "@types/bun": "^1.3.1",
9
+ "@types/node": "^24.10.0",
10
10
  "@types/uuid": "^11.0.0",
11
11
  "@types/ws": "^8.18.1",
12
12
  "typescript": "^5.9.3"