@riddl/core 0.2.6 → 0.2.8

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/api.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import type { APIOptions, API } from "./types";
1
+ import type { APIOptions, API } from './types';
2
2
  export declare const api: ({ routeModuleMap, config, base, cors, registry, onInternalError, }: APIOptions) => Promise<API>;
package/dist/api.js CHANGED
@@ -1,49 +1,56 @@
1
- import { OpenAPIRegistry } from "@asteasolutions/zod-to-openapi";
2
- import { json, recursiveAwait } from "./utils.js";
3
- import { CORS_DEFAULT, METHOD } from "./constants.js";
4
- import { log, logError } from "./log.js";
5
- import { parseBody, parseOutput, parseParam, parseQuery } from "./validation.js";
6
- import { cleanupTempFiles } from "./multipart.js";
7
- import { parseRouteId } from "./parser.js";
8
- import { buildRouter } from "./routing.js";
9
- import { openapiHandler, registerRoutes } from "./openapi.js";
10
- import { error } from "./errors.js";
11
- export const api = async ({ routeModuleMap, config, base = "", cors = true, registry = new OpenAPIRegistry(), onInternalError, }) => {
1
+ import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi';
2
+ import { json, recursiveAwait } from './utils.js';
3
+ import { CORS_DEFAULT, METHOD } from './constants.js';
4
+ import { log, logError } from './log.js';
5
+ import { parseBody, parseOutput, parseParam, parseQuery } from './validation.js';
6
+ import { cleanupTempFiles } from './multipart.js';
7
+ import { parseRouteId } from './parser.js';
8
+ import { buildRouter } from './routing.js';
9
+ import { openapiHandler, registerRoutes } from './openapi.js';
10
+ import { error } from './errors.js';
11
+ export const api = async ({ routeModuleMap, config, base = '', cors = true, registry = new OpenAPIRegistry(), onInternalError, }) => {
12
12
  const reportInternalError = (error) => {
13
- logError("internal error", error);
13
+ logError('internal error', error);
14
14
  onInternalError === null || onInternalError === void 0 ? void 0 : onInternalError(error);
15
15
  };
16
- const { routes, middlewares } = await Object.entries(routeModuleMap)
17
- .reduce(async (accPromise, [path, module]) => {
16
+ const { routes, middlewares } = await Object.entries(routeModuleMap).reduce(async (accPromise, [path, module]) => {
18
17
  var _a, _b, _c;
19
18
  const acc = await accPromise;
20
- const parts = path.split("/");
21
- const last = parts[parts.length - 1].split(".")[0];
19
+ const parts = path.split('/');
20
+ const last = parts[parts.length - 1].split('.')[0];
22
21
  parts[parts.length - 1] = last;
23
- const id = parts.slice(0, -1).join("/");
22
+ const id = parts.slice(0, -1).join('/');
24
23
  if (METHOD.test(last)) {
25
24
  const method = last.toUpperCase();
26
- const endpoint = (await module()).apiEndpoint;
25
+ const endpoint = (await module())
26
+ .apiEndpoint;
27
27
  acc.routes[id] = {
28
28
  matcher: parseRouteId(id),
29
29
  handlers: {
30
30
  ...((_c = (_b = (_a = acc.routes) === null || _a === void 0 ? void 0 : _a[id]) === null || _b === void 0 ? void 0 : _b.handlers) !== null && _c !== void 0 ? _c : {}),
31
31
  [method]: endpoint,
32
- }
32
+ },
33
33
  };
34
34
  }
35
- if (last === "middleware") {
35
+ if (last === 'middleware') {
36
36
  acc.middlewares[id] = (await module());
37
37
  }
38
38
  return acc;
39
- }, Promise.resolve({ routes: {}, middlewares: {} }));
40
- log("routes: %O", routes);
41
- log("middlewares: %O", middlewares);
42
- log("config: %O", config);
43
- log("base: %s", base);
39
+ }, Promise.resolve({
40
+ routes: {},
41
+ middlewares: {},
42
+ }));
43
+ log('routes: %O', routes);
44
+ log('middlewares: %O', middlewares);
45
+ log('config: %O', config);
46
+ log('base: %s', base);
44
47
  registerRoutes({ routes, registry, base });
45
48
  const router = buildRouter(routes, middlewares);
46
- const effectiveCorsSettings = cors ? typeof cors === "object" ? cors : CORS_DEFAULT : undefined;
49
+ const effectiveCorsSettings = cors
50
+ ? typeof cors === 'object'
51
+ ? cors
52
+ : CORS_DEFAULT
53
+ : undefined;
47
54
  const handler = async (request) => {
48
55
  var _a, _b, _c;
49
56
  // Temp files produced by streamed multipart uploads; removed once the
@@ -51,7 +58,7 @@ export const api = async ({ routeModuleMap, config, base = "", cors = true, regi
51
58
  // storage before returning).
52
59
  const tempFiles = [];
53
60
  try {
54
- log("request: %O", request);
61
+ log('request: %O', request);
55
62
  const event = {
56
63
  request,
57
64
  url: new URL(request.url),
@@ -59,18 +66,18 @@ export const api = async ({ routeModuleMap, config, base = "", cors = true, regi
59
66
  };
60
67
  const { id, params, routeWithMiddlewares } = (_a = router(event.url.pathname)) !== null && _a !== void 0 ? _a : {};
61
68
  if (!id || !params || !routeWithMiddlewares) {
62
- throw error(404, "Route not found");
69
+ throw error(404, 'Route not found');
63
70
  }
64
- log("route id: %s", id);
71
+ log('route id: %s', id);
65
72
  // handle OPTIONS
66
- if (request.method.toUpperCase() === "OPTIONS") {
73
+ if (request.method.toUpperCase() === 'OPTIONS') {
67
74
  return new Response(null, {
68
- headers: (_b = effectiveCorsSettings) !== null && _b !== void 0 ? _b : {}
75
+ headers: (_b = effectiveCorsSettings) !== null && _b !== void 0 ? _b : {},
69
76
  });
70
77
  }
71
78
  const endpoint = routeWithMiddlewares.handlers[request.method.toUpperCase()];
72
79
  if (!(endpoint === null || endpoint === void 0 ? void 0 : endpoint.handle)) {
73
- throw error(405, "Method not allowed");
80
+ throw error(405, 'Method not allowed');
74
81
  }
75
82
  let ctxEvent = event;
76
83
  for (const mw of routeWithMiddlewares.middlewares) {
@@ -79,12 +86,17 @@ export const api = async ({ routeModuleMap, config, base = "", cors = true, regi
79
86
  const param = await parseParam(params !== null && params !== void 0 ? params : {}, endpoint);
80
87
  const query = await parseQuery(request, endpoint);
81
88
  const body = await parseBody(request, endpoint, tempFiles);
82
- log("handler: %O", endpoint.handle);
83
- const output = await endpoint.handle({ body, query, param, event: ctxEvent });
89
+ log('handler: %O', endpoint.handle);
90
+ const output = await endpoint.handle({
91
+ body,
92
+ query,
93
+ param,
94
+ event: ctxEvent,
95
+ });
84
96
  for (const mw of [...routeWithMiddlewares.middlewares].reverse()) {
85
97
  ctxEvent = await mw.post(ctxEvent);
86
98
  }
87
- log("output: %O", output);
99
+ log('output: %O', output);
88
100
  if (output instanceof Response) {
89
101
  if (effectiveCorsSettings) {
90
102
  for (const [key, value] of Object.entries(effectiveCorsSettings)) {
@@ -97,9 +109,9 @@ export const api = async ({ routeModuleMap, config, base = "", cors = true, regi
97
109
  if (output instanceof ReadableStream) {
98
110
  const res = new Response(output, {
99
111
  headers: {
100
- "Content-Type": "text/event-stream",
101
- "Cache-Control": "no-cache",
102
- Connection: "keep-alive",
112
+ 'Content-Type': 'text/event-stream',
113
+ 'Cache-Control': 'no-cache',
114
+ Connection: 'keep-alive',
103
115
  ...(effectiveCorsSettings !== null && effectiveCorsSettings !== void 0 ? effectiveCorsSettings : {}),
104
116
  },
105
117
  });
@@ -108,7 +120,7 @@ export const api = async ({ routeModuleMap, config, base = "", cors = true, regi
108
120
  if (output instanceof ArrayBuffer) {
109
121
  const res = new Response(output, {
110
122
  headers: {
111
- "Content-Type": "application/octet-stream",
123
+ 'Content-Type': 'application/octet-stream',
112
124
  ...(effectiveCorsSettings !== null && effectiveCorsSettings !== void 0 ? effectiveCorsSettings : {}),
113
125
  },
114
126
  });
@@ -123,12 +135,13 @@ export const api = async ({ routeModuleMap, config, base = "", cors = true, regi
123
135
  }
124
136
  }
125
137
  catch (e) {
126
- if ('__HTTP_ERROR__' in e) {
127
- if (typeof e.status === "number" && e.status >= 500) {
138
+ if (e && typeof e === 'object' && '__HTTP_ERROR__' in e) {
139
+ const httpError = e;
140
+ if (typeof httpError.status === 'number' && httpError.status >= 500) {
128
141
  reportInternalError(e);
129
142
  }
130
143
  return json(e, {
131
- status: e.status,
144
+ status: httpError.status,
132
145
  });
133
146
  }
134
147
  reportInternalError(e);
package/dist/openapi.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { OpenApiGeneratorV3, } from "@asteasolutions/zod-to-openapi";
2
2
  import { z } from "./zod.js";
3
3
  const openApiPath = (id, base) => id.split("/")
4
- .slice(0, -1)
5
4
  .join("/")
6
5
  .replace(/^\./, base)
7
6
  .replace(/\[(\.{3})?(.+?)\]/g, "{$2}")
@@ -1,4 +1,4 @@
1
- import { GenericApiEndpoint } from "./types";
1
+ import { GenericApiEndpoint } from './types';
2
2
  export declare const parseBody: (req: Request, module: GenericApiEndpoint, tempFiles?: string[]) => Promise<unknown>;
3
3
  export declare const parseQuery: (request: Request, module: GenericApiEndpoint) => Promise<Record<string, string | string[]>>;
4
4
  export declare const parseParam: (params: Record<string, string>, module: GenericApiEndpoint) => Promise<Record<string, string | never>>;
@@ -1,20 +1,20 @@
1
- import { fromZodError } from "zod-validation-error";
2
- import { log } from "./log.js";
3
- import { z } from "./zod.js";
4
- import { error } from "./errors.js";
5
- import { parseMultipart } from "./multipart.js";
1
+ import { fromZodError } from 'zod-validation-error';
2
+ import { log } from './log.js';
3
+ import { z } from './zod.js';
4
+ import { error } from './errors.js';
5
+ import { parseMultipart } from './multipart.js';
6
6
  const parseJsonBody = async (req) => {
7
7
  var _a;
8
8
  const clonedRequest = req.clone();
9
- if ((_a = clonedRequest.headers.get("content-type")) === null || _a === void 0 ? void 0 : _a.startsWith("application/json")) {
9
+ if ((_a = clonedRequest.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.startsWith('application/json')) {
10
10
  try {
11
11
  const json = await clonedRequest.json();
12
- if (typeof json === "object") {
12
+ if (typeof json === 'object') {
13
13
  return json;
14
14
  }
15
15
  }
16
16
  catch {
17
- throw error(400, "Invalid JSON body");
17
+ throw error(400, 'Invalid JSON body');
18
18
  }
19
19
  }
20
20
  };
@@ -23,26 +23,29 @@ export const parseBody = async (req, module, tempFiles = []) => {
23
23
  let body = {};
24
24
  // GET, HEAD, DELETE, OPTIONS have no body
25
25
  const method = req.method.toUpperCase();
26
- if (["GET", "HEAD", "DELETE", "OPTIONS"].includes(method)) {
26
+ if (['GET', 'HEAD', 'DELETE', 'OPTIONS'].includes(method)) {
27
27
  return body;
28
28
  }
29
- const contentType = (_a = req.headers.get("content-type")) !== null && _a !== void 0 ? _a : "";
29
+ const contentType = (_a = req.headers.get('content-type')) !== null && _a !== void 0 ? _a : '';
30
30
  // JSON body
31
- if (contentType.startsWith("application/json")) {
32
- body = await req.clone().json().catch(() => {
33
- throw error(400, "Invalid JSON body");
31
+ if (contentType.startsWith('application/json')) {
32
+ body = await req
33
+ .clone()
34
+ .json()
35
+ .catch(() => {
36
+ throw error(400, 'Invalid JSON body');
34
37
  });
35
38
  }
36
39
  // Multipart form body: stream uploads to disk rather than buffering the
37
40
  // whole payload in memory. Consume `req` directly (no `clone()` — a tee
38
41
  // would re-buffer the unread branch, defeating the point).
39
- else if (contentType.startsWith("multipart/form-data")) {
42
+ else if (contentType.startsWith('multipart/form-data')) {
40
43
  const result = await parseMultipart(req);
41
44
  tempFiles.push(...result.tempFiles);
42
45
  body = result.body;
43
46
  }
44
47
  // URL-encoded form body (always small — buffering is fine)
45
- else if (contentType.startsWith("application/x-www-form-urlencoded")) {
48
+ else if (contentType.startsWith('application/x-www-form-urlencoded')) {
46
49
  const formBody = {};
47
50
  const form = await req.clone().formData();
48
51
  for (const [key, value] of form.entries()) {
@@ -62,15 +65,15 @@ export const parseBody = async (req, module, tempFiles = []) => {
62
65
  body = formBody;
63
66
  }
64
67
  // Text body
65
- else if (contentType.startsWith("text/plain")) {
68
+ else if (contentType.startsWith('text/plain')) {
66
69
  body = { text: await req.clone().text() };
67
70
  }
68
71
  // Binary body
69
- else if (contentType.startsWith("application/octet-stream")) {
72
+ else if (contentType.startsWith('application/octet-stream')) {
70
73
  const buffer = await req.clone().arrayBuffer();
71
74
  body = { buffer };
72
75
  }
73
- log("body: %O", body);
76
+ log('body: %O', body);
74
77
  const validator = (_b = module.Input) !== null && _b !== void 0 ? _b : z.object({});
75
78
  const validation = validator.safeParse(body);
76
79
  if (!validation.success) {
@@ -98,7 +101,7 @@ export const parseQuery = async (request, module) => {
98
101
  query[key] = value;
99
102
  }
100
103
  }
101
- log("query: %O", query);
104
+ log('query: %O', query);
102
105
  const validator = (_a = module.Query) !== null && _a !== void 0 ? _a : z.object({});
103
106
  const validation = validator.safeParse(query);
104
107
  if (!validation.success) {
@@ -111,7 +114,7 @@ export const parseQuery = async (request, module) => {
111
114
  };
112
115
  export const parseParam = async (params, module) => {
113
116
  var _a;
114
- log("param: %O", params);
117
+ log('param: %O', params);
115
118
  const validator = (_a = module.Param) !== null && _a !== void 0 ? _a : z.object({});
116
119
  const validation = validator.safeParse(params);
117
120
  if (!validation.success) {
@@ -125,12 +128,15 @@ export const parseOutput = async (out, module) => {
125
128
  var _a;
126
129
  let output = Array.isArray(out)
127
130
  ? out
128
- : out && typeof out === "object" ? out : {};
131
+ : out && typeof out === 'object'
132
+ ? out
133
+ : {};
129
134
  const validator = (_a = module.Output) !== null && _a !== void 0 ? _a : z.object({});
130
135
  const validation = await validator.spa(output);
131
136
  if (!validation.success) {
132
- log.extend("error")("output: %O failed validation: %O", output, validation.error);
133
- throw error(500, "Output validation failed. Please report this error to the developer.");
137
+ const details = fromZodError(validation.error).message;
138
+ log.extend('error')('output: %O failed validation: %O', output, validation.error);
139
+ throw error(500, `Output validation failed. ${details}`);
134
140
  }
135
141
  else {
136
142
  output = validation.data;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riddl/core",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -43,6 +43,7 @@
43
43
  "format": "prettier --write .",
44
44
  "clean": "rm -f tsconfig.tsbuildinfo dist",
45
45
  "build": "tsc && tsc-alias",
46
- "build:watch": "tsc && tsc-alias && (concurrently \"tsc -w\" \"tsc-alias -w\")"
46
+ "build:watch": "tsc && tsc-alias && (concurrently \"tsc -w\" \"tsc-alias -w\")",
47
+ "prepublishOnly": "pnpm build"
47
48
  }
48
- }
49
+ }