better-call 0.2.11 → 0.2.13-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/README.md +2 -384
- package/dist/index.cjs +269 -704
- package/dist/index.d.cts +333 -110
- package/dist/index.d.mts +350 -0
- package/dist/index.d.ts +333 -110
- package/dist/index.mjs +646 -0
- package/package.json +20 -24
- package/dist/client.cjs +0 -39
- package/dist/client.cjs.map +0 -1
- package/dist/client.d.cts +0 -40
- package/dist/client.d.ts +0 -40
- package/dist/client.js +0 -14
- package/dist/client.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js +0 -1065
- package/dist/index.js.map +0 -1
- package/dist/router-DIrNt-mY.d.cts +0 -337
- package/dist/router-DIrNt-mY.d.ts +0 -337
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
import crypto from 'uncrypto';
|
|
2
|
+
import { createRouter as createRouter$1, addRoute, findRoute, findAllRoutes } from 'rou3';
|
|
3
|
+
|
|
4
|
+
function createJSON({
|
|
5
|
+
asResponse,
|
|
6
|
+
response
|
|
7
|
+
}) {
|
|
8
|
+
return async function json(json, routerResponse) {
|
|
9
|
+
if (!asResponse) {
|
|
10
|
+
return json;
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
body: json,
|
|
14
|
+
routerResponse,
|
|
15
|
+
_flag: "json"
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function runValidation(options, context) {
|
|
20
|
+
let request = {
|
|
21
|
+
body: void 0,
|
|
22
|
+
query: void 0
|
|
23
|
+
};
|
|
24
|
+
if (options.body) {
|
|
25
|
+
const result = options.body.safeParse(context.body);
|
|
26
|
+
if (result.error) {
|
|
27
|
+
return {
|
|
28
|
+
data: null,
|
|
29
|
+
error: fromError(result.error)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
request.body = result.data;
|
|
33
|
+
}
|
|
34
|
+
if (options.query) {
|
|
35
|
+
const result = options.query.safeParse(context.query);
|
|
36
|
+
if (result.error) {
|
|
37
|
+
return {
|
|
38
|
+
data: null,
|
|
39
|
+
error: fromError(result.error)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
request.query = result.data;
|
|
43
|
+
}
|
|
44
|
+
if (options.requireHeaders && !(context.headers instanceof Headers)) {
|
|
45
|
+
return {
|
|
46
|
+
data: null,
|
|
47
|
+
error: { message: "Validation Error: Headers are required" }
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (options.requireRequest && !context.request) {
|
|
51
|
+
return {
|
|
52
|
+
data: null,
|
|
53
|
+
error: { message: "Validation Error: Request is required" }
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
data: request,
|
|
58
|
+
error: null
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function fromError(error) {
|
|
62
|
+
const errorMessages = [];
|
|
63
|
+
for (const issue of error.issues) {
|
|
64
|
+
const path = issue.path.join(".");
|
|
65
|
+
const message = issue.message;
|
|
66
|
+
if (path) {
|
|
67
|
+
errorMessages.push(`${message} at "${path}"`);
|
|
68
|
+
} else {
|
|
69
|
+
errorMessages.push(message);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
message: `Validation error: ${errorMessages.join(", ")}`
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function createSetHeader(headers) {
|
|
78
|
+
return (key, value) => {
|
|
79
|
+
headers.set(key, value);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function createGetHeader(headers) {
|
|
83
|
+
return (key) => {
|
|
84
|
+
return headers.get(key);
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
class APIError extends Error {
|
|
89
|
+
constructor(message, status = 500, code = "INTERNAL_SERVER_ERROR", headers = {}) {
|
|
90
|
+
super(message);
|
|
91
|
+
this.message = message;
|
|
92
|
+
this.status = status;
|
|
93
|
+
this.code = code;
|
|
94
|
+
this.headers = headers;
|
|
95
|
+
this.name = "APIError";
|
|
96
|
+
this.message = message;
|
|
97
|
+
this.status = status;
|
|
98
|
+
this.code = code;
|
|
99
|
+
this.headers = headers;
|
|
100
|
+
this.stack = "";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const algorithm = { name: "HMAC", hash: "SHA-256" };
|
|
105
|
+
const getCryptoKey = async (secret) => {
|
|
106
|
+
const secretBuf = typeof secret === "string" ? new TextEncoder().encode(secret) : secret;
|
|
107
|
+
return await crypto.subtle.importKey("raw", secretBuf, algorithm, false, [
|
|
108
|
+
"sign",
|
|
109
|
+
"verify"
|
|
110
|
+
]);
|
|
111
|
+
};
|
|
112
|
+
const makeSignature = async (value, secret) => {
|
|
113
|
+
const key = await getCryptoKey(secret);
|
|
114
|
+
const signature = await crypto.subtle.sign(
|
|
115
|
+
algorithm.name,
|
|
116
|
+
key,
|
|
117
|
+
new TextEncoder().encode(value)
|
|
118
|
+
);
|
|
119
|
+
return btoa(String.fromCharCode(...new Uint8Array(signature)));
|
|
120
|
+
};
|
|
121
|
+
const verifySignature = async (base64Signature, value, secret) => {
|
|
122
|
+
try {
|
|
123
|
+
const signatureBinStr = atob(base64Signature);
|
|
124
|
+
const signature = new Uint8Array(signatureBinStr.length);
|
|
125
|
+
for (let i = 0, len = signatureBinStr.length; i < len; i++) {
|
|
126
|
+
signature[i] = signatureBinStr.charCodeAt(i);
|
|
127
|
+
}
|
|
128
|
+
return await crypto.subtle.verify(
|
|
129
|
+
algorithm,
|
|
130
|
+
secret,
|
|
131
|
+
signature,
|
|
132
|
+
new TextEncoder().encode(value)
|
|
133
|
+
);
|
|
134
|
+
} catch (e) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const validCookieNameRegEx = /^[\w!#$%&'*.^`|~+-]+$/;
|
|
139
|
+
const validCookieValueRegEx = /^[ !#-:<-[\]-~]*$/;
|
|
140
|
+
const parse = (cookie, name) => {
|
|
141
|
+
const pairs = cookie.trim().split(";");
|
|
142
|
+
return pairs.reduce((parsedCookie, pairStr) => {
|
|
143
|
+
pairStr = pairStr.trim();
|
|
144
|
+
const valueStartPos = pairStr.indexOf("=");
|
|
145
|
+
if (valueStartPos === -1) {
|
|
146
|
+
return parsedCookie;
|
|
147
|
+
}
|
|
148
|
+
const cookieName = pairStr.substring(0, valueStartPos).trim();
|
|
149
|
+
if (name && name !== cookieName || !validCookieNameRegEx.test(cookieName)) {
|
|
150
|
+
return parsedCookie;
|
|
151
|
+
}
|
|
152
|
+
let cookieValue = pairStr.substring(valueStartPos + 1).trim();
|
|
153
|
+
if (cookieValue.startsWith('"') && cookieValue.endsWith('"')) {
|
|
154
|
+
cookieValue = cookieValue.slice(1, -1);
|
|
155
|
+
}
|
|
156
|
+
if (validCookieValueRegEx.test(cookieValue)) {
|
|
157
|
+
parsedCookie[cookieName] = decodeURIComponent(cookieValue);
|
|
158
|
+
}
|
|
159
|
+
return parsedCookie;
|
|
160
|
+
}, {});
|
|
161
|
+
};
|
|
162
|
+
const parseSigned = async (cookie, secret, name) => {
|
|
163
|
+
const parsedCookie = {};
|
|
164
|
+
const secretKey = await getCryptoKey(secret);
|
|
165
|
+
for (const [key, value] of Object.entries(parse(cookie, name))) {
|
|
166
|
+
const signatureStartPos = value.lastIndexOf(".");
|
|
167
|
+
if (signatureStartPos < 1) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const signedValue = value.substring(0, signatureStartPos);
|
|
171
|
+
const signature = value.substring(signatureStartPos + 1);
|
|
172
|
+
if (signature.length !== 44 || !signature.endsWith("=")) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const isVerified = await verifySignature(signature, signedValue, secretKey);
|
|
176
|
+
parsedCookie[key] = isVerified ? signedValue : false;
|
|
177
|
+
}
|
|
178
|
+
return parsedCookie;
|
|
179
|
+
};
|
|
180
|
+
const _serialize = (name, value, opt = {}) => {
|
|
181
|
+
let cookie = `${name}=${value}`;
|
|
182
|
+
if (name.startsWith("__Secure-") && !opt.secure) {
|
|
183
|
+
opt.secure = true;
|
|
184
|
+
}
|
|
185
|
+
if (name.startsWith("__Host-")) {
|
|
186
|
+
if (!opt.secure) {
|
|
187
|
+
opt.secure = true;
|
|
188
|
+
}
|
|
189
|
+
if (opt.path !== "/") {
|
|
190
|
+
opt.path = "/";
|
|
191
|
+
}
|
|
192
|
+
if (opt.domain) {
|
|
193
|
+
opt.domain = void 0;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (opt && typeof opt.maxAge === "number" && opt.maxAge >= 0) {
|
|
197
|
+
if (opt.maxAge > 3456e4) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
"Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration."
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
cookie += `; Max-Age=${Math.floor(opt.maxAge)}`;
|
|
203
|
+
}
|
|
204
|
+
if (opt.domain && opt.prefix !== "host") {
|
|
205
|
+
cookie += `; Domain=${opt.domain}`;
|
|
206
|
+
}
|
|
207
|
+
if (opt.path) {
|
|
208
|
+
cookie += `; Path=${opt.path}`;
|
|
209
|
+
}
|
|
210
|
+
if (opt.expires) {
|
|
211
|
+
if (opt.expires.getTime() - Date.now() > 3456e7) {
|
|
212
|
+
throw new Error(
|
|
213
|
+
"Cookies Expires SHOULD NOT be greater than 400 days (34560000 seconds) in the future."
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
cookie += `; Expires=${opt.expires.toUTCString()}`;
|
|
217
|
+
}
|
|
218
|
+
if (opt.httpOnly) {
|
|
219
|
+
cookie += "; HttpOnly";
|
|
220
|
+
}
|
|
221
|
+
if (opt.secure) {
|
|
222
|
+
cookie += "; Secure";
|
|
223
|
+
}
|
|
224
|
+
if (opt.sameSite) {
|
|
225
|
+
cookie += `; SameSite=${opt.sameSite.charAt(0).toUpperCase() + opt.sameSite.slice(1)}`;
|
|
226
|
+
}
|
|
227
|
+
if (opt.partitioned) {
|
|
228
|
+
if (!opt.secure) {
|
|
229
|
+
throw new Error("Partitioned Cookie must have Secure attributes");
|
|
230
|
+
}
|
|
231
|
+
cookie += "; Partitioned";
|
|
232
|
+
}
|
|
233
|
+
return cookie;
|
|
234
|
+
};
|
|
235
|
+
const serialize = (name, value, opt) => {
|
|
236
|
+
value = encodeURIComponent(value);
|
|
237
|
+
return _serialize(name, value, opt);
|
|
238
|
+
};
|
|
239
|
+
const serializeSigned = async (name, value, secret, opt = {}) => {
|
|
240
|
+
const signature = await makeSignature(value, secret);
|
|
241
|
+
value = `${value}.${signature}`;
|
|
242
|
+
value = encodeURIComponent(value);
|
|
243
|
+
return _serialize(name, value, opt);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const getCookie = (cookie, key, prefix) => {
|
|
247
|
+
if (!cookie) {
|
|
248
|
+
return void 0;
|
|
249
|
+
}
|
|
250
|
+
let finalKey = key;
|
|
251
|
+
if (prefix) {
|
|
252
|
+
if (prefix === "secure") {
|
|
253
|
+
finalKey = "__Secure-" + key;
|
|
254
|
+
} else if (prefix === "host") {
|
|
255
|
+
finalKey = "__Host-" + key;
|
|
256
|
+
} else {
|
|
257
|
+
return void 0;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const obj = parse(cookie, finalKey);
|
|
261
|
+
return obj[finalKey];
|
|
262
|
+
};
|
|
263
|
+
const setCookie = (header, name, value, opt) => {
|
|
264
|
+
const existingCookies = header.get("Set-Cookie");
|
|
265
|
+
if (existingCookies) {
|
|
266
|
+
const cookies = existingCookies.split(", ");
|
|
267
|
+
const updatedCookies = cookies.filter(
|
|
268
|
+
(cookie2) => !cookie2.startsWith(`${name}=`)
|
|
269
|
+
);
|
|
270
|
+
header.delete("Set-Cookie");
|
|
271
|
+
updatedCookies.forEach((cookie2) => header.append("Set-Cookie", cookie2));
|
|
272
|
+
}
|
|
273
|
+
let cookie;
|
|
274
|
+
if (opt?.prefix === "secure") {
|
|
275
|
+
cookie = serialize("__Secure-" + name, value, {
|
|
276
|
+
path: "/",
|
|
277
|
+
...opt,
|
|
278
|
+
secure: true
|
|
279
|
+
});
|
|
280
|
+
} else if (opt?.prefix === "host") {
|
|
281
|
+
cookie = serialize("__Host-" + name, value, {
|
|
282
|
+
...opt,
|
|
283
|
+
path: "/",
|
|
284
|
+
secure: true,
|
|
285
|
+
domain: void 0
|
|
286
|
+
});
|
|
287
|
+
} else {
|
|
288
|
+
cookie = serialize(name, value, { path: "/", ...opt });
|
|
289
|
+
}
|
|
290
|
+
header.append("Set-Cookie", cookie);
|
|
291
|
+
};
|
|
292
|
+
const setSignedCookie = async (header, name, value, secret, opt) => {
|
|
293
|
+
let cookie;
|
|
294
|
+
if (opt?.prefix === "secure") {
|
|
295
|
+
cookie = await serializeSigned("__Secure-" + name, value, secret, {
|
|
296
|
+
path: "/",
|
|
297
|
+
...opt,
|
|
298
|
+
secure: true
|
|
299
|
+
});
|
|
300
|
+
} else if (opt?.prefix === "host") {
|
|
301
|
+
cookie = await serializeSigned("__Host-" + name, value, secret, {
|
|
302
|
+
...opt,
|
|
303
|
+
path: "/",
|
|
304
|
+
secure: true,
|
|
305
|
+
domain: void 0
|
|
306
|
+
});
|
|
307
|
+
} else {
|
|
308
|
+
cookie = await serializeSigned(name, value, secret, { path: "/", ...opt });
|
|
309
|
+
}
|
|
310
|
+
header.append("Set-Cookie", cookie);
|
|
311
|
+
};
|
|
312
|
+
const getSignedCookie = async (header, secret, key, prefix) => {
|
|
313
|
+
const cookie = header.get("cookie");
|
|
314
|
+
if (!cookie) {
|
|
315
|
+
return void 0;
|
|
316
|
+
}
|
|
317
|
+
let finalKey = key;
|
|
318
|
+
if (prefix) {
|
|
319
|
+
if (prefix === "secure") {
|
|
320
|
+
finalKey = "__Secure-" + key;
|
|
321
|
+
} else if (prefix === "host") {
|
|
322
|
+
finalKey = "__Host-" + key;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const obj = await parseSigned(cookie, secret, finalKey);
|
|
326
|
+
return obj[finalKey];
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const createResponse = (handlerResponse, response) => {
|
|
330
|
+
if (handlerResponse instanceof Response) {
|
|
331
|
+
response.headers.forEach((value, key) => {
|
|
332
|
+
handlerResponse.headers.set(key, value);
|
|
333
|
+
});
|
|
334
|
+
return handlerResponse;
|
|
335
|
+
} else if (handlerResponse?._flag === "json") {
|
|
336
|
+
const responseObj = new Response(
|
|
337
|
+
JSON.stringify(
|
|
338
|
+
handlerResponse.routerResponse?.body || handlerResponse.body
|
|
339
|
+
),
|
|
340
|
+
{
|
|
341
|
+
status: handlerResponse.routerResponse?.status || 200,
|
|
342
|
+
headers: handlerResponse.routerResponse?.headers
|
|
343
|
+
}
|
|
344
|
+
);
|
|
345
|
+
response.headers.forEach((value, key) => {
|
|
346
|
+
responseObj.headers.set(key, value);
|
|
347
|
+
});
|
|
348
|
+
return responseObj;
|
|
349
|
+
} else {
|
|
350
|
+
const responseObj = new Response(
|
|
351
|
+
handlerResponse ? JSON.stringify(handlerResponse) : void 0
|
|
352
|
+
);
|
|
353
|
+
response.headers.forEach((value, key) => {
|
|
354
|
+
responseObj.headers.set(key, value);
|
|
355
|
+
});
|
|
356
|
+
return responseObj;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
const runMiddleware = async (options, context) => {
|
|
360
|
+
let finalContext = {};
|
|
361
|
+
for (const middleware of options.use || []) {
|
|
362
|
+
const result = await middleware(context);
|
|
363
|
+
if (result?.context && result._flag === "context") {
|
|
364
|
+
context = { ...context, ...result.context };
|
|
365
|
+
} else {
|
|
366
|
+
finalContext = { ...result, ...finalContext };
|
|
367
|
+
context.context = finalContext;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return finalContext;
|
|
371
|
+
};
|
|
372
|
+
const createEndpoint = (path, options, handler) => {
|
|
373
|
+
const internalHandler = async (...inputCtx) => {
|
|
374
|
+
let response = new Response();
|
|
375
|
+
const { asResponse, ...ctx } = inputCtx[0] || {};
|
|
376
|
+
const { data, error } = runValidation(options, ctx);
|
|
377
|
+
if (error) {
|
|
378
|
+
throw new APIError(error.message, 400);
|
|
379
|
+
}
|
|
380
|
+
const context = {
|
|
381
|
+
json: createJSON({
|
|
382
|
+
asResponse,
|
|
383
|
+
response
|
|
384
|
+
}),
|
|
385
|
+
body: "body" in data ? data.body : void 0,
|
|
386
|
+
path,
|
|
387
|
+
method: "method" in ctx ? ctx.method : void 0,
|
|
388
|
+
query: "query" in data ? data.query : void 0,
|
|
389
|
+
params: "params" in ctx ? ctx.params : void 0,
|
|
390
|
+
headers: "headers" in ctx ? ctx.headers : void 0,
|
|
391
|
+
request: "request" in ctx ? ctx.request : void 0,
|
|
392
|
+
setHeader: createSetHeader(response.headers),
|
|
393
|
+
getHeader: (key) => {
|
|
394
|
+
const requestHeaders = "headers" in ctx && ctx.headers instanceof Headers ? ctx.headers : "request" in ctx && ctx.request instanceof Request ? ctx.request.headers : null;
|
|
395
|
+
if (!requestHeaders)
|
|
396
|
+
return null;
|
|
397
|
+
return requestHeaders.get(key);
|
|
398
|
+
},
|
|
399
|
+
setCookie: (name, value, options2) => {
|
|
400
|
+
setCookie(response.headers, name, value, options2);
|
|
401
|
+
},
|
|
402
|
+
getCookie(key, prefix) {
|
|
403
|
+
const headers = context.headers?.get("cookie");
|
|
404
|
+
if (!headers)
|
|
405
|
+
return void 0;
|
|
406
|
+
return getCookie(headers, key, prefix);
|
|
407
|
+
},
|
|
408
|
+
setSignedCookie(key, value, secret, options2) {
|
|
409
|
+
return setSignedCookie(response.headers, key, value, secret, options2);
|
|
410
|
+
},
|
|
411
|
+
async getSignedCookie(key, secret, prefix) {
|
|
412
|
+
const headers = context.headers;
|
|
413
|
+
if (!headers)
|
|
414
|
+
return void 0;
|
|
415
|
+
return getSignedCookie(headers, secret, key, prefix);
|
|
416
|
+
},
|
|
417
|
+
redirect: (url) => {
|
|
418
|
+
const apiError = new APIError("Redirecting", 302, "FOUND", {
|
|
419
|
+
Location: url,
|
|
420
|
+
...response.headers
|
|
421
|
+
});
|
|
422
|
+
return apiError;
|
|
423
|
+
},
|
|
424
|
+
context: {}
|
|
425
|
+
};
|
|
426
|
+
const finalContext = await runMiddleware(options, context);
|
|
427
|
+
context.context = finalContext;
|
|
428
|
+
const handlerResponse = await handler(context).catch((e) => {
|
|
429
|
+
if (e instanceof APIError && asResponse) {
|
|
430
|
+
const headers = response.headers;
|
|
431
|
+
for (const [key, value] of Object.entries(e.headers)) {
|
|
432
|
+
headers.set(key, value);
|
|
433
|
+
}
|
|
434
|
+
return new Response(null, {
|
|
435
|
+
status: e.status,
|
|
436
|
+
headers
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
throw e;
|
|
440
|
+
});
|
|
441
|
+
response = createResponse(handlerResponse, response);
|
|
442
|
+
const res = asResponse ? response : handlerResponse;
|
|
443
|
+
return res;
|
|
444
|
+
};
|
|
445
|
+
internalHandler.path = path;
|
|
446
|
+
internalHandler.options = options;
|
|
447
|
+
return internalHandler;
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
async function getBody(request) {
|
|
451
|
+
const contentType = request.headers.get("content-type") || "";
|
|
452
|
+
if (!request.body) {
|
|
453
|
+
return void 0;
|
|
454
|
+
}
|
|
455
|
+
if (contentType.includes("application/json")) {
|
|
456
|
+
return await request.json();
|
|
457
|
+
}
|
|
458
|
+
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
459
|
+
const formData = await request.formData();
|
|
460
|
+
const result = {};
|
|
461
|
+
formData.forEach((value, key) => {
|
|
462
|
+
result[key] = value.toString();
|
|
463
|
+
});
|
|
464
|
+
return result;
|
|
465
|
+
}
|
|
466
|
+
if (contentType.includes("multipart/form-data")) {
|
|
467
|
+
const formData = await request.formData();
|
|
468
|
+
const result = {};
|
|
469
|
+
formData.forEach((value, key) => {
|
|
470
|
+
result[key] = value;
|
|
471
|
+
});
|
|
472
|
+
return result;
|
|
473
|
+
}
|
|
474
|
+
if (contentType.includes("text/plain")) {
|
|
475
|
+
return await request.text();
|
|
476
|
+
}
|
|
477
|
+
if (contentType.includes("application/octet-stream")) {
|
|
478
|
+
return await request.arrayBuffer();
|
|
479
|
+
}
|
|
480
|
+
if (contentType.includes("application/pdf") || contentType.includes("image/") || contentType.includes("video/")) {
|
|
481
|
+
const blob = await request.blob();
|
|
482
|
+
return blob;
|
|
483
|
+
}
|
|
484
|
+
if (contentType.includes("application/stream") || request.body instanceof ReadableStream) {
|
|
485
|
+
return request.body;
|
|
486
|
+
}
|
|
487
|
+
return await request.text();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const createRouter = (endpoints, config) => {
|
|
491
|
+
const _endpoints = Object.values(endpoints);
|
|
492
|
+
const router = createRouter$1();
|
|
493
|
+
for (const endpoint of _endpoints) {
|
|
494
|
+
if (Array.isArray(endpoint.options?.method)) {
|
|
495
|
+
for (const method of endpoint.options.method) {
|
|
496
|
+
addRoute(router, method, endpoint.path, endpoint);
|
|
497
|
+
}
|
|
498
|
+
} else {
|
|
499
|
+
addRoute(router, endpoint.options.method, endpoint.path, endpoint);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const middlewareRouter = createRouter$1();
|
|
503
|
+
for (const route of config?.routerMiddleware || []) {
|
|
504
|
+
addRoute(middlewareRouter, "*", route.path, route.middleware);
|
|
505
|
+
}
|
|
506
|
+
const handler = async (request) => {
|
|
507
|
+
const url = new URL(request.url);
|
|
508
|
+
let path = url.pathname;
|
|
509
|
+
if (config?.basePath) {
|
|
510
|
+
path = path.split(config.basePath)[1];
|
|
511
|
+
}
|
|
512
|
+
if (!path?.length) {
|
|
513
|
+
config?.onError?.(new APIError("NOT_FOUND"));
|
|
514
|
+
console.warn(
|
|
515
|
+
`[better-call]: Make sure the URL has the basePath (${config?.basePath}).`
|
|
516
|
+
);
|
|
517
|
+
return new Response(null, {
|
|
518
|
+
status: 404,
|
|
519
|
+
statusText: "Not Found"
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
const method = request.method;
|
|
523
|
+
const route = findRoute(router, method, path);
|
|
524
|
+
const handler2 = route?.data;
|
|
525
|
+
const body = await getBody(request);
|
|
526
|
+
const headers = request.headers;
|
|
527
|
+
const query = Object.fromEntries(url.searchParams);
|
|
528
|
+
const routerMiddleware = findAllRoutes(middlewareRouter, "*", path);
|
|
529
|
+
if (!handler2) {
|
|
530
|
+
return new Response(null, {
|
|
531
|
+
status: 404,
|
|
532
|
+
statusText: "Not Found"
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
try {
|
|
536
|
+
let middlewareContext = {};
|
|
537
|
+
if (routerMiddleware?.length) {
|
|
538
|
+
for (const route2 of routerMiddleware) {
|
|
539
|
+
const middleware = route2.data;
|
|
540
|
+
const res = await middleware({
|
|
541
|
+
path,
|
|
542
|
+
method,
|
|
543
|
+
headers,
|
|
544
|
+
params: route2?.params,
|
|
545
|
+
request,
|
|
546
|
+
body,
|
|
547
|
+
query,
|
|
548
|
+
context: {
|
|
549
|
+
...config?.extraContext
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
if (res instanceof Response) {
|
|
553
|
+
return res;
|
|
554
|
+
}
|
|
555
|
+
if (res?._flag === "json") {
|
|
556
|
+
return new Response(JSON.stringify(res), {
|
|
557
|
+
headers: res.headers
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
if (res) {
|
|
561
|
+
middlewareContext = {
|
|
562
|
+
...res,
|
|
563
|
+
...middlewareContext
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
const handlerRes = await handler2({
|
|
569
|
+
path,
|
|
570
|
+
method,
|
|
571
|
+
headers,
|
|
572
|
+
params: route?.params,
|
|
573
|
+
request,
|
|
574
|
+
body,
|
|
575
|
+
query,
|
|
576
|
+
context: {
|
|
577
|
+
...middlewareContext,
|
|
578
|
+
...config?.extraContext
|
|
579
|
+
},
|
|
580
|
+
asResponse: true
|
|
581
|
+
});
|
|
582
|
+
if (handlerRes instanceof Response) {
|
|
583
|
+
return handlerRes;
|
|
584
|
+
}
|
|
585
|
+
return new Response(handlerRes ? JSON.stringify(handlerRes) : void 0, {
|
|
586
|
+
status: 200
|
|
587
|
+
});
|
|
588
|
+
} catch (e) {
|
|
589
|
+
if (config?.onError) {
|
|
590
|
+
const onErrorRes = await config.onError(e);
|
|
591
|
+
if (onErrorRes instanceof Response) {
|
|
592
|
+
return onErrorRes;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (e instanceof APIError) {
|
|
596
|
+
return new Response(
|
|
597
|
+
e.message ? JSON.stringify({
|
|
598
|
+
message: e.message,
|
|
599
|
+
code: e.code
|
|
600
|
+
}) : null,
|
|
601
|
+
{
|
|
602
|
+
status: e.status,
|
|
603
|
+
headers: e.headers
|
|
604
|
+
}
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
if (config?.throwError) {
|
|
608
|
+
throw e;
|
|
609
|
+
}
|
|
610
|
+
return new Response(null, {
|
|
611
|
+
status: 500,
|
|
612
|
+
statusText: "Internal Server Error"
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
return {
|
|
617
|
+
handler,
|
|
618
|
+
endpoints
|
|
619
|
+
};
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
function createMiddleware(optionsOrHandler, handler) {
|
|
623
|
+
if (typeof optionsOrHandler === "function") {
|
|
624
|
+
return createEndpoint(
|
|
625
|
+
"*",
|
|
626
|
+
{
|
|
627
|
+
method: "*"
|
|
628
|
+
},
|
|
629
|
+
optionsOrHandler
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
if (!handler) {
|
|
633
|
+
throw new Error("Middleware handler is required");
|
|
634
|
+
}
|
|
635
|
+
const endpoint = createEndpoint(
|
|
636
|
+
"*",
|
|
637
|
+
{
|
|
638
|
+
...optionsOrHandler,
|
|
639
|
+
method: "*"
|
|
640
|
+
},
|
|
641
|
+
handler
|
|
642
|
+
);
|
|
643
|
+
return endpoint;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export { createEndpoint, createGetHeader, createJSON, createMiddleware, createRouter, createSetHeader, fromError, getCookie, getSignedCookie, parse, parseSigned, runValidation, serialize, serializeSigned, setCookie, setSignedCookie };
|
package/package.json
CHANGED
|
@@ -1,44 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-call",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"
|
|
3
|
+
"version": "0.2.13-beta.1",
|
|
4
|
+
"description": "",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
|
-
"module": "./dist/index.js",
|
|
7
6
|
"types": "./dist/index.d.ts",
|
|
8
7
|
"scripts": {
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"dev": "tsup --watch",
|
|
15
|
-
"dev:dts": "tsup --watch --dts"
|
|
8
|
+
"build": "unbuild",
|
|
9
|
+
"prepack": "unbuild",
|
|
10
|
+
"release": "build && bumpp && npm publish",
|
|
11
|
+
"release:next": "bun run build && bumpp && npm publish --tag next --no-git-checks",
|
|
12
|
+
"publish-test": "vitest run"
|
|
16
13
|
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [],
|
|
18
|
+
"author": "",
|
|
19
|
+
"license": "ISC",
|
|
17
20
|
"devDependencies": {
|
|
18
|
-
"
|
|
21
|
+
"bumpp": "^9.7.1",
|
|
22
|
+
"unbuild": "^2.0.0",
|
|
19
23
|
"@types/bun": "latest",
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"vitest": "^2.0.4",
|
|
24
|
+
"@types/node": "^22.7.9",
|
|
25
|
+
"typescript": "^5.6.3",
|
|
26
|
+
"np": "^10.0.7",
|
|
27
|
+
"vitest": "^2.1.3",
|
|
25
28
|
"zod": "^3.23.8"
|
|
26
29
|
},
|
|
27
30
|
"dependencies": {
|
|
28
|
-
"@better-fetch/fetch": "^1.1.4",
|
|
29
31
|
"rou3": "^0.5.1",
|
|
30
32
|
"uncrypto": "^0.1.3"
|
|
31
33
|
},
|
|
32
34
|
"exports": {
|
|
33
35
|
".": {
|
|
34
|
-
"
|
|
35
|
-
"import": "./dist/index.js",
|
|
36
|
+
"import": "./dist/index.mjs",
|
|
36
37
|
"require": "./dist/index.cjs"
|
|
37
|
-
},
|
|
38
|
-
"./client": {
|
|
39
|
-
"types": "./dist/client.d.ts",
|
|
40
|
-
"import": "./dist/client.js",
|
|
41
|
-
"require": "./dist/client.cjs"
|
|
42
38
|
}
|
|
43
39
|
},
|
|
44
40
|
"files": [
|