@kaito-http/core 3.0.2 → 4.0.0-beta.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.
- package/dist/index.cjs +220 -146
- package/dist/index.d.cts +94 -79
- package/dist/index.d.ts +94 -79
- package/dist/index.js +218 -142
- package/dist/stream/stream.cjs +0 -5
- package/dist/stream/stream.d.cts +0 -1
- package/dist/stream/stream.d.ts +0 -1
- package/dist/stream/stream.js +0 -5
- package/package.json +25 -20
package/dist/index.cjs
CHANGED
|
@@ -24,19 +24,17 @@ __export(index_exports, {
|
|
|
24
24
|
KaitoRequest: () => KaitoRequest,
|
|
25
25
|
Router: () => Router,
|
|
26
26
|
WrappedError: () => WrappedError,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
isNodeLikeDev: () => isNodeLikeDev,
|
|
30
|
-
parsable: () => parsable
|
|
27
|
+
create: () => create,
|
|
28
|
+
isNodeLikeDev: () => isNodeLikeDev
|
|
31
29
|
});
|
|
32
30
|
module.exports = __toCommonJS(index_exports);
|
|
33
31
|
|
|
32
|
+
// src/router/router.ts
|
|
33
|
+
var import_zod = require("zod");
|
|
34
|
+
var import_zod_openapi = require("zod-openapi");
|
|
35
|
+
|
|
34
36
|
// src/error.ts
|
|
35
37
|
var WrappedError = class _WrappedError extends Error {
|
|
36
|
-
constructor(data) {
|
|
37
|
-
super("Something was thrown, but it was not an instance of Error, so a WrappedError was created.");
|
|
38
|
-
this.data = data;
|
|
39
|
-
}
|
|
40
38
|
static maybe(maybeError) {
|
|
41
39
|
if (maybeError instanceof Error) {
|
|
42
40
|
return maybeError;
|
|
@@ -46,36 +44,62 @@ var WrappedError = class _WrappedError extends Error {
|
|
|
46
44
|
static from(data) {
|
|
47
45
|
return new _WrappedError(data);
|
|
48
46
|
}
|
|
47
|
+
data;
|
|
48
|
+
constructor(data) {
|
|
49
|
+
super("Something was thrown, but it was not an instance of Error, so a WrappedError was created.");
|
|
50
|
+
this.data = data;
|
|
51
|
+
}
|
|
49
52
|
};
|
|
50
53
|
var KaitoError = class extends Error {
|
|
54
|
+
status;
|
|
51
55
|
constructor(status, message) {
|
|
52
56
|
super(message);
|
|
53
57
|
this.status = status;
|
|
54
58
|
}
|
|
55
59
|
};
|
|
56
60
|
|
|
57
|
-
// src/
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return result;
|
|
69
|
-
}
|
|
61
|
+
// src/head.ts
|
|
62
|
+
var KaitoHead = class {
|
|
63
|
+
#headers;
|
|
64
|
+
#status;
|
|
65
|
+
constructor() {
|
|
66
|
+
this.#headers = null;
|
|
67
|
+
this.#status = 200;
|
|
68
|
+
}
|
|
69
|
+
get headers() {
|
|
70
|
+
if (this.#headers === null) {
|
|
71
|
+
this.#headers = new Headers();
|
|
70
72
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
return this.#headers;
|
|
74
|
+
}
|
|
75
|
+
status(status) {
|
|
76
|
+
if (status === void 0) {
|
|
77
|
+
return this.#status;
|
|
75
78
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
+
this.#status = status;
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Turn this KaitoHead instance into a Response instance
|
|
84
|
+
* @param body The Kaito JSON format to be sent as the response body
|
|
85
|
+
* @returns A Response instance, ready to be sent
|
|
86
|
+
*/
|
|
87
|
+
toResponse(body) {
|
|
88
|
+
const init = {
|
|
89
|
+
status: this.#status
|
|
90
|
+
};
|
|
91
|
+
if (this.#headers) {
|
|
92
|
+
init.headers = this.#headers;
|
|
93
|
+
}
|
|
94
|
+
return Response.json(body, init);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Whether this KaitoHead instance has been touched/modified
|
|
98
|
+
*/
|
|
99
|
+
get touched() {
|
|
100
|
+
return this.#status !== 200 || this.#headers !== null;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
79
103
|
|
|
80
104
|
// src/request.ts
|
|
81
105
|
var KaitoRequest = class {
|
|
@@ -115,91 +139,25 @@ var KaitoRequest = class {
|
|
|
115
139
|
}
|
|
116
140
|
};
|
|
117
141
|
|
|
118
|
-
// src/head.ts
|
|
119
|
-
var KaitoHead = class {
|
|
120
|
-
_headers;
|
|
121
|
-
_status;
|
|
122
|
-
constructor() {
|
|
123
|
-
this._headers = null;
|
|
124
|
-
this._status = 200;
|
|
125
|
-
}
|
|
126
|
-
get headers() {
|
|
127
|
-
if (this._headers === null) {
|
|
128
|
-
this._headers = new Headers();
|
|
129
|
-
}
|
|
130
|
-
return this._headers;
|
|
131
|
-
}
|
|
132
|
-
status(status) {
|
|
133
|
-
if (status === void 0) {
|
|
134
|
-
return this._status;
|
|
135
|
-
}
|
|
136
|
-
this._status = status;
|
|
137
|
-
return this;
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Turn this KaitoHead instance into a Response instance
|
|
141
|
-
* @param body The Kaito JSON format to be sent as the response body
|
|
142
|
-
* @returns A Response instance, ready to be sent
|
|
143
|
-
*/
|
|
144
|
-
toResponse(body) {
|
|
145
|
-
const init = {
|
|
146
|
-
status: this._status
|
|
147
|
-
};
|
|
148
|
-
if (this._headers) {
|
|
149
|
-
init.headers = this._headers;
|
|
150
|
-
}
|
|
151
|
-
return Response.json(body, init);
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Whether this KaitoHead instance has been touched/modified
|
|
155
|
-
*/
|
|
156
|
-
get touched() {
|
|
157
|
-
return this._status !== 200 || this._headers !== null;
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
|
|
161
142
|
// src/util.ts
|
|
162
143
|
var isNodeLikeDev = typeof process !== "undefined" && typeof process.env !== "undefined" && process.env.NODE_ENV === "development";
|
|
163
|
-
function createUtilities(getContext) {
|
|
164
|
-
return {
|
|
165
|
-
getContext,
|
|
166
|
-
router: () => Router.create()
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
function parsable(parse) {
|
|
170
|
-
return {
|
|
171
|
-
parse
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
144
|
|
|
175
145
|
// src/router/router.ts
|
|
176
146
|
var Router = class _Router {
|
|
177
147
|
state;
|
|
178
|
-
static create = () => new _Router({
|
|
148
|
+
static create = (config) => new _Router({
|
|
179
149
|
through: async (context) => context,
|
|
180
|
-
routes: /* @__PURE__ */ new Set()
|
|
150
|
+
routes: /* @__PURE__ */ new Set(),
|
|
151
|
+
config
|
|
181
152
|
});
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return {};
|
|
185
|
-
}
|
|
186
|
-
const result = {};
|
|
187
|
-
for (const key in schema) {
|
|
188
|
-
if (!schema.hasOwnProperty(key)) continue;
|
|
189
|
-
const value = url.searchParams.get(key);
|
|
190
|
-
result[key] = schema[key].parse(value);
|
|
191
|
-
}
|
|
192
|
-
return result;
|
|
193
|
-
}
|
|
194
|
-
constructor(options) {
|
|
195
|
-
this.state = options;
|
|
153
|
+
constructor(state) {
|
|
154
|
+
this.state = state;
|
|
196
155
|
}
|
|
197
156
|
get routes() {
|
|
198
157
|
return this.state.routes;
|
|
199
158
|
}
|
|
200
159
|
add = (method, path, route) => {
|
|
201
160
|
const merged = {
|
|
202
|
-
// TODO: Ideally fix the typing here, but this will be replaced in Kaito v4 where all routes must return a Response (which we can type)
|
|
203
161
|
...typeof route === "object" ? route : { run: route },
|
|
204
162
|
method,
|
|
205
163
|
path,
|
|
@@ -220,39 +178,54 @@ var Router = class _Router {
|
|
|
220
178
|
routes: /* @__PURE__ */ new Set([...this.state.routes, ...newRoutes])
|
|
221
179
|
});
|
|
222
180
|
};
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
181
|
+
static getFindRoute = (routes) => (method, path) => {
|
|
182
|
+
const params = {};
|
|
183
|
+
const pathParts = path.split("/").filter(Boolean);
|
|
184
|
+
const methodRoutes = routes.get(method);
|
|
185
|
+
if (!methodRoutes) return {};
|
|
186
|
+
for (const [routePath, route] of methodRoutes) {
|
|
187
|
+
const routeParts = routePath.split("/").filter(Boolean);
|
|
188
|
+
if (routeParts.length !== pathParts.length) {
|
|
189
|
+
continue;
|
|
228
190
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
for (let i = 0; i < routeParts.length; i++) {
|
|
239
|
-
const routePart = routeParts[i];
|
|
240
|
-
const pathPart = pathParts[i];
|
|
241
|
-
if (routePart && pathPart && routePart.startsWith(":")) {
|
|
242
|
-
params[routePart.slice(1)] = pathPart;
|
|
243
|
-
} else if (routePart !== pathPart) {
|
|
244
|
-
matches = false;
|
|
245
|
-
break;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
if (matches) {
|
|
249
|
-
const route = methodHandlers.get(method);
|
|
250
|
-
if (route) return { route, params };
|
|
191
|
+
let matches = true;
|
|
192
|
+
for (let i = 0; i < routeParts.length; i++) {
|
|
193
|
+
const routePart = routeParts[i];
|
|
194
|
+
const pathPart = pathParts[i];
|
|
195
|
+
if (routePart && pathPart && routePart.startsWith(":")) {
|
|
196
|
+
params[routePart.slice(1)] = pathPart;
|
|
197
|
+
} else if (routePart !== pathPart) {
|
|
198
|
+
matches = false;
|
|
199
|
+
break;
|
|
251
200
|
}
|
|
252
201
|
}
|
|
253
|
-
return { params };
|
|
254
|
-
}
|
|
255
|
-
return
|
|
202
|
+
if (matches) return { route, params };
|
|
203
|
+
}
|
|
204
|
+
return {};
|
|
205
|
+
};
|
|
206
|
+
static buildQuerySchema = (schema) => {
|
|
207
|
+
const keys = Object.keys(schema);
|
|
208
|
+
return import_zod.z.instanceof(URLSearchParams).transform((params) => {
|
|
209
|
+
const result = {};
|
|
210
|
+
for (const key of keys) {
|
|
211
|
+
result[key] = params.get(key);
|
|
212
|
+
}
|
|
213
|
+
return result;
|
|
214
|
+
}).pipe(import_zod.z.object(schema));
|
|
215
|
+
};
|
|
216
|
+
serve = () => {
|
|
217
|
+
const methodToRoutesMap = /* @__PURE__ */ new Map();
|
|
218
|
+
for (const route of this.state.routes) {
|
|
219
|
+
if (!methodToRoutesMap.has(route.method)) {
|
|
220
|
+
methodToRoutesMap.set(route.method, /* @__PURE__ */ new Map());
|
|
221
|
+
}
|
|
222
|
+
methodToRoutesMap.get(route.method).set(route.path, {
|
|
223
|
+
...route,
|
|
224
|
+
fastQuerySchema: route.query ? _Router.buildQuerySchema(route.query) : void 0
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
const findRoute = _Router.getFindRoute(methodToRoutesMap);
|
|
228
|
+
const handle = async (req) => {
|
|
256
229
|
const url = new URL(req.url);
|
|
257
230
|
const method = req.method;
|
|
258
231
|
const { route, params } = findRoute(method, url.pathname);
|
|
@@ -267,10 +240,9 @@ var Router = class _Router {
|
|
|
267
240
|
const request = new KaitoRequest(url, req);
|
|
268
241
|
const head = new KaitoHead();
|
|
269
242
|
try {
|
|
270
|
-
const body = route.body ? await route.body.
|
|
271
|
-
const query =
|
|
272
|
-
const
|
|
273
|
-
const ctx = await route.through(rootCtx);
|
|
243
|
+
const body = route.body ? await route.body.parseAsync(await req.json()) : void 0;
|
|
244
|
+
const query = route.fastQuerySchema ? await route.fastQuerySchema.parseAsync(url.searchParams) : {};
|
|
245
|
+
const ctx = await route.through(await this.state.config.getContext?.(request, head) ?? null);
|
|
274
246
|
const result = await route.run({
|
|
275
247
|
ctx,
|
|
276
248
|
body,
|
|
@@ -293,8 +265,7 @@ var Router = class _Router {
|
|
|
293
265
|
}
|
|
294
266
|
return head.toResponse({
|
|
295
267
|
success: true,
|
|
296
|
-
data: result
|
|
297
|
-
message: "OK"
|
|
268
|
+
data: result
|
|
298
269
|
});
|
|
299
270
|
} catch (e) {
|
|
300
271
|
const error = WrappedError.maybe(e);
|
|
@@ -305,15 +276,115 @@ var Router = class _Router {
|
|
|
305
276
|
message: error.message
|
|
306
277
|
});
|
|
307
278
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
279
|
+
if (!this.state.config.onError) {
|
|
280
|
+
return head.status(500).toResponse({
|
|
281
|
+
success: false,
|
|
282
|
+
data: null,
|
|
283
|
+
message: "Internal Server Error"
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
const { status, message } = await this.state.config.onError(error, request);
|
|
288
|
+
return head.status(status).toResponse({
|
|
289
|
+
success: false,
|
|
290
|
+
data: null,
|
|
291
|
+
message
|
|
292
|
+
});
|
|
293
|
+
} catch (e2) {
|
|
294
|
+
console.error("KAITO - Failed to handle error inside `.onError()`, returning 500 and Internal Server Error");
|
|
295
|
+
console.error(e2);
|
|
296
|
+
return head.status(500).toResponse({
|
|
297
|
+
success: false,
|
|
298
|
+
data: null,
|
|
299
|
+
message: "Internal Server Error"
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
return async (request) => {
|
|
305
|
+
if (this.state.config.before) {
|
|
306
|
+
const result = await this.state.config.before(request);
|
|
307
|
+
if (result instanceof Response) {
|
|
308
|
+
if (this.state.config.transform) {
|
|
309
|
+
const transformed = await this.state.config.transform(request, result);
|
|
310
|
+
if (transformed instanceof Response) {
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const response = await handle(request);
|
|
318
|
+
if (this.state.config.transform) {
|
|
319
|
+
const transformed = await this.state.config.transform(request, response);
|
|
320
|
+
if (transformed instanceof Response) {
|
|
321
|
+
return transformed;
|
|
322
|
+
}
|
|
314
323
|
}
|
|
324
|
+
return response;
|
|
315
325
|
};
|
|
316
326
|
};
|
|
327
|
+
openapi = (highLevelSpec) => {
|
|
328
|
+
const OPENAPI_VERSION = "3.0.0";
|
|
329
|
+
const paths = {};
|
|
330
|
+
for (const route of this.state.routes) {
|
|
331
|
+
const path = route.path;
|
|
332
|
+
const pathWithColonParamsReplaceWithCurlyBraces = path.replace(/:(\w+)/g, "{$1}");
|
|
333
|
+
if (!paths[pathWithColonParamsReplaceWithCurlyBraces]) {
|
|
334
|
+
paths[pathWithColonParamsReplaceWithCurlyBraces] = {};
|
|
335
|
+
}
|
|
336
|
+
const item = {
|
|
337
|
+
description: route.openapi?.description ?? "Successful response",
|
|
338
|
+
responses: {
|
|
339
|
+
200: {
|
|
340
|
+
description: route.openapi?.description ?? "Successful response",
|
|
341
|
+
...route.openapi ? {
|
|
342
|
+
content: {
|
|
343
|
+
[{
|
|
344
|
+
json: "application/json",
|
|
345
|
+
sse: "text/event-stream"
|
|
346
|
+
}[route.openapi.body.type]]: { schema: route.openapi?.body.schema }
|
|
347
|
+
}
|
|
348
|
+
} : {}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
if (route.body) {
|
|
353
|
+
item.requestBody = {
|
|
354
|
+
content: {
|
|
355
|
+
"application/json": { schema: route.body }
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
const params = {};
|
|
360
|
+
if (route.query) {
|
|
361
|
+
params.query = import_zod.z.object(route.query);
|
|
362
|
+
}
|
|
363
|
+
const urlParams = path.match(/:(\w+)/g);
|
|
364
|
+
if (urlParams) {
|
|
365
|
+
const pathParams = {};
|
|
366
|
+
for (const param of urlParams) {
|
|
367
|
+
pathParams[param.slice(1)] = import_zod.z.string();
|
|
368
|
+
}
|
|
369
|
+
params.path = import_zod.z.object(pathParams);
|
|
370
|
+
}
|
|
371
|
+
item.requestParams = params;
|
|
372
|
+
paths[pathWithColonParamsReplaceWithCurlyBraces][route.method.toLowerCase()] = item;
|
|
373
|
+
}
|
|
374
|
+
const doc = (0, import_zod_openapi.createDocument)({
|
|
375
|
+
openapi: OPENAPI_VERSION,
|
|
376
|
+
paths,
|
|
377
|
+
...highLevelSpec,
|
|
378
|
+
servers: Object.entries(highLevelSpec.servers ?? {}).map((entry) => {
|
|
379
|
+
const [url, description] = entry;
|
|
380
|
+
return {
|
|
381
|
+
url,
|
|
382
|
+
description
|
|
383
|
+
};
|
|
384
|
+
})
|
|
385
|
+
});
|
|
386
|
+
return this.add("GET", "/openapi.json", async () => Response.json(doc));
|
|
387
|
+
};
|
|
317
388
|
method = (method) => (path, route) => {
|
|
318
389
|
return this.add(method, path, route);
|
|
319
390
|
};
|
|
@@ -327,18 +398,21 @@ var Router = class _Router {
|
|
|
327
398
|
through = (through) => {
|
|
328
399
|
return new _Router({
|
|
329
400
|
...this.state,
|
|
330
|
-
through: async (context) => through(await this.state.through(context))
|
|
401
|
+
through: async (context) => await through(await this.state.through(context))
|
|
331
402
|
});
|
|
332
403
|
};
|
|
333
404
|
};
|
|
405
|
+
|
|
406
|
+
// src/create.ts
|
|
407
|
+
function create(config = {}) {
|
|
408
|
+
return () => Router.create(config);
|
|
409
|
+
}
|
|
334
410
|
// Annotate the CommonJS export names for ESM import in node:
|
|
335
411
|
0 && (module.exports = {
|
|
336
412
|
KaitoError,
|
|
337
413
|
KaitoRequest,
|
|
338
414
|
Router,
|
|
339
415
|
WrappedError,
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
isNodeLikeDev,
|
|
343
|
-
parsable
|
|
416
|
+
create,
|
|
417
|
+
isNodeLikeDev
|
|
344
418
|
});
|