@server/next 0.26.0 → 0.27.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/index.js +1952 -0
- package/package.json +19 -13
- package/readme.md +1 -0
- package/src/{helpers/jsx.js → jsx/jsx-dev-runtime.js} +32 -17
- package/src/{helpers/jsx.test.jsx → jsx/jsx-runtime.test.jsx} +1 -1
- package/src/ServerError.js +0 -27
- package/src/auth/NoSession.js +0 -19
- package/src/auth/auth-cookie.test.js +0 -42
- package/src/auth/auth-token.test.js +0 -110
- package/src/auth/auth.js +0 -62
- package/src/auth/index.js +0 -104
- package/src/auth/index.test.js +0 -133
- package/src/auth/logout.js +0 -20
- package/src/auth/providers/email.js +0 -145
- package/src/auth/providers/github.js +0 -84
- package/src/auth/providers/index.js +0 -4
- package/src/auth/session.js +0 -18
- package/src/auth/updateUser.js +0 -6
- package/src/auth/user.js +0 -9
- package/src/context/node.js +0 -68
- package/src/context/parseBody.js +0 -107
- package/src/context/parseBody.test.js +0 -60
- package/src/context/parseCookies.js +0 -9
- package/src/context/winter.js +0 -46
- package/src/errors/index.js +0 -36
- package/src/helpers/StatusError.js +0 -6
- package/src/helpers/bucket.js +0 -89
- package/src/helpers/bucket.test.js +0 -51
- package/src/helpers/color.js +0 -30
- package/src/helpers/config.js +0 -64
- package/src/helpers/cookies.test.js +0 -23
- package/src/helpers/cors.js +0 -24
- package/src/helpers/cors.test.js +0 -64
- package/src/helpers/createCookies.js +0 -16
- package/src/helpers/createId.js +0 -51
- package/src/helpers/createWebsocket.js +0 -21
- package/src/helpers/define.js +0 -18
- package/src/helpers/getMachine.js +0 -26
- package/src/helpers/handleRequest.js +0 -34
- package/src/helpers/index.js +0 -14
- package/src/helpers/iterate.js +0 -8
- package/src/helpers/iteratorAsyncToReadable.js +0 -20
- package/src/helpers/iteratorToReadable.js +0 -10
- package/src/helpers/parseHeaders.js +0 -15
- package/src/helpers/toWeb.js +0 -15
- package/src/helpers/types.js +0 -81
- package/src/helpers/validate.js +0 -34
- package/src/index.d.ts +0 -198
- package/src/index.js +0 -250
- package/src/index.test.js +0 -87
- package/src/index.types.ts +0 -28
- package/src/middle/assets.js +0 -17
- package/src/middle/assets.test.js +0 -10
- package/src/middle/index.js +0 -7
- package/src/middle/openapi.js +0 -147
- package/src/middle/timer.js +0 -14
- package/src/parseResponse.js +0 -127
- package/src/pathPattern.js +0 -47
- package/src/pathPattern.test.js +0 -99
- package/src/polyfill.js +0 -18
- package/src/reply.js +0 -153
- package/src/router.js +0 -53
- package/src/router.test.js +0 -57
- package/src/session.test.js +0 -65
- package/src/test/toSucceed.js +0 -64
- package/src/url.test.js +0 -26
package/index.js
ADDED
|
@@ -0,0 +1,1952 @@
|
|
|
1
|
+
// src/ServerError.ts
|
|
2
|
+
var ServerError = class _ServerError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
status;
|
|
5
|
+
constructor(code, status2, message, vars = {}) {
|
|
6
|
+
let messageStr;
|
|
7
|
+
if (typeof message === "function") {
|
|
8
|
+
messageStr = message(vars);
|
|
9
|
+
} else {
|
|
10
|
+
messageStr = message;
|
|
11
|
+
}
|
|
12
|
+
if (typeof messageStr !== "string")
|
|
13
|
+
throw Error(`Invalid error ${messageStr}`);
|
|
14
|
+
for (const key in vars) {
|
|
15
|
+
let value = vars[key];
|
|
16
|
+
value = Array.isArray(value) ? value.join(",") : value;
|
|
17
|
+
const regex = new RegExp(`\\{${key}\\}`, "g");
|
|
18
|
+
messageStr = messageStr.replace(regex, value);
|
|
19
|
+
}
|
|
20
|
+
super(messageStr);
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.message = messageStr;
|
|
23
|
+
this.status = status2;
|
|
24
|
+
}
|
|
25
|
+
static extend(errors) {
|
|
26
|
+
for (const code in errors) {
|
|
27
|
+
const error = errors[code];
|
|
28
|
+
if (typeof error === "string") {
|
|
29
|
+
_ServerError[code] = (vars = {}) => new _ServerError(code, 500, error, vars);
|
|
30
|
+
} else {
|
|
31
|
+
_ServerError[code] = (vars = {}) => new _ServerError(code, error.status, error.message, vars);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return errors;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var TypedServerError = ServerError;
|
|
38
|
+
var ServerError_default = TypedServerError;
|
|
39
|
+
|
|
40
|
+
// src/errors/index.ts
|
|
41
|
+
ServerError_default.extend({
|
|
42
|
+
NO_STORE: "You need a 'store' to write 'ctx.session'",
|
|
43
|
+
NO_STORE_WRITE: "You need a 'store' to write 'ctx.session.{key}'",
|
|
44
|
+
NO_STORE_READ: "You need a 'store' to read 'ctx.session.{key}'",
|
|
45
|
+
AUTH_ARGON_NEEDED: "Argon2 is needed for the auth module, please install it with 'npm i argon2'",
|
|
46
|
+
AUTH_INVALID_TOKEN: "Invalid Authorization token",
|
|
47
|
+
AUTH_INVALID_COOKIE: "Invalid Authorization cookie",
|
|
48
|
+
AUTH_INVALID_HEADER: "Invalid authorization header {type}, must send 'Bearer {TOKEN}' (with space)",
|
|
49
|
+
AUTH_INVALID_STRATEGY: "Invalid Authorization type '{strategy}', valid one is '{valid}'",
|
|
50
|
+
AUTH_NO_PROVIDER: "No provider passed to the option 'auth.provider'",
|
|
51
|
+
AUTH_INVALID_PROVIDER: "Invalid provider '{provider}', valid ones are: '{valid}'",
|
|
52
|
+
AUTH_NO_SESSION: { status: 401, message: "Invalid session" },
|
|
53
|
+
AUTH_NO_USER: {
|
|
54
|
+
status: 401,
|
|
55
|
+
message: "Credentials do not correspond to a user"
|
|
56
|
+
},
|
|
57
|
+
LOGIN_NO_EMAIL: "The email is required to log in",
|
|
58
|
+
LOGIN_INVALID_EMAIL: "The email you wrote is not correct",
|
|
59
|
+
LOGIN_NO_PASSWORD: "The email is required to log in",
|
|
60
|
+
LOGIN_INVALID_PASSWORD: "The password you wrote is not correct",
|
|
61
|
+
LOGIN_WRONG_ACCOUNT: "That email does not correspond to any account",
|
|
62
|
+
LOGIN_WRONG_PASSWORD: "That is not the valid password",
|
|
63
|
+
REGISTER_NO_EMAIL: "Email needed",
|
|
64
|
+
REGISTER_INVALID_EMAIL: "The email you wrote is not correct",
|
|
65
|
+
REGISTER_NO_PASSWORD: "Password needed",
|
|
66
|
+
REGISTER_INVALID_PASSWORD: "The password you wrote is not correct",
|
|
67
|
+
REGISTER_EMAIL_EXISTS: "Email is already registered"
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// src/polyfill.ts
|
|
71
|
+
globalThis.env = {};
|
|
72
|
+
if (typeof globalThis.Netlify !== "undefined") {
|
|
73
|
+
Object.assign(
|
|
74
|
+
globalThis.env,
|
|
75
|
+
globalThis.Netlify.env.toObject()
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (typeof process !== "undefined") {
|
|
79
|
+
Object.assign(globalThis.env, process.env);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/auth/updateUser.ts
|
|
83
|
+
async function updateUser(user, auth2, store) {
|
|
84
|
+
if (auth2.provider === "email") {
|
|
85
|
+
return await store.set(auth2.email, user);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/auth/providers/email.ts
|
|
90
|
+
var createSession = async (user, ctx) => {
|
|
91
|
+
const { strategy, session: session2, cleanUser, redirect: redirect2 = "/user" } = ctx.options.auth;
|
|
92
|
+
user = await cleanUser(user);
|
|
93
|
+
const id = createId();
|
|
94
|
+
const provider = "email";
|
|
95
|
+
ctx.user = {
|
|
96
|
+
id,
|
|
97
|
+
strategy,
|
|
98
|
+
provider,
|
|
99
|
+
email: user.email
|
|
100
|
+
};
|
|
101
|
+
await session2.set(
|
|
102
|
+
id,
|
|
103
|
+
{ id, strategy, provider, user: user.email },
|
|
104
|
+
{ expires: "1w" }
|
|
105
|
+
);
|
|
106
|
+
if (!strategy) throw new Error(`Invalid strategy "${strategy}"`);
|
|
107
|
+
if (strategy.includes("token")) {
|
|
108
|
+
return status(201).json({ ...user, token: id });
|
|
109
|
+
}
|
|
110
|
+
if (strategy.includes("cookie")) {
|
|
111
|
+
return status(302).cookies({ authentication: id }).redirect(redirect2);
|
|
112
|
+
}
|
|
113
|
+
if (strategy.includes("jwt")) {
|
|
114
|
+
throw new Error("JWT auth not supported yet");
|
|
115
|
+
}
|
|
116
|
+
if (strategy.includes("key")) {
|
|
117
|
+
throw new Error("Key auth not supported yet");
|
|
118
|
+
}
|
|
119
|
+
throw new Error("Unknown auth type");
|
|
120
|
+
};
|
|
121
|
+
async function emailLogin(ctx) {
|
|
122
|
+
const { email, password } = ctx.body;
|
|
123
|
+
if (!email) throw ServerError_default.LOGIN_NO_EMAIL();
|
|
124
|
+
if (!/@/.test(email)) throw ServerError_default.LOGIN_INVALID_EMAIL();
|
|
125
|
+
if (!password) throw ServerError_default.LOGIN_NO_PASSWORD();
|
|
126
|
+
if (password.length < 8) throw ServerError_default.LOGIN_INVALID_PASSWORD();
|
|
127
|
+
const store = ctx.options.auth.store;
|
|
128
|
+
if (!await store.has(email)) throw ServerError_default.LOGIN_WRONG_EMAIL();
|
|
129
|
+
const user = await store.get(email);
|
|
130
|
+
const isValid = await verify(password, user.password);
|
|
131
|
+
if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
|
|
132
|
+
return createSession(user, ctx);
|
|
133
|
+
}
|
|
134
|
+
async function emailRegister(ctx) {
|
|
135
|
+
const { email, password, ...data } = ctx.body;
|
|
136
|
+
if (!email) throw ServerError_default.REGISTER_NO_EMAIL();
|
|
137
|
+
if (!/@/.test(email)) throw ServerError_default.REGISTER_INVALID_EMAIL();
|
|
138
|
+
if (!password) throw ServerError_default.REGISTER_NO_PASSWORD();
|
|
139
|
+
if (password.length < 8) throw ServerError_default.REGISTER_INVALID_PASSWORD();
|
|
140
|
+
const store = ctx.options.auth.store;
|
|
141
|
+
if (await store.has(email)) throw ServerError_default.REGISTER_EMAIL_EXISTS();
|
|
142
|
+
const time = (/* @__PURE__ */ new Date()).toISOString().replace(/\.[0-9]*/, "");
|
|
143
|
+
const user = {
|
|
144
|
+
id: createId(email),
|
|
145
|
+
strategy: ctx.options.auth.strategy,
|
|
146
|
+
provider: "email",
|
|
147
|
+
email,
|
|
148
|
+
password: await hash(password),
|
|
149
|
+
time,
|
|
150
|
+
...data
|
|
151
|
+
};
|
|
152
|
+
await store.set(email, user);
|
|
153
|
+
return createSession(user, ctx);
|
|
154
|
+
}
|
|
155
|
+
async function emailResetPassword() {
|
|
156
|
+
}
|
|
157
|
+
async function emailUpdatePassword(ctx) {
|
|
158
|
+
const passwords = ctx.body;
|
|
159
|
+
const fullUser = await ctx.options.auth.store.get(ctx.user.email);
|
|
160
|
+
if (!fullUser) throw ServerError_default.AUTH_NO_USER();
|
|
161
|
+
const isValid = await verify(passwords.previous, fullUser.password);
|
|
162
|
+
if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
|
|
163
|
+
fullUser.password = await hash(passwords.updated);
|
|
164
|
+
await updateUser(fullUser, ctx.user, ctx.options.auth.store);
|
|
165
|
+
return 200;
|
|
166
|
+
}
|
|
167
|
+
var email_default = {
|
|
168
|
+
login: emailLogin,
|
|
169
|
+
register: emailRegister,
|
|
170
|
+
reset: emailResetPassword,
|
|
171
|
+
password: emailUpdatePassword
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// src/reply.ts
|
|
175
|
+
var Reply = class {
|
|
176
|
+
res;
|
|
177
|
+
constructor() {
|
|
178
|
+
this.res = {
|
|
179
|
+
headers: {},
|
|
180
|
+
cookies: {}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
generateHeaders() {
|
|
184
|
+
const headers2 = new Headers(this.res.headers);
|
|
185
|
+
for (const cookie of createCookies(this.res.cookies)) {
|
|
186
|
+
headers2.append("set-cookie", cookie);
|
|
187
|
+
}
|
|
188
|
+
return headers2;
|
|
189
|
+
}
|
|
190
|
+
status(status2) {
|
|
191
|
+
this.res.status = status2;
|
|
192
|
+
return this;
|
|
193
|
+
}
|
|
194
|
+
type(type2) {
|
|
195
|
+
if (!type2) return this;
|
|
196
|
+
type2 = types_default[type2.replace(/^\./, "")] || type2;
|
|
197
|
+
return this.headers({ "content-type": type2 });
|
|
198
|
+
}
|
|
199
|
+
download(name, type2) {
|
|
200
|
+
if (name && !type2) type2 = name.split(".").pop();
|
|
201
|
+
if (type2) this.type(type2);
|
|
202
|
+
const filename = name ? `; filename="${name}"` : "";
|
|
203
|
+
return this.headers({ "content-disposition": `attachment${filename}` });
|
|
204
|
+
}
|
|
205
|
+
headers(headers2) {
|
|
206
|
+
if (!headers2 || typeof headers2 !== "object") return this;
|
|
207
|
+
for (const key in headers2) {
|
|
208
|
+
this.res.headers[key] = headers2[key];
|
|
209
|
+
}
|
|
210
|
+
return this;
|
|
211
|
+
}
|
|
212
|
+
cookies(cookies2) {
|
|
213
|
+
if (!cookies2 || typeof cookies2 !== "object") return this;
|
|
214
|
+
for (const key in cookies2) {
|
|
215
|
+
if (typeof cookies2[key] === "string") {
|
|
216
|
+
this.res.cookies[key] = { value: cookies2[key] };
|
|
217
|
+
} else {
|
|
218
|
+
this.res.cookies[key] = cookies2[key];
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return this;
|
|
222
|
+
}
|
|
223
|
+
json(body) {
|
|
224
|
+
return this.headers({
|
|
225
|
+
"content-type": "application/json"
|
|
226
|
+
}).send(JSON.stringify(body));
|
|
227
|
+
}
|
|
228
|
+
redirect(Location) {
|
|
229
|
+
return this.headers({ Location }).status(302).send();
|
|
230
|
+
}
|
|
231
|
+
async file(path2, renderer = async (data) => data) {
|
|
232
|
+
try {
|
|
233
|
+
const fs2 = await import("fs/promises");
|
|
234
|
+
const data = await fs2.readFile(path2);
|
|
235
|
+
const ext = path2.split(".").pop();
|
|
236
|
+
return this.type(ext).send(await renderer(data));
|
|
237
|
+
} catch (error) {
|
|
238
|
+
if (error.code === "ENOENT") {
|
|
239
|
+
return this.status(404).send();
|
|
240
|
+
}
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
async view(path2, renderer = async (data) => data, ctx) {
|
|
245
|
+
if (!ctx?.options.views) {
|
|
246
|
+
throw new Error("Views not enabled");
|
|
247
|
+
}
|
|
248
|
+
const data = await ctx.options.views.read(path2);
|
|
249
|
+
if (!data) return this.status(404).send();
|
|
250
|
+
return this.type(path2.split(".").pop()).send(await renderer(data));
|
|
251
|
+
}
|
|
252
|
+
send(body = "") {
|
|
253
|
+
const { status: status2 = 200 } = this.res;
|
|
254
|
+
if (typeof body === "string") {
|
|
255
|
+
if (!this.res.headers["content-type"]) {
|
|
256
|
+
const isHtml = body.startsWith("<");
|
|
257
|
+
this.res.headers["content-type"] = isHtml ? "text/html" : "text/plain";
|
|
258
|
+
}
|
|
259
|
+
const headers2 = this.generateHeaders();
|
|
260
|
+
return new Response(body, { status: status2, headers: headers2 });
|
|
261
|
+
}
|
|
262
|
+
const name = body?.constructor?.name;
|
|
263
|
+
if (name === "Buffer") {
|
|
264
|
+
const headers2 = this.generateHeaders();
|
|
265
|
+
return new Response(body, { status: status2, headers: headers2 });
|
|
266
|
+
}
|
|
267
|
+
if (name === "ReadableStream") {
|
|
268
|
+
const headers2 = this.generateHeaders();
|
|
269
|
+
return new Response(body, { status: status2, headers: headers2 });
|
|
270
|
+
}
|
|
271
|
+
if (name === "PassThrough" || name === "Readable") {
|
|
272
|
+
const headers2 = this.generateHeaders();
|
|
273
|
+
return new Response(toWeb(body), { status: status2, headers: headers2 });
|
|
274
|
+
}
|
|
275
|
+
return this.json(body);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
var status = (...args) => new Reply().status(...args);
|
|
279
|
+
var headers = (...args) => new Reply().headers(...args);
|
|
280
|
+
var type = (...args) => new Reply().type(...args);
|
|
281
|
+
var download = (...args) => new Reply().download(...args);
|
|
282
|
+
var cookies = (...args) => new Reply().cookies(...args);
|
|
283
|
+
var send = (...args) => new Reply().send(...args);
|
|
284
|
+
var json = (...args) => new Reply().json(...args);
|
|
285
|
+
var file = (...args) => new Reply().file(...args);
|
|
286
|
+
var redirect = (...args) => new Reply().redirect(...args);
|
|
287
|
+
var view = (...args) => new Reply().view(...args);
|
|
288
|
+
|
|
289
|
+
// src/auth/providers/github.ts
|
|
290
|
+
var oauth = async (code) => {
|
|
291
|
+
const fch = async (url, { body, headers: headers2 = {}, ...rest } = {}) => {
|
|
292
|
+
headers2.accept = "application/json";
|
|
293
|
+
headers2["content-type"] = "application/json";
|
|
294
|
+
const res2 = await fetch(url, { ...rest, body, headers: headers2 });
|
|
295
|
+
if (!res2.ok) throw new Error("Invalid request");
|
|
296
|
+
return res2.json();
|
|
297
|
+
};
|
|
298
|
+
const res = await fch("https://github.com/login/oauth/access_token", {
|
|
299
|
+
method: "post",
|
|
300
|
+
body: JSON.stringify({
|
|
301
|
+
client_id: env.GITHUB_ID,
|
|
302
|
+
client_secret: env.GITHUB_SECRET,
|
|
303
|
+
code
|
|
304
|
+
})
|
|
305
|
+
});
|
|
306
|
+
return (path2) => {
|
|
307
|
+
return fch(`https://api.github.com${path2}`, {
|
|
308
|
+
headers: { Authorization: `Bearer ${res.access_token}` }
|
|
309
|
+
});
|
|
310
|
+
};
|
|
311
|
+
};
|
|
312
|
+
var login = function githubLogin() {
|
|
313
|
+
return redirect(
|
|
314
|
+
`https://github.com/login/oauth/authorize?client_id=${env.GITHUB_ID}&scope=user:email`
|
|
315
|
+
);
|
|
316
|
+
};
|
|
317
|
+
var getUserProfile = async (code) => {
|
|
318
|
+
const api = await oauth(code);
|
|
319
|
+
const [profile, emails] = await Promise.all([
|
|
320
|
+
api("/user"),
|
|
321
|
+
api("/user/emails")
|
|
322
|
+
]);
|
|
323
|
+
const email = emails.sort((a) => a.primary ? -1 : 1)[0]?.email;
|
|
324
|
+
return { ...profile, email };
|
|
325
|
+
};
|
|
326
|
+
var callback = async (ctx) => {
|
|
327
|
+
const { strategy, cleanUser, store, session: session2, redirect: redirect2 } = ctx.options.auth;
|
|
328
|
+
const profile = await getUserProfile(ctx.url.query.code);
|
|
329
|
+
const auth2 = {
|
|
330
|
+
id: createId(),
|
|
331
|
+
strategy,
|
|
332
|
+
provider: "github",
|
|
333
|
+
user: createId(profile.email),
|
|
334
|
+
email: profile.email,
|
|
335
|
+
time: (/* @__PURE__ */ new Date()).toISOString().replace(/\.[0-9]*/, "")
|
|
336
|
+
};
|
|
337
|
+
const user = cleanUser({
|
|
338
|
+
id: profile.id,
|
|
339
|
+
name: profile.name,
|
|
340
|
+
email: profile.email,
|
|
341
|
+
picture: profile.avatar_url,
|
|
342
|
+
location: profile.location,
|
|
343
|
+
created: profile.created_at
|
|
344
|
+
});
|
|
345
|
+
await store.set(auth2.user, user);
|
|
346
|
+
await session2.set(auth2.id, auth2, { expires: "1w" });
|
|
347
|
+
if (auth2.strategy.includes("token")) {
|
|
348
|
+
return status(201).json({ ...user, token: auth2.id });
|
|
349
|
+
}
|
|
350
|
+
if (auth2.strategy.includes("cookie")) {
|
|
351
|
+
return status(302).cookies({ authentication: auth2.id }).redirect(redirect2);
|
|
352
|
+
}
|
|
353
|
+
if (auth2.strategy.includes("jwt")) {
|
|
354
|
+
throw new Error("JWT auth not supported yet");
|
|
355
|
+
}
|
|
356
|
+
if (auth2.strategy.includes("key")) {
|
|
357
|
+
throw new Error("Key auth not supported yet");
|
|
358
|
+
}
|
|
359
|
+
throw new Error("Unknown auth type");
|
|
360
|
+
};
|
|
361
|
+
var github_default = { login, callback };
|
|
362
|
+
|
|
363
|
+
// src/auth/providers/index.ts
|
|
364
|
+
var providers_default = { email: email_default, github: github_default };
|
|
365
|
+
|
|
366
|
+
// src/auth/parseAuthOptions.ts
|
|
367
|
+
function parseAuthOptions(auth2, all) {
|
|
368
|
+
if (!auth2) return null;
|
|
369
|
+
if (typeof auth2 === "string") {
|
|
370
|
+
const [strategy, provider] = auth2.split(":");
|
|
371
|
+
auth2 = { strategy, provider };
|
|
372
|
+
}
|
|
373
|
+
if (typeof auth2.provider === "string") {
|
|
374
|
+
auth2.provider = auth2.provider.split("|").filter(Boolean);
|
|
375
|
+
}
|
|
376
|
+
if (!auth2.strategy) {
|
|
377
|
+
throw new Error("Auth options needs a strategy");
|
|
378
|
+
}
|
|
379
|
+
if (!auth2.strategy.length) {
|
|
380
|
+
throw new Error("Auth options needs a strategy");
|
|
381
|
+
}
|
|
382
|
+
if (!auth2.provider || !auth2.provider.length) {
|
|
383
|
+
throw new Error("Auth options needs a provider");
|
|
384
|
+
}
|
|
385
|
+
const providerNotFound = auth2.provider.find((p) => !providers_default[p]);
|
|
386
|
+
if (providerNotFound) {
|
|
387
|
+
throw new Error(
|
|
388
|
+
`Provider "${providerNotFound}" not found, available ones are "${Object.keys(providers_default).join('", "')}"`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
if (!auth2.session && all.store) {
|
|
392
|
+
auth2.session = all.store.prefix("auth:");
|
|
393
|
+
}
|
|
394
|
+
if (!auth2.store && all.store) {
|
|
395
|
+
auth2.store = all.store.prefix("user:");
|
|
396
|
+
}
|
|
397
|
+
if (!auth2.cleanUser) {
|
|
398
|
+
auth2.cleanUser = (fullUser) => {
|
|
399
|
+
const { password: _password, ...user } = fullUser;
|
|
400
|
+
return user;
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
if (!auth2.redirect) {
|
|
404
|
+
auth2.redirect = "/user";
|
|
405
|
+
}
|
|
406
|
+
return auth2;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// src/helpers/bucket.ts
|
|
410
|
+
import * as fs from "fs";
|
|
411
|
+
import * as fsp from "fs/promises";
|
|
412
|
+
import * as path from "path";
|
|
413
|
+
function thinLocalBucket(root) {
|
|
414
|
+
const absolute = (name) => {
|
|
415
|
+
if (!name) throw new Error("File name is required");
|
|
416
|
+
return path.resolve(path.join(root, name));
|
|
417
|
+
};
|
|
418
|
+
return {
|
|
419
|
+
location: path.resolve(root),
|
|
420
|
+
read: async (name) => {
|
|
421
|
+
const fullPath = absolute(name);
|
|
422
|
+
const stats = await fsp.stat(fullPath).catch(() => null);
|
|
423
|
+
if (!stats || !stats.isFile()) return null;
|
|
424
|
+
const nodeStream = fs.createReadStream(fullPath);
|
|
425
|
+
return new ReadableStream({
|
|
426
|
+
start(controller) {
|
|
427
|
+
nodeStream.on("data", (chunk) => controller.enqueue(chunk));
|
|
428
|
+
nodeStream.on("end", () => controller.close());
|
|
429
|
+
nodeStream.on("error", (err) => controller.error(err));
|
|
430
|
+
},
|
|
431
|
+
cancel() {
|
|
432
|
+
nodeStream.destroy();
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
},
|
|
436
|
+
write: (name, value, type2) => {
|
|
437
|
+
const fullPath = absolute(name);
|
|
438
|
+
if (value) {
|
|
439
|
+
return fsp.writeFile(fullPath, value, type2).then(() => fullPath);
|
|
440
|
+
}
|
|
441
|
+
return fs.createWriteStream(fullPath);
|
|
442
|
+
},
|
|
443
|
+
delete: async (name) => {
|
|
444
|
+
const fullPath = absolute(name);
|
|
445
|
+
try {
|
|
446
|
+
await fsp.unlink(fullPath);
|
|
447
|
+
return true;
|
|
448
|
+
} catch {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function thinBunBucket(s3) {
|
|
455
|
+
return {
|
|
456
|
+
read: async (name) => {
|
|
457
|
+
const file2 = s3.file(name);
|
|
458
|
+
if (!await file2.exists()) return null;
|
|
459
|
+
return await file2.stream();
|
|
460
|
+
},
|
|
461
|
+
write: async (name, value) => {
|
|
462
|
+
const file2 = s3.file(name);
|
|
463
|
+
if (value) {
|
|
464
|
+
await file2.write(value);
|
|
465
|
+
return name;
|
|
466
|
+
}
|
|
467
|
+
return s3.presign(name, { expiresIn: 3600, acl: "public-read-write" });
|
|
468
|
+
},
|
|
469
|
+
delete: async (name) => {
|
|
470
|
+
const file2 = s3.file(name);
|
|
471
|
+
if (!await file2.exists()) return null;
|
|
472
|
+
return await file2.delete();
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
function bucket_default(root) {
|
|
477
|
+
if (!root) return null;
|
|
478
|
+
if (typeof root === "string") {
|
|
479
|
+
return thinLocalBucket(root);
|
|
480
|
+
}
|
|
481
|
+
if (root.file && root.write) {
|
|
482
|
+
return thinBunBucket(root);
|
|
483
|
+
}
|
|
484
|
+
return root;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// src/helpers/createId.ts
|
|
488
|
+
var alphabet = "useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict";
|
|
489
|
+
var random = (bytes) => crypto.getRandomValues(new Uint8Array(bytes));
|
|
490
|
+
var cyrb53 = (str, seed = 0) => {
|
|
491
|
+
if (typeof str !== "string") str = String(str);
|
|
492
|
+
let h1 = 3735928559 ^ seed;
|
|
493
|
+
let h2 = 1103547991 ^ seed;
|
|
494
|
+
for (let i = 0, ch; i < str.length; i++) {
|
|
495
|
+
ch = str.charCodeAt(i);
|
|
496
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
497
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
498
|
+
}
|
|
499
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
|
|
500
|
+
h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
501
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
|
|
502
|
+
h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
503
|
+
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
|
504
|
+
};
|
|
505
|
+
var hash2 = (str, size) => {
|
|
506
|
+
let chars = "";
|
|
507
|
+
let num = cyrb53(str);
|
|
508
|
+
for (let i = 0; i < size; i++) {
|
|
509
|
+
if (num < alphabet.length) num = cyrb53(str, i);
|
|
510
|
+
chars += alphabet[num % alphabet.length];
|
|
511
|
+
num = Math.floor(num / alphabet.length);
|
|
512
|
+
}
|
|
513
|
+
return chars;
|
|
514
|
+
};
|
|
515
|
+
var randomId = (size = 16) => {
|
|
516
|
+
let id = "";
|
|
517
|
+
const bytes = random(size);
|
|
518
|
+
while (size--) {
|
|
519
|
+
id += alphabet[bytes[size] & 61];
|
|
520
|
+
}
|
|
521
|
+
return id;
|
|
522
|
+
};
|
|
523
|
+
function createId(source, size = 16) {
|
|
524
|
+
if (source) return hash2(source, size);
|
|
525
|
+
return randomId(size);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// src/helpers/color.ts
|
|
529
|
+
var map = {
|
|
530
|
+
reset: 0,
|
|
531
|
+
bright: 1,
|
|
532
|
+
dim: 2,
|
|
533
|
+
under: 4,
|
|
534
|
+
blink: 5,
|
|
535
|
+
reverse: 7,
|
|
536
|
+
black: 30,
|
|
537
|
+
red: 31,
|
|
538
|
+
green: 32,
|
|
539
|
+
yellow: 33,
|
|
540
|
+
blue: 34,
|
|
541
|
+
magenta: 35,
|
|
542
|
+
cyan: 36,
|
|
543
|
+
white: 37,
|
|
544
|
+
bgblack: 40,
|
|
545
|
+
bgred: 41,
|
|
546
|
+
bggreen: 42,
|
|
547
|
+
bgyellow: 43,
|
|
548
|
+
bgblue: 44,
|
|
549
|
+
bgmagenta: 45,
|
|
550
|
+
bgcyan: 46,
|
|
551
|
+
bgwhite: 47
|
|
552
|
+
};
|
|
553
|
+
var replace = (k) => {
|
|
554
|
+
if (process.env.NO_COLOR) return "";
|
|
555
|
+
if (!(k in map)) throw new Error(`"{${k}}" is not a valid color`);
|
|
556
|
+
return `\x1B[${map[k]}m`;
|
|
557
|
+
};
|
|
558
|
+
function color(str, ...vals) {
|
|
559
|
+
if (typeof str === "string") {
|
|
560
|
+
return str.replace(/\{(\w+)\}/g, (_m, k) => replace(k)).replace(/\{\/\w*\}/g, () => replace("reset"));
|
|
561
|
+
}
|
|
562
|
+
return color(str[0] + vals.map((v, i) => v + str[i + 1]).join(""));
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// src/helpers/debugInfo.ts
|
|
566
|
+
var isDebug = process.argv.includes("--debug");
|
|
567
|
+
function debugInfo(options, name, cb, icon = "") {
|
|
568
|
+
if (!isDebug) return;
|
|
569
|
+
if (!options[name]) {
|
|
570
|
+
console.log(color`options:${String(name)}\t→ {dim}[not set]{/}`);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
console.log(
|
|
574
|
+
color`options:${String(name)}\t→ ${icon ? `${icon} ` : ""}${cb(options[name])}`
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// src/helpers/config.ts
|
|
579
|
+
var env2 = globalThis.env;
|
|
580
|
+
function config(options = {}) {
|
|
581
|
+
const settings = {
|
|
582
|
+
port: options.port || env2.PORT || 3e3,
|
|
583
|
+
secret: options.secret || env2.SECRET || `unsafe-${createId()}`
|
|
584
|
+
};
|
|
585
|
+
options.cors = options.cors || env2.CORS || null;
|
|
586
|
+
if (options.cors) {
|
|
587
|
+
const cors2 = {
|
|
588
|
+
origin: "",
|
|
589
|
+
methods: "GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS",
|
|
590
|
+
headers: "*"
|
|
591
|
+
};
|
|
592
|
+
if (options.cors === true) {
|
|
593
|
+
cors2.origin = true;
|
|
594
|
+
} else if (typeof options.cors === "string") {
|
|
595
|
+
cors2.origin = options.cors;
|
|
596
|
+
} else if (Array.isArray(options.cors)) {
|
|
597
|
+
cors2.origin = options.cors.join(",");
|
|
598
|
+
} else if (typeof options.cors === "object") {
|
|
599
|
+
if (!options.cors.origin) {
|
|
600
|
+
cors2.origin = "*";
|
|
601
|
+
} else if (typeof options.cors.origin === "string") {
|
|
602
|
+
cors2.origin = options.cors.origin;
|
|
603
|
+
} else if (Array.isArray(options.cors.origin)) {
|
|
604
|
+
cors2.origin = options.cors.origin.join(",");
|
|
605
|
+
}
|
|
606
|
+
if ("methods" in options.cors) {
|
|
607
|
+
cors2.methods = Array.isArray(options.cors.methods) ? options.cors.methods.join(",") : options.cors.methods;
|
|
608
|
+
}
|
|
609
|
+
if ("headers" in options.cors) {
|
|
610
|
+
cors2.headers = Array.isArray(options.cors.headers) ? options.cors.headers.join(",") : options.cors.headers;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
if (typeof cors2.origin === "string") {
|
|
614
|
+
cors2.origin = cors2.origin.toLowerCase();
|
|
615
|
+
}
|
|
616
|
+
settings.cors = cors2;
|
|
617
|
+
}
|
|
618
|
+
settings.views = options.views ? bucket_default(options.views) : null;
|
|
619
|
+
debugInfo(options, "views", (views) => views?.location || "true", "\u{1F4C2}");
|
|
620
|
+
settings.public = options.public ? bucket_default(options.public) : null;
|
|
621
|
+
debugInfo(options, "public", (pub) => pub?.location || "true", "\u{1F4C2}");
|
|
622
|
+
settings.uploads = options.uploads ? bucket_default(options.uploads) : null;
|
|
623
|
+
debugInfo(options, "uploads", (ups) => ups?.location || "true", "\u{1F4C2}");
|
|
624
|
+
settings.store = options.store ?? null;
|
|
625
|
+
debugInfo(options, "store", (store) => store?.name || "working", "\u{1F4E6}");
|
|
626
|
+
settings.cookies = options.cookies ?? null;
|
|
627
|
+
debugInfo(options, "cookies", (cookies2) => cookies2?.name || "working", "\u{1F36A}");
|
|
628
|
+
if (options.store && !options.session) {
|
|
629
|
+
settings.session = { store: options.store.prefix("session:") };
|
|
630
|
+
}
|
|
631
|
+
debugInfo(
|
|
632
|
+
options,
|
|
633
|
+
"session",
|
|
634
|
+
(session2) => session2?.store?.name || "working",
|
|
635
|
+
"\u{1F510}"
|
|
636
|
+
);
|
|
637
|
+
if (options.auth || env2.AUTH) {
|
|
638
|
+
settings.auth = parseAuthOptions(options.auth || env2.AUTH || null, options);
|
|
639
|
+
}
|
|
640
|
+
if (options.openapi) {
|
|
641
|
+
if (options.openapi === true) {
|
|
642
|
+
settings.openapi = {};
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return settings;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// src/helpers/cors.ts
|
|
649
|
+
var localhost = /^https?:\/\/localhost(:\d+)?$/;
|
|
650
|
+
function cors(config2, origin = "") {
|
|
651
|
+
origin = origin.toLowerCase();
|
|
652
|
+
if (config2 === true) return origin || null;
|
|
653
|
+
if (config2 === "*") return "*";
|
|
654
|
+
if (!origin) return null;
|
|
655
|
+
if (localhost.test(origin)) return origin;
|
|
656
|
+
const arr = Array.isArray(config2) ? config2 : typeof config2 === "string" ? config2.split(/\s*,\s*/g) : [];
|
|
657
|
+
if (arr.includes(origin)) return origin;
|
|
658
|
+
console.warn(`CORS: Origin "${origin}" not allowed. Allowed "${config2}"`);
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// src/helpers/createCookies.ts
|
|
663
|
+
function createCookies(cookies2) {
|
|
664
|
+
if (!cookies2 || !Object.keys(cookies2).length) return [];
|
|
665
|
+
return Object.entries(cookies2).map(([key, val]) => {
|
|
666
|
+
if (!val) {
|
|
667
|
+
val = { value: "", expires: (/* @__PURE__ */ new Date(0)).toUTCString() };
|
|
668
|
+
}
|
|
669
|
+
if (typeof val === "string") {
|
|
670
|
+
val = { value: val };
|
|
671
|
+
}
|
|
672
|
+
const { value, path: path2, expires } = val;
|
|
673
|
+
const pathPart = `;Path=${path2 || "/"}`;
|
|
674
|
+
const expiresPart = expires ? `;Expires=${expires}` : "";
|
|
675
|
+
return `${key}=${value || ""}${pathPart}${expiresPart}`;
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// src/helpers/createWebsocket.ts
|
|
680
|
+
function createWebsocket(sockets, handlers) {
|
|
681
|
+
return {
|
|
682
|
+
message: async (socket, body) => {
|
|
683
|
+
handlers.socket?.filter((s) => s[1] === "message")?.map((s) => s[2]({ socket, sockets, body }));
|
|
684
|
+
},
|
|
685
|
+
open: (socket) => {
|
|
686
|
+
sockets.push(socket);
|
|
687
|
+
handlers.socket?.filter((s) => s[1] === "open")?.map((s) => s[2]({ socket, sockets, body: void 0 }));
|
|
688
|
+
},
|
|
689
|
+
close: (socket) => {
|
|
690
|
+
sockets.splice(sockets.indexOf(socket), 1);
|
|
691
|
+
handlers.socket?.filter((s) => s[1] === "close")?.map((s) => s[2]({ socket, sockets, body: void 0 }));
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// src/helpers/define.ts
|
|
697
|
+
function define(obj, key, cb) {
|
|
698
|
+
Object.defineProperty(obj, key, {
|
|
699
|
+
configurable: true,
|
|
700
|
+
get() {
|
|
701
|
+
const value = cb(obj);
|
|
702
|
+
Object.defineProperty(obj, key, {
|
|
703
|
+
configurable: true,
|
|
704
|
+
writable: true,
|
|
705
|
+
value
|
|
706
|
+
});
|
|
707
|
+
return obj[key];
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// src/helpers/getMachine.ts
|
|
713
|
+
function getProvider() {
|
|
714
|
+
if (typeof globalThis.Netlify !== "undefined") return "netlify";
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
function getRuntime() {
|
|
718
|
+
if (typeof Bun !== "undefined") return "bun";
|
|
719
|
+
if (typeof globalThis.Deno !== "undefined") return "deno";
|
|
720
|
+
if (globalThis.process?.versions?.node) return "node";
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
function getProduction() {
|
|
724
|
+
if (typeof globalThis.Netlify !== "undefined")
|
|
725
|
+
return globalThis.Netlify.env.get("NETLIFY_DEV") !== "true";
|
|
726
|
+
return process.env.NODE_ENV === "production";
|
|
727
|
+
}
|
|
728
|
+
function getMachine() {
|
|
729
|
+
return {
|
|
730
|
+
provider: getProvider(),
|
|
731
|
+
runtime: getRuntime(),
|
|
732
|
+
production: getProduction()
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// src/parseResponse.ts
|
|
737
|
+
async function parseResponse(out, ctx) {
|
|
738
|
+
if (!out && typeof out !== "string") return;
|
|
739
|
+
if (typeof out === "function") {
|
|
740
|
+
out = await out(ctx);
|
|
741
|
+
}
|
|
742
|
+
if (out instanceof Blob) {
|
|
743
|
+
out = new Response(out, { headers: { "Content-Type": out.type } });
|
|
744
|
+
}
|
|
745
|
+
if (out instanceof ReadableStream) {
|
|
746
|
+
out = new Response(out);
|
|
747
|
+
}
|
|
748
|
+
if (typeof out === "number") {
|
|
749
|
+
out = new Response(void 0, { status: out });
|
|
750
|
+
}
|
|
751
|
+
if (typeof out === "string") {
|
|
752
|
+
const type2 = /^\s*</.test(out) ? "text/html" : "text/plain";
|
|
753
|
+
out = new Response(out, { headers: { "content-type": type2 } });
|
|
754
|
+
}
|
|
755
|
+
if (out?.constructor === Object || Array.isArray(out)) {
|
|
756
|
+
out = json(out);
|
|
757
|
+
}
|
|
758
|
+
if (out[Symbol.iterator]) {
|
|
759
|
+
out = new Response(iteratorToReadable(out));
|
|
760
|
+
}
|
|
761
|
+
if (out[Symbol.asyncIterator] && !(out instanceof Response)) {
|
|
762
|
+
out = new Response(iteratorAsyncToReadable(out));
|
|
763
|
+
}
|
|
764
|
+
if (out instanceof Response && out.url && out.body) {
|
|
765
|
+
out = new Response(out.body, {
|
|
766
|
+
status: out.status,
|
|
767
|
+
headers: out.headers
|
|
768
|
+
});
|
|
769
|
+
if (/^(br|gzip)$/.test(out.headers.get("content-encoding") || "")) {
|
|
770
|
+
out.headers.delete("content-encoding");
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
if (!(out instanceof Response)) {
|
|
774
|
+
throw new Error(`Invalid response type ${out}`);
|
|
775
|
+
}
|
|
776
|
+
if (ctx.options.cors) {
|
|
777
|
+
const origin = cors(ctx.options.cors.origin, ctx.headers.origin);
|
|
778
|
+
if (origin) {
|
|
779
|
+
out.headers.set("Access-Control-Allow-Origin", origin);
|
|
780
|
+
out.headers.set("Access-Control-Allow-Methods", ctx.options.cors.methods);
|
|
781
|
+
out.headers.set("Access-Control-Allow-Headers", ctx.options.cors.headers);
|
|
782
|
+
if (ctx.options.cors.credentials) {
|
|
783
|
+
out.headers.set("Access-Control-Allow-Credentials", "true");
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (ctx.time?.times?.length > 1) {
|
|
788
|
+
out.headers.set("Server-Timing", ctx.time.headers());
|
|
789
|
+
}
|
|
790
|
+
if (Object.keys(ctx.session || {}).length) {
|
|
791
|
+
if (!ctx.options.session?.store) {
|
|
792
|
+
throw ServerError_default.NO_STORE({});
|
|
793
|
+
}
|
|
794
|
+
if (!ctx.cookies.session) {
|
|
795
|
+
ctx.res.cookies.session = createId();
|
|
796
|
+
}
|
|
797
|
+
const id = ctx.cookies.session;
|
|
798
|
+
ctx.options.session.store.set(id, ctx.session);
|
|
799
|
+
}
|
|
800
|
+
if (ctx.options.cookies) {
|
|
801
|
+
if (Object.keys(ctx.res.cookies).length) {
|
|
802
|
+
for (const cookie of ctx.res.cookies) {
|
|
803
|
+
ctx.res.headers.append("set-cookie", cookie);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
if (ctx?.res?.headers) {
|
|
808
|
+
for (const key in ctx.res.headers) {
|
|
809
|
+
out.headers[key] = ctx.res.headers[key];
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return out;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// src/pathPattern.ts
|
|
816
|
+
function pathPattern(pattern, path2) {
|
|
817
|
+
if (pattern === "*" && path2 === "/") return {};
|
|
818
|
+
pattern = `/${pattern.replace(/^\//, "")}`;
|
|
819
|
+
pattern = pattern.replace(/\/$/, "") || "/";
|
|
820
|
+
path2 = path2.replace(/\/$/, "") || "/";
|
|
821
|
+
if (pattern === path2) return {};
|
|
822
|
+
const params = {};
|
|
823
|
+
const pathParts = path2.split("/").slice(1);
|
|
824
|
+
const pattParts = pattern.split("/").slice(1);
|
|
825
|
+
let allSame = true;
|
|
826
|
+
for (let i = 0; i < Math.max(pathParts.length, pattParts.length); i++) {
|
|
827
|
+
const patt = pattParts[i] || "";
|
|
828
|
+
const part = pathParts[i] || "";
|
|
829
|
+
const last = pattParts[pattParts.length - 1];
|
|
830
|
+
const key = patt.replace(/^:/, "").replace(/\?$/, "").replace(/\(\w*\)/, "");
|
|
831
|
+
if (patt === part) continue;
|
|
832
|
+
if (patt.endsWith("?") && !part) continue;
|
|
833
|
+
if (patt.startsWith(":")) {
|
|
834
|
+
params[key] = part;
|
|
835
|
+
if (/\(\w*\)/.test(patt)) {
|
|
836
|
+
if (patt.includes("(number)")) {
|
|
837
|
+
const value = Number(part);
|
|
838
|
+
params[key] = Number.isNaN(value) ? void 0 : value;
|
|
839
|
+
}
|
|
840
|
+
if (patt.includes("(date)")) {
|
|
841
|
+
const value = new Date(part);
|
|
842
|
+
params[key] = Number.isNaN(value.getTime()) ? void 0 : value;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
if (!patt && last === "*" && part || patt === "*" && part) {
|
|
848
|
+
params["*"] = params["*"] || [];
|
|
849
|
+
params["*"].push(part);
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
allSame = false;
|
|
853
|
+
}
|
|
854
|
+
if (allSame) return params;
|
|
855
|
+
return null;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// src/helpers/StatusError.ts
|
|
859
|
+
var StatusError = class extends Error {
|
|
860
|
+
status;
|
|
861
|
+
constructor(msg, status2 = 500) {
|
|
862
|
+
super(msg);
|
|
863
|
+
this.status = status2;
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
// src/helpers/validate.ts
|
|
868
|
+
function validate(ctx, schema) {
|
|
869
|
+
if (!schema || typeof schema !== "object") return;
|
|
870
|
+
let base;
|
|
871
|
+
try {
|
|
872
|
+
if (typeof schema?.body === "function") {
|
|
873
|
+
base = "body";
|
|
874
|
+
schema.body(ctx.body || {});
|
|
875
|
+
}
|
|
876
|
+
if (typeof schema?.body?.parse === "function") {
|
|
877
|
+
base = "body";
|
|
878
|
+
schema.body.parse(ctx.body || {});
|
|
879
|
+
}
|
|
880
|
+
if (typeof schema?.query === "function") {
|
|
881
|
+
base = "query";
|
|
882
|
+
schema.query(ctx.url.query || {});
|
|
883
|
+
}
|
|
884
|
+
if (typeof schema?.query?.parse === "function") {
|
|
885
|
+
base = "query";
|
|
886
|
+
schema.query.parse(ctx.url.query || {});
|
|
887
|
+
}
|
|
888
|
+
} catch (error) {
|
|
889
|
+
if (error.name === "ZodError" || error.constructor.name === "ZodError") {
|
|
890
|
+
const message = error.issues.map(
|
|
891
|
+
({ path: path2, message: message2 }) => `[${base}.${path2.join(".")}]: ${message2}`
|
|
892
|
+
).sort().join("\n");
|
|
893
|
+
throw new StatusError(message, 422);
|
|
894
|
+
}
|
|
895
|
+
throw error;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// src/helpers/handleRequest.ts
|
|
900
|
+
async function handleRequest(handlers, ctx) {
|
|
901
|
+
try {
|
|
902
|
+
for (const [method, matcher, ...cbs] of handlers[ctx.method]) {
|
|
903
|
+
const match = pathPattern(matcher, ctx.url.pathname || "/");
|
|
904
|
+
if (!match) continue;
|
|
905
|
+
define(ctx.url, "params", () => match);
|
|
906
|
+
for (const cb of cbs) {
|
|
907
|
+
if (typeof cb === "function") {
|
|
908
|
+
const res = await cb(ctx);
|
|
909
|
+
const out = await parseResponse(res, ctx);
|
|
910
|
+
if (out) return out;
|
|
911
|
+
} else {
|
|
912
|
+
validate(ctx, cb);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
if (method !== "*") break;
|
|
916
|
+
}
|
|
917
|
+
if (ctx.platform.provider === "netlify") return;
|
|
918
|
+
return new Response("Not Found", { status: 404 });
|
|
919
|
+
} catch (error) {
|
|
920
|
+
return new Response(error.message || "", { status: error.status || 500 });
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// src/helpers/hash.ts
|
|
925
|
+
import * as crypto2 from "crypto";
|
|
926
|
+
import { getRandomValues } from "crypto";
|
|
927
|
+
import { promisify } from "util";
|
|
928
|
+
async function hash(password) {
|
|
929
|
+
if ("argon2" in crypto2) {
|
|
930
|
+
const argon23 = promisify(crypto2.argon2);
|
|
931
|
+
const buf = await argon23("argon2id", {
|
|
932
|
+
message: Buffer.from(password),
|
|
933
|
+
nonce: getRandomValues(new Uint8Array(16)),
|
|
934
|
+
parallelism: 4,
|
|
935
|
+
tagLength: 64,
|
|
936
|
+
memory: 65536,
|
|
937
|
+
passes: 3
|
|
938
|
+
});
|
|
939
|
+
return buf.toString("base64");
|
|
940
|
+
}
|
|
941
|
+
return await Bun.password.hash(password, {
|
|
942
|
+
algorithm: "argon2id",
|
|
943
|
+
memoryCost: 65536,
|
|
944
|
+
timeCost: 3
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// src/helpers/iterate.ts
|
|
949
|
+
async function iterate(stream, cb) {
|
|
950
|
+
const reader = stream.getReader();
|
|
951
|
+
while (true) {
|
|
952
|
+
const chunk = await reader.read();
|
|
953
|
+
if (chunk.done || !chunk.value) return;
|
|
954
|
+
cb(chunk.value);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// src/helpers/iteratorAsyncToReadable.ts
|
|
959
|
+
function iteratorAsyncToReadable(asyncGenerator) {
|
|
960
|
+
return new ReadableStream({
|
|
961
|
+
async pull(controller) {
|
|
962
|
+
try {
|
|
963
|
+
const { value, done } = await asyncGenerator.next();
|
|
964
|
+
if (done) {
|
|
965
|
+
controller.close();
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
controller.enqueue(new TextEncoder().encode(value));
|
|
969
|
+
} catch (err) {
|
|
970
|
+
console.error("Stream error:", err);
|
|
971
|
+
controller.error(err);
|
|
972
|
+
}
|
|
973
|
+
},
|
|
974
|
+
cancel() {
|
|
975
|
+
console.log("Stream cancelled");
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// src/helpers/iteratorToReadable.ts
|
|
981
|
+
function iteratorToReadable(generator) {
|
|
982
|
+
return new ReadableStream({
|
|
983
|
+
async start(controller) {
|
|
984
|
+
for await (const chunk of generator) {
|
|
985
|
+
controller.enqueue(chunk);
|
|
986
|
+
}
|
|
987
|
+
controller.close();
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// src/helpers/parseBody.ts
|
|
993
|
+
function getBoundary(header) {
|
|
994
|
+
if (!header) return null;
|
|
995
|
+
if (header.includes("multipart/form-data") && !header.includes("boundary=")) {
|
|
996
|
+
console.error("Do not set the `Content-Type` manually for FormData");
|
|
997
|
+
}
|
|
998
|
+
const items = header.split(";");
|
|
999
|
+
for (const item of items) {
|
|
1000
|
+
const trimmedItem = item.trim();
|
|
1001
|
+
if (trimmedItem.startsWith("boundary=")) {
|
|
1002
|
+
return trimmedItem.split("=")[1].trim();
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
function getMatching(string, regex) {
|
|
1008
|
+
const matches = string.match(regex);
|
|
1009
|
+
return matches?.[1] ?? "";
|
|
1010
|
+
}
|
|
1011
|
+
var saveFile = async (name, value, bucket) => {
|
|
1012
|
+
const ext = name.split(".").pop();
|
|
1013
|
+
const id = `${createId()}.${ext}`;
|
|
1014
|
+
await bucket.write(id, value);
|
|
1015
|
+
return id;
|
|
1016
|
+
};
|
|
1017
|
+
function splitBuffer(buffer, delimiter) {
|
|
1018
|
+
const result = [];
|
|
1019
|
+
let start = 0;
|
|
1020
|
+
let index = buffer.indexOf(delimiter);
|
|
1021
|
+
while (index !== -1) {
|
|
1022
|
+
result.push(buffer.slice(start, index));
|
|
1023
|
+
start = index + delimiter.length;
|
|
1024
|
+
index = buffer.indexOf(delimiter, start);
|
|
1025
|
+
}
|
|
1026
|
+
result.push(buffer.slice(start));
|
|
1027
|
+
return result;
|
|
1028
|
+
}
|
|
1029
|
+
var BREAK_BUFFER = Buffer.from("\r\n\r\n");
|
|
1030
|
+
function isProbablyText(buffer) {
|
|
1031
|
+
for (let i = 0; i < Math.min(buffer.length, 512); i++) {
|
|
1032
|
+
const byte = buffer[i];
|
|
1033
|
+
if (byte === 0) return false;
|
|
1034
|
+
if (byte < 7 || byte > 13 && byte < 32) return false;
|
|
1035
|
+
}
|
|
1036
|
+
return true;
|
|
1037
|
+
}
|
|
1038
|
+
async function parseBody(raw, contentType, bucket) {
|
|
1039
|
+
const contentTypeStr = Array.isArray(contentType) ? contentType[0] : contentType;
|
|
1040
|
+
if (!raw) return {};
|
|
1041
|
+
if (!contentTypeStr || /^text\//.test(contentTypeStr)) {
|
|
1042
|
+
return raw.toString("utf-8");
|
|
1043
|
+
}
|
|
1044
|
+
if (/application\/json/.test(contentTypeStr)) {
|
|
1045
|
+
return JSON.parse(raw.toString("utf-8"));
|
|
1046
|
+
}
|
|
1047
|
+
const boundary = getBoundary(contentTypeStr);
|
|
1048
|
+
if (!boundary) return null;
|
|
1049
|
+
const body = {};
|
|
1050
|
+
const boundaryBuffer = Buffer.from(`--${boundary}`);
|
|
1051
|
+
const parts = splitBuffer(raw, boundaryBuffer);
|
|
1052
|
+
for (const part of parts) {
|
|
1053
|
+
if (part.length === 0 || part.equals(Buffer.from("--\r\n"))) continue;
|
|
1054
|
+
const idx = part.indexOf(BREAK_BUFFER);
|
|
1055
|
+
if (idx === -1) continue;
|
|
1056
|
+
const headerStr = part.slice(0, idx).toString("utf-8");
|
|
1057
|
+
const contentBuf = part.slice(idx + BREAK_BUFFER.length, part.length - 2);
|
|
1058
|
+
const name = getMatching(headerStr, /name="(.+?)"/).trim().replace(/\[\]$/, "");
|
|
1059
|
+
if (!name) continue;
|
|
1060
|
+
const filename = getMatching(headerStr, /filename="(.+?)"/).trim();
|
|
1061
|
+
if (filename) {
|
|
1062
|
+
if (!bucket) throw new Error("Bucket is required to save files");
|
|
1063
|
+
body[name] = await saveFile(filename, contentBuf, bucket);
|
|
1064
|
+
} else {
|
|
1065
|
+
const value = isProbablyText(contentBuf) ? contentBuf.toString("utf-8").trim() : contentBuf;
|
|
1066
|
+
if (body[name]) {
|
|
1067
|
+
if (!Array.isArray(body[name])) body[name] = [body[name]];
|
|
1068
|
+
body[name].push(value);
|
|
1069
|
+
} else {
|
|
1070
|
+
body[name] = value;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
return body;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// src/helpers/parseCookies.ts
|
|
1078
|
+
function parseCookies(cookies2) {
|
|
1079
|
+
if (!cookies2) return {};
|
|
1080
|
+
const cookieStr = Array.isArray(cookies2) ? cookies2[0] : cookies2;
|
|
1081
|
+
if (!cookieStr) return {};
|
|
1082
|
+
return Object.fromEntries(
|
|
1083
|
+
cookieStr.split(/;\s*/).map((part) => {
|
|
1084
|
+
const [key, ...rest] = part.split("=");
|
|
1085
|
+
return [key, decodeURIComponent(rest.join("="))];
|
|
1086
|
+
})
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// src/helpers/parseHeaders.ts
|
|
1091
|
+
var parseHeaders_default = (raw) => {
|
|
1092
|
+
const headers2 = {};
|
|
1093
|
+
raw.forEach((value, originalKey) => {
|
|
1094
|
+
const key = originalKey.toLowerCase();
|
|
1095
|
+
if (headers2[key]) {
|
|
1096
|
+
if (!Array.isArray(headers2[key])) {
|
|
1097
|
+
headers2[key] = [headers2[key]];
|
|
1098
|
+
}
|
|
1099
|
+
headers2[key].push(value);
|
|
1100
|
+
} else {
|
|
1101
|
+
headers2[key] = value;
|
|
1102
|
+
}
|
|
1103
|
+
});
|
|
1104
|
+
return headers2;
|
|
1105
|
+
};
|
|
1106
|
+
|
|
1107
|
+
// src/helpers/toWeb.ts
|
|
1108
|
+
function toWeb(nodeStream) {
|
|
1109
|
+
if (typeof ReadableStream === "undefined") {
|
|
1110
|
+
throw new Error("Environment not supported, please report this as a bug");
|
|
1111
|
+
}
|
|
1112
|
+
return new ReadableStream({
|
|
1113
|
+
start(controller) {
|
|
1114
|
+
nodeStream.on("data", (chunk) => controller.enqueue(chunk));
|
|
1115
|
+
nodeStream.on("end", () => controller.close());
|
|
1116
|
+
nodeStream.on("error", (err) => controller.error(err));
|
|
1117
|
+
},
|
|
1118
|
+
cancel() {
|
|
1119
|
+
nodeStream.destroy();
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// src/helpers/types.ts
|
|
1125
|
+
var types = {
|
|
1126
|
+
aac: "audio/aac",
|
|
1127
|
+
abw: "application/x-abiword",
|
|
1128
|
+
arc: "application/x-freearc",
|
|
1129
|
+
avif: "image/avif",
|
|
1130
|
+
avi: "video/x-msvideo",
|
|
1131
|
+
azw: "application/vnd.amazon.ebook",
|
|
1132
|
+
bin: "application/octet-stream",
|
|
1133
|
+
bmp: "image/bmp",
|
|
1134
|
+
bz: "application/x-bzip",
|
|
1135
|
+
bz2: "application/x-bzip2",
|
|
1136
|
+
cda: "application/x-cdf",
|
|
1137
|
+
csh: "application/x-csh",
|
|
1138
|
+
css: "text/css",
|
|
1139
|
+
csv: "text/csv",
|
|
1140
|
+
doc: "application/msword",
|
|
1141
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
1142
|
+
eot: "application/vnd.ms-fontobject",
|
|
1143
|
+
epub: "application/epub+zip",
|
|
1144
|
+
gz: "application/gzip",
|
|
1145
|
+
gif: "image/gif",
|
|
1146
|
+
htm: "text/html",
|
|
1147
|
+
html: "text/html",
|
|
1148
|
+
ico: "image/vnd.microsoft.icon",
|
|
1149
|
+
ics: "text/calendar",
|
|
1150
|
+
jar: "application/java-archive",
|
|
1151
|
+
jpeg: "image/jpeg",
|
|
1152
|
+
jpg: "image/jpeg",
|
|
1153
|
+
js: "text/javascript",
|
|
1154
|
+
json: "application/json",
|
|
1155
|
+
jsonld: "application/ld+json",
|
|
1156
|
+
md: "text/markdown",
|
|
1157
|
+
mid: "audio/midi",
|
|
1158
|
+
midi: "audio/midi",
|
|
1159
|
+
mjs: "text/javascript",
|
|
1160
|
+
mp3: "audio/mpeg",
|
|
1161
|
+
mp4: "video/mp4",
|
|
1162
|
+
mpeg: "video/mpeg",
|
|
1163
|
+
mpkg: "application/vnd.apple.installer+xml",
|
|
1164
|
+
odp: "application/vnd.oasis.opendocument.presentation",
|
|
1165
|
+
ods: "application/vnd.oasis.opendocument.spreadsheet",
|
|
1166
|
+
odt: "application/vnd.oasis.opendocument.text",
|
|
1167
|
+
oga: "audio/ogg",
|
|
1168
|
+
ogv: "video/ogg",
|
|
1169
|
+
ogx: "application/ogg",
|
|
1170
|
+
opus: "audio/opus",
|
|
1171
|
+
otf: "font/otf",
|
|
1172
|
+
png: "image/png",
|
|
1173
|
+
pdf: "application/pdf",
|
|
1174
|
+
php: "application/x-httpd-php",
|
|
1175
|
+
ppt: "application/vnd.ms-powerpoint",
|
|
1176
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
1177
|
+
rar: "application/vnd.rar",
|
|
1178
|
+
rtf: "application/rtf",
|
|
1179
|
+
sh: "application/x-sh",
|
|
1180
|
+
svg: "image/svg+xml",
|
|
1181
|
+
tar: "application/x-tar",
|
|
1182
|
+
text: "text/plain",
|
|
1183
|
+
tif: "image/tiff",
|
|
1184
|
+
tiff: "image/tiff",
|
|
1185
|
+
ts: "video/mp2t",
|
|
1186
|
+
ttf: "font/ttf",
|
|
1187
|
+
txt: "text/plain",
|
|
1188
|
+
vsd: "application/vnd.visio",
|
|
1189
|
+
wav: "audio/wav",
|
|
1190
|
+
weba: "audio/webm",
|
|
1191
|
+
webm: "video/webm",
|
|
1192
|
+
webp: "image/webp",
|
|
1193
|
+
woff: "font/woff",
|
|
1194
|
+
woff2: "font/woff2",
|
|
1195
|
+
xhtml: "application/xhtml+xml",
|
|
1196
|
+
xls: "application/vnd.ms-excel",
|
|
1197
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
1198
|
+
xml: "application/xml",
|
|
1199
|
+
xul: "application/vnd.mozilla.xul+xml",
|
|
1200
|
+
zip: "application/zip",
|
|
1201
|
+
"3gp": "video/3gpp",
|
|
1202
|
+
"3g2": "video/3gpp2",
|
|
1203
|
+
"7z": "application/x-7z-compressed"
|
|
1204
|
+
};
|
|
1205
|
+
var types_default = types;
|
|
1206
|
+
|
|
1207
|
+
// src/helpers/verify.ts
|
|
1208
|
+
import * as crypto3 from "crypto";
|
|
1209
|
+
function timingSafeEqual(a, b) {
|
|
1210
|
+
const len = Math.max(a.length, b.length);
|
|
1211
|
+
let mismatch = a.length ^ b.length;
|
|
1212
|
+
for (let i = 0; i < len; i++) {
|
|
1213
|
+
const ca = a.charCodeAt(i) || 0;
|
|
1214
|
+
const cb = b.charCodeAt(i) || 0;
|
|
1215
|
+
mismatch |= ca ^ cb;
|
|
1216
|
+
}
|
|
1217
|
+
return mismatch === 0;
|
|
1218
|
+
}
|
|
1219
|
+
async function verify(password, hash3) {
|
|
1220
|
+
if ("Bun" in globalThis) {
|
|
1221
|
+
return Bun.password.verify(password, hash3, "argon2id");
|
|
1222
|
+
}
|
|
1223
|
+
const match = /^\$argon2(id|i|d)\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([^$]+)\$([^$]+)$/.exec(
|
|
1224
|
+
hash3
|
|
1225
|
+
);
|
|
1226
|
+
if (!match) throw new Error("Invalid Argon2 hash format");
|
|
1227
|
+
const [, variant, , memory, passes, parallelism, saltB64, hashB64] = match;
|
|
1228
|
+
const nonce = Buffer.from(saltB64, "base64");
|
|
1229
|
+
const expected = Buffer.from(hashB64, "base64");
|
|
1230
|
+
return new Promise((resolve2, reject) => {
|
|
1231
|
+
crypto3.argon2(
|
|
1232
|
+
`argon2${variant}`,
|
|
1233
|
+
{
|
|
1234
|
+
message: password,
|
|
1235
|
+
nonce,
|
|
1236
|
+
memory: parseInt(memory, 10),
|
|
1237
|
+
passes: parseInt(passes, 10),
|
|
1238
|
+
parallelism: parseInt(parallelism, 10),
|
|
1239
|
+
tagLength: expected.length
|
|
1240
|
+
},
|
|
1241
|
+
(err, derivedKey) => {
|
|
1242
|
+
if (err) return reject(err);
|
|
1243
|
+
if (derivedKey.length === expected.length && timingSafeEqual(derivedKey, expected)) {
|
|
1244
|
+
resolve2(true);
|
|
1245
|
+
} else {
|
|
1246
|
+
resolve2(false);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
);
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// src/auth/findSessionId.ts
|
|
1254
|
+
var validateToken = (authorization) => {
|
|
1255
|
+
const [type2, id] = authorization.trim().split(" ");
|
|
1256
|
+
if (type2.toLowerCase() !== "bearer") {
|
|
1257
|
+
throw ServerError_default.AUTH_INVALID_HEADER({ type: type2 });
|
|
1258
|
+
}
|
|
1259
|
+
if (id.length !== 16) {
|
|
1260
|
+
throw ServerError_default.AUTH_INVALID_TOKEN();
|
|
1261
|
+
}
|
|
1262
|
+
return id;
|
|
1263
|
+
};
|
|
1264
|
+
var validateCookie = (authorization) => {
|
|
1265
|
+
if (authorization.length !== 16) {
|
|
1266
|
+
throw ServerError_default.AUTH_INVALID_COOKIE();
|
|
1267
|
+
}
|
|
1268
|
+
return authorization;
|
|
1269
|
+
};
|
|
1270
|
+
function findSessionId(ctx) {
|
|
1271
|
+
const strategy = ctx.options.auth.strategy;
|
|
1272
|
+
if (!strategy) throw new Error(`Invalid strategy "${strategy}"`);
|
|
1273
|
+
if (strategy.includes("token")) {
|
|
1274
|
+
if (!ctx.headers.authorization) return;
|
|
1275
|
+
return validateToken(ctx.headers.authorization);
|
|
1276
|
+
}
|
|
1277
|
+
if (strategy.includes("cookie")) {
|
|
1278
|
+
if (!ctx.cookies.authentication) return;
|
|
1279
|
+
return validateCookie(ctx.cookies.authentication);
|
|
1280
|
+
}
|
|
1281
|
+
throw new Error(`Invalid auth type "${strategy}"`);
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
// src/auth/getUser.ts
|
|
1285
|
+
async function getUser(ctx) {
|
|
1286
|
+
if (!ctx.options.auth) return;
|
|
1287
|
+
const options = ctx.options.auth;
|
|
1288
|
+
const sessionId = findSessionId(ctx);
|
|
1289
|
+
if (!sessionId) return;
|
|
1290
|
+
const auth2 = await options.session.get(sessionId);
|
|
1291
|
+
if (!auth2) return;
|
|
1292
|
+
if (options.strategy !== auth2.strategy) {
|
|
1293
|
+
throw ServerError_default.AUTH_INVALID_STRATEGY({
|
|
1294
|
+
strategy: auth2.strategy || "undefined",
|
|
1295
|
+
valid: options.strategy
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
if (!options.provider.includes(auth2.provider)) {
|
|
1299
|
+
throw ServerError_default.AUTH_INVALID_PROVIDER({
|
|
1300
|
+
provider: auth2.provider,
|
|
1301
|
+
valid: options.provider
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
const user = await ctx.options.auth.store.get(auth2.user);
|
|
1305
|
+
if (!user) throw ServerError_default.AUTH_NO_USER();
|
|
1306
|
+
user.strategy = auth2.strategy;
|
|
1307
|
+
user.provider = auth2.provider;
|
|
1308
|
+
return ctx.options.auth.cleanUser(user);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// src/auth/logout.ts
|
|
1312
|
+
async function logout(ctx) {
|
|
1313
|
+
const session2 = findSessionId(ctx);
|
|
1314
|
+
const { strategy } = ctx.user;
|
|
1315
|
+
await ctx.options.auth.session.del(session2);
|
|
1316
|
+
if (!strategy) throw new Error(`Invalid strategy "${strategy}"`);
|
|
1317
|
+
if (strategy.includes("token")) {
|
|
1318
|
+
return { token: null };
|
|
1319
|
+
}
|
|
1320
|
+
if (strategy.includes("cookie")) {
|
|
1321
|
+
return cookies({ authorization: null }).redirect("/");
|
|
1322
|
+
}
|
|
1323
|
+
if (strategy.includes("jwt")) {
|
|
1324
|
+
throw new Error("JWT auth not supported yet");
|
|
1325
|
+
}
|
|
1326
|
+
if (strategy.includes("key")) {
|
|
1327
|
+
throw new Error("Key auth not supported yet");
|
|
1328
|
+
}
|
|
1329
|
+
throw new Error("Unknown auth type");
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
// src/auth/index.ts
|
|
1333
|
+
function auth(app) {
|
|
1334
|
+
app.use(async function middle(ctx) {
|
|
1335
|
+
ctx.user = await getUser(ctx);
|
|
1336
|
+
});
|
|
1337
|
+
if (app.settings.auth.provider.includes("github")) {
|
|
1338
|
+
if (!env.GITHUB_ID) throw new Error("GITHUB_ID not defined");
|
|
1339
|
+
if (!env.GITHUB_SECRET) throw new Error("GITHUB_SECRET not defined");
|
|
1340
|
+
app.get("/auth/logout", logout);
|
|
1341
|
+
app.get("/auth/login/github", providers_default.github.login);
|
|
1342
|
+
app.get("/auth/callback/github", providers_default.github.callback);
|
|
1343
|
+
}
|
|
1344
|
+
if (app.settings.auth.provider.includes("email")) {
|
|
1345
|
+
app.post("/auth/logout", logout);
|
|
1346
|
+
app.post("/auth/register/email", providers_default.email.register);
|
|
1347
|
+
app.post("/auth/login/email", providers_default.email.login);
|
|
1348
|
+
app.put("/auth/password/email", providers_default.email.password);
|
|
1349
|
+
app.put("/auth/reset/email", providers_default.email.reset);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// src/middle/assets.ts
|
|
1354
|
+
async function assets(ctx) {
|
|
1355
|
+
if (!ctx.options.public) return;
|
|
1356
|
+
if (ctx.method !== "get") return;
|
|
1357
|
+
if (ctx.url.pathname === "/") return;
|
|
1358
|
+
try {
|
|
1359
|
+
const asset = await ctx.options.public.read(ctx.url.pathname);
|
|
1360
|
+
if (!asset) return;
|
|
1361
|
+
return type(ctx.url.pathname.split(".").pop()).send(asset);
|
|
1362
|
+
} catch {
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// src/middle/openapi.ts
|
|
1367
|
+
import * as fsp2 from "fs/promises";
|
|
1368
|
+
var entities = {
|
|
1369
|
+
"&": "&",
|
|
1370
|
+
"<": "<",
|
|
1371
|
+
">": ">",
|
|
1372
|
+
'"': """
|
|
1373
|
+
};
|
|
1374
|
+
var encode = (str = "") => {
|
|
1375
|
+
if (typeof str === "number") str = String(str);
|
|
1376
|
+
if (typeof str !== "string") return "";
|
|
1377
|
+
return str.replace(/[&<>"]/g, (tag) => entities[tag]);
|
|
1378
|
+
};
|
|
1379
|
+
var getConfig = (routes) => {
|
|
1380
|
+
const config2 = routes.find(
|
|
1381
|
+
(r) => typeof r !== "string" && typeof r !== "function" && typeof r === "object"
|
|
1382
|
+
);
|
|
1383
|
+
if (!config2) return {};
|
|
1384
|
+
if (config2.tags) {
|
|
1385
|
+
if (typeof config2.tags === "string") {
|
|
1386
|
+
config2.tags = config2.tags.split(/\s*,\s*/g);
|
|
1387
|
+
}
|
|
1388
|
+
if (!Array.isArray(config2.tags)) {
|
|
1389
|
+
throw new Error("invalid tags");
|
|
1390
|
+
}
|
|
1391
|
+
config2.tags = config2.tags.map((t) => t.trim());
|
|
1392
|
+
}
|
|
1393
|
+
return config2;
|
|
1394
|
+
};
|
|
1395
|
+
function zodToSchema(schema) {
|
|
1396
|
+
const type2 = schema?.def?.type || "string";
|
|
1397
|
+
if (type2 === "object") {
|
|
1398
|
+
const shape = schema.def.shape;
|
|
1399
|
+
const properties = {};
|
|
1400
|
+
const req = [];
|
|
1401
|
+
for (const key in shape) {
|
|
1402
|
+
const field = shape[key];
|
|
1403
|
+
properties[key] = zodToSchema(field);
|
|
1404
|
+
if (!field.isOptional() && !field.isNullable()) {
|
|
1405
|
+
req.push(key);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
const required = req.length ? req : void 0;
|
|
1409
|
+
return { type: type2, properties, required };
|
|
1410
|
+
}
|
|
1411
|
+
if (type2 === "array") {
|
|
1412
|
+
return { type: type2, items: zodToSchema(schema.def.element) };
|
|
1413
|
+
}
|
|
1414
|
+
return { type: type2 };
|
|
1415
|
+
}
|
|
1416
|
+
var pkgProm = fsp2.readFile("package.json", "utf-8").then((data) => JSON.parse(data)).catch(() => ({}));
|
|
1417
|
+
var getTag = (name, fn) => {
|
|
1418
|
+
const found = fn.toString().split("\n").filter((l) => /\s+\/\/\s/.test(l)).map((l) => l.trim().replace("// ", "")).find((l) => l.startsWith(name));
|
|
1419
|
+
if (!found) return "";
|
|
1420
|
+
return encode(found.replace(name, "").trim());
|
|
1421
|
+
};
|
|
1422
|
+
var getDescription = (fn) => getTag("@description", fn) || "";
|
|
1423
|
+
var getReturn = (fn) => getTag("@returns", fn) || "OK";
|
|
1424
|
+
var generateOpenApiPaths = (handlers) => {
|
|
1425
|
+
const paths = {};
|
|
1426
|
+
for (const [method, routes] of Object.entries(handlers)) {
|
|
1427
|
+
for (const route of routes) {
|
|
1428
|
+
const [_, path2, fn, meta] = [
|
|
1429
|
+
route[0],
|
|
1430
|
+
route[1],
|
|
1431
|
+
route.find((p) => typeof p === "function"),
|
|
1432
|
+
route.find((p) => typeof p === "object")
|
|
1433
|
+
];
|
|
1434
|
+
const config2 = getConfig(route);
|
|
1435
|
+
if (typeof path2 !== "string" || path2 === "*" || path2 === "/docs" || !fn) {
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
const normalizedPath = path2.replace(/\(\w+\)/gi, "").replace(/:([a-zA-Z0-9_]+)/g, "{$1}");
|
|
1439
|
+
if (!paths[normalizedPath]) {
|
|
1440
|
+
paths[normalizedPath] = {};
|
|
1441
|
+
}
|
|
1442
|
+
const getTitle = (fn2) => {
|
|
1443
|
+
if (!fn2.name) return null;
|
|
1444
|
+
const wrongNames = ["default"];
|
|
1445
|
+
if (wrongNames.includes(fn2.name)) return null;
|
|
1446
|
+
if (fn2.name.length <= 3) return null;
|
|
1447
|
+
if (fn2.name.includes("_")) return fn2.name.replace(/_/g, " ");
|
|
1448
|
+
const name = fn2.name.split(/(?=[A-Z])/).join(" ").toLowerCase();
|
|
1449
|
+
return name[0].toUpperCase() + name.slice(1);
|
|
1450
|
+
};
|
|
1451
|
+
let requestBody;
|
|
1452
|
+
if (meta?.body) {
|
|
1453
|
+
const schema = zodToSchema(meta.body);
|
|
1454
|
+
requestBody = { content: { "application/json": { schema } } };
|
|
1455
|
+
}
|
|
1456
|
+
let responses;
|
|
1457
|
+
if (meta?.response) {
|
|
1458
|
+
const schema = zodToSchema(meta.response);
|
|
1459
|
+
const description = getReturn(fn);
|
|
1460
|
+
responses = {
|
|
1461
|
+
200: { description, content: { "application/json": { schema } } }
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
const parameters = [];
|
|
1465
|
+
const matched = Array.from(path2.matchAll(/:[\w()]+/gi));
|
|
1466
|
+
matched.forEach((match) => {
|
|
1467
|
+
const [name, type2 = "string"] = match[0].slice(1).replace(/\)/, "").split("(");
|
|
1468
|
+
parameters.push({
|
|
1469
|
+
name,
|
|
1470
|
+
in: "path",
|
|
1471
|
+
required: true,
|
|
1472
|
+
schema: { type: type2 }
|
|
1473
|
+
});
|
|
1474
|
+
});
|
|
1475
|
+
if (meta?.query) {
|
|
1476
|
+
Object.entries(meta.query).map(([key, value]) => ({
|
|
1477
|
+
name: key,
|
|
1478
|
+
in: "query",
|
|
1479
|
+
required: false,
|
|
1480
|
+
schema: { type: typeof value },
|
|
1481
|
+
example: value
|
|
1482
|
+
}));
|
|
1483
|
+
}
|
|
1484
|
+
paths[normalizedPath][method] = {
|
|
1485
|
+
tags: config2.tags,
|
|
1486
|
+
summary: config2.title || getTag("@title", fn) || `${method.toUpperCase()} ${normalizedPath}`,
|
|
1487
|
+
description: getTitle(fn) || getDescription(fn),
|
|
1488
|
+
requestBody,
|
|
1489
|
+
parameters,
|
|
1490
|
+
responses
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
return paths;
|
|
1495
|
+
};
|
|
1496
|
+
var openapi_default = async (ctx) => {
|
|
1497
|
+
const pkg = await pkgProm;
|
|
1498
|
+
const domain = pkg.homepage || ctx.url.origin;
|
|
1499
|
+
const openApi = {
|
|
1500
|
+
openapi: "3.0.0",
|
|
1501
|
+
info: {
|
|
1502
|
+
title: pkg.name || "API Documentation",
|
|
1503
|
+
version: pkg.version || "1.0.0",
|
|
1504
|
+
description: pkg.description || ""
|
|
1505
|
+
},
|
|
1506
|
+
servers: domain ? [{ url: domain }] : [],
|
|
1507
|
+
paths: generateOpenApiPaths(ctx.app.handlers)
|
|
1508
|
+
};
|
|
1509
|
+
const configuration = ctx.options.openapi?.scalar || {};
|
|
1510
|
+
return `
|
|
1511
|
+
<!doctype html>
|
|
1512
|
+
<html>
|
|
1513
|
+
<head>
|
|
1514
|
+
<title>API Reference</title>
|
|
1515
|
+
<meta charset="utf-8" />
|
|
1516
|
+
<meta
|
|
1517
|
+
name="viewport"
|
|
1518
|
+
content="width=device-width, initial-scale=1" />
|
|
1519
|
+
<style>.open-api-client-button {display: none!important;}</style>
|
|
1520
|
+
</head>
|
|
1521
|
+
<body>
|
|
1522
|
+
<script id="api-reference" type="application/json" data-configuration="${encode(JSON.stringify(configuration))}">${JSON.stringify(openApi, null, 2)}</script>
|
|
1523
|
+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
|
1524
|
+
</body>
|
|
1525
|
+
</html> `;
|
|
1526
|
+
};
|
|
1527
|
+
|
|
1528
|
+
// src/middle/timer.ts
|
|
1529
|
+
var createTime = () => {
|
|
1530
|
+
const times = [["init", performance.now()]];
|
|
1531
|
+
const time = (name) => times.push([name, performance.now()]);
|
|
1532
|
+
time.times = times;
|
|
1533
|
+
time.headers = () => {
|
|
1534
|
+
const r = (t) => Math.round(t);
|
|
1535
|
+
const times2 = time.times;
|
|
1536
|
+
const timing = times2.slice(1).map(([name, time2], i) => `${name};dur=${r(time2 - times2[i][1])}`).join(", ");
|
|
1537
|
+
return timing;
|
|
1538
|
+
};
|
|
1539
|
+
return time;
|
|
1540
|
+
};
|
|
1541
|
+
function timer(ctx) {
|
|
1542
|
+
ctx.time = createTime();
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// src/auth/NoSession.ts
|
|
1546
|
+
var NoSession = class {
|
|
1547
|
+
};
|
|
1548
|
+
function createNoSession() {
|
|
1549
|
+
return new Proxy(NoSession, {
|
|
1550
|
+
get(target, key) {
|
|
1551
|
+
if (target[key]) return target[key];
|
|
1552
|
+
if (key === "then") return target[key];
|
|
1553
|
+
if (typeof key === "symbol") return target[key];
|
|
1554
|
+
throw ServerError_default.NO_STORE_READ({ key: String(key) });
|
|
1555
|
+
},
|
|
1556
|
+
set(target, key, value) {
|
|
1557
|
+
if (target[key] || key === "then" || typeof key === "symbol") {
|
|
1558
|
+
target[key] = value;
|
|
1559
|
+
return true;
|
|
1560
|
+
}
|
|
1561
|
+
throw ServerError_default.NO_STORE_WRITE({ key: String(key) });
|
|
1562
|
+
}
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
// src/auth/session.ts
|
|
1567
|
+
async function session(ctx) {
|
|
1568
|
+
const store = ctx.options.session?.store;
|
|
1569
|
+
if (!store) {
|
|
1570
|
+
ctx.session = createNoSession();
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
if (ctx.cookies.session) {
|
|
1574
|
+
const session2 = await store.get(ctx.cookies.session);
|
|
1575
|
+
ctx.session = session2;
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
// src/context/node.ts
|
|
1581
|
+
import { TLSSocket } from "tls";
|
|
1582
|
+
|
|
1583
|
+
// src/context/createEvents.ts
|
|
1584
|
+
function createEvents() {
|
|
1585
|
+
const events = {};
|
|
1586
|
+
events.on = (name, callback2) => {
|
|
1587
|
+
events[name] = events[name] || [];
|
|
1588
|
+
events[name].push(callback2);
|
|
1589
|
+
};
|
|
1590
|
+
events.trigger = (name, data) => {
|
|
1591
|
+
if (!events[name]) return;
|
|
1592
|
+
for (const cb of events[name]) {
|
|
1593
|
+
cb(data);
|
|
1594
|
+
}
|
|
1595
|
+
};
|
|
1596
|
+
return events;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
// src/context/isValidMethod.ts
|
|
1600
|
+
var methods = [
|
|
1601
|
+
"get",
|
|
1602
|
+
"post",
|
|
1603
|
+
"put",
|
|
1604
|
+
"patch",
|
|
1605
|
+
"delete",
|
|
1606
|
+
"head",
|
|
1607
|
+
"options",
|
|
1608
|
+
"socket"
|
|
1609
|
+
];
|
|
1610
|
+
function isValidMethod(method) {
|
|
1611
|
+
return methods.includes(method);
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
// src/context/node.ts
|
|
1615
|
+
var chunkArray = (arr) => arr.length > 2 ? [[arr[0], arr[1]], ...chunkArray(arr.slice(2))] : [arr];
|
|
1616
|
+
async function createNode(req, app) {
|
|
1617
|
+
const init = performance.now();
|
|
1618
|
+
const method = req.method?.toLowerCase() || "get";
|
|
1619
|
+
if (!isValidMethod(method)) {
|
|
1620
|
+
throw new Error(`Invalid HTTP method: ${method}`);
|
|
1621
|
+
}
|
|
1622
|
+
const chunks = chunkArray(req.rawHeaders);
|
|
1623
|
+
const headers2 = parseHeaders_default(new Headers(chunks));
|
|
1624
|
+
const cookies2 = parseCookies(headers2.cookie);
|
|
1625
|
+
const scheme = req.socket instanceof TLSSocket ? "https" : "http";
|
|
1626
|
+
const host = headers2.host || `localhost:${app.settings.port}`;
|
|
1627
|
+
const path2 = (req.url || "/").replace(/\/$/, "") || "/";
|
|
1628
|
+
const baseUrl = `${scheme}://${host}`;
|
|
1629
|
+
const url = new URL(path2, baseUrl);
|
|
1630
|
+
define(
|
|
1631
|
+
url,
|
|
1632
|
+
"query",
|
|
1633
|
+
(url2) => Object.fromEntries(url2.searchParams.entries())
|
|
1634
|
+
);
|
|
1635
|
+
const rawBody = await new Promise((resolve2, reject) => {
|
|
1636
|
+
const body2 = [];
|
|
1637
|
+
req.on("data", (chunk) => body2.push(chunk)).on("end", () => resolve2(Buffer.concat(body2))).on("error", reject);
|
|
1638
|
+
});
|
|
1639
|
+
const body = rawBody ? await parseBody(rawBody, headers2["content-type"], app.settings.uploads) : void 0;
|
|
1640
|
+
const events = createEvents();
|
|
1641
|
+
return {
|
|
1642
|
+
options: app.settings,
|
|
1643
|
+
platform: app.platform,
|
|
1644
|
+
url,
|
|
1645
|
+
method,
|
|
1646
|
+
body,
|
|
1647
|
+
headers: headers2,
|
|
1648
|
+
cookies: cookies2,
|
|
1649
|
+
init,
|
|
1650
|
+
events,
|
|
1651
|
+
app
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
// src/context/winter.ts
|
|
1656
|
+
async function createWinter(req, app) {
|
|
1657
|
+
const init = performance.now();
|
|
1658
|
+
const method = req.method.toLowerCase();
|
|
1659
|
+
if (!isValidMethod(method)) {
|
|
1660
|
+
throw new Error(`Invalid HTTP method: ${method}`);
|
|
1661
|
+
}
|
|
1662
|
+
const headers2 = parseHeaders_default(req.headers);
|
|
1663
|
+
const cookies2 = parseCookies(headers2.cookie);
|
|
1664
|
+
const baseUrl = req.url.replace(/\/$/, "") || "/";
|
|
1665
|
+
const url = new URL(baseUrl);
|
|
1666
|
+
define(
|
|
1667
|
+
url,
|
|
1668
|
+
"query",
|
|
1669
|
+
(url2) => Object.fromEntries(url2.searchParams.entries())
|
|
1670
|
+
);
|
|
1671
|
+
const rawBody = Buffer.from(await req.arrayBuffer());
|
|
1672
|
+
const body = req.body ? await parseBody(rawBody, headers2["content-type"], app.settings.uploads) : void 0;
|
|
1673
|
+
const events = createEvents();
|
|
1674
|
+
return {
|
|
1675
|
+
options: app.settings,
|
|
1676
|
+
platform: app.platform,
|
|
1677
|
+
url,
|
|
1678
|
+
method,
|
|
1679
|
+
body,
|
|
1680
|
+
headers: headers2,
|
|
1681
|
+
cookies: cookies2,
|
|
1682
|
+
init,
|
|
1683
|
+
events,
|
|
1684
|
+
app
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
// src/context/handlers.ts
|
|
1689
|
+
var Winter = async (app, request, env3) => {
|
|
1690
|
+
if (env3?.upgrade(request)) return;
|
|
1691
|
+
Object.assign(globalThis.env, env3);
|
|
1692
|
+
const ctx = await createWinter(request, app);
|
|
1693
|
+
const res = await handleRequest(app.handlers, ctx);
|
|
1694
|
+
ctx.events.trigger("finish", { ...ctx, res, end: performance.now() });
|
|
1695
|
+
return res;
|
|
1696
|
+
};
|
|
1697
|
+
var Node = async (app) => {
|
|
1698
|
+
const http = await import("http");
|
|
1699
|
+
http.createServer(async (request, response) => {
|
|
1700
|
+
const ctx = await createNode(request, app);
|
|
1701
|
+
if ("error" in ctx) throw ctx.error;
|
|
1702
|
+
const out = await handleRequest(app.handlers, ctx);
|
|
1703
|
+
response.writeHead(out.status || 200, parseHeaders_default(out.headers));
|
|
1704
|
+
if (out.body instanceof ReadableStream) {
|
|
1705
|
+
await iterate(out.body, (chunk) => response.write(chunk));
|
|
1706
|
+
} else {
|
|
1707
|
+
response.write(out.body || "");
|
|
1708
|
+
}
|
|
1709
|
+
response.end();
|
|
1710
|
+
}).listen(app.settings.port);
|
|
1711
|
+
};
|
|
1712
|
+
var Netlify = async (app, request, context) => {
|
|
1713
|
+
request.context = context;
|
|
1714
|
+
if (typeof Netlify === "undefined") {
|
|
1715
|
+
throw new Error("Netlify doesn't exist");
|
|
1716
|
+
}
|
|
1717
|
+
const ctx = await createWinter(request, app);
|
|
1718
|
+
const res = await handleRequest(app.handlers, ctx);
|
|
1719
|
+
ctx.events.trigger("finish", { ...ctx, res, end: performance.now() });
|
|
1720
|
+
return res;
|
|
1721
|
+
};
|
|
1722
|
+
|
|
1723
|
+
// src/router.ts
|
|
1724
|
+
function isMiddleware(x) {
|
|
1725
|
+
return typeof x === "function";
|
|
1726
|
+
}
|
|
1727
|
+
var Router = class _Router {
|
|
1728
|
+
handlers = {
|
|
1729
|
+
socket: [],
|
|
1730
|
+
get: [],
|
|
1731
|
+
head: [],
|
|
1732
|
+
post: [],
|
|
1733
|
+
put: [],
|
|
1734
|
+
patch: [],
|
|
1735
|
+
delete: [],
|
|
1736
|
+
options: []
|
|
1737
|
+
};
|
|
1738
|
+
// For the router we can just return itself since it's not the final export,
|
|
1739
|
+
// but then on the root it'll return some fancy wrappers
|
|
1740
|
+
self() {
|
|
1741
|
+
return this;
|
|
1742
|
+
}
|
|
1743
|
+
handle(method, path2, ...middleware) {
|
|
1744
|
+
if (typeof path2 !== "string") {
|
|
1745
|
+
middleware.unshift(path2);
|
|
1746
|
+
path2 = "*";
|
|
1747
|
+
}
|
|
1748
|
+
const methods2 = method === "*" ? Object.keys(this.handlers) : [method];
|
|
1749
|
+
for (const m of methods2) {
|
|
1750
|
+
this.handlers[m].push([method, path2, ...middleware]);
|
|
1751
|
+
}
|
|
1752
|
+
return this.self();
|
|
1753
|
+
}
|
|
1754
|
+
socket(pathOrMid, optionsOrMid, ...middleware) {
|
|
1755
|
+
if (typeof pathOrMid === "string" && isMiddleware(optionsOrMid)) {
|
|
1756
|
+
return this.handle("socket", pathOrMid, optionsOrMid, ...middleware);
|
|
1757
|
+
}
|
|
1758
|
+
return this.handle("socket", pathOrMid, ...middleware);
|
|
1759
|
+
}
|
|
1760
|
+
get(pathOrMid, optionsOrMid, ...middleware) {
|
|
1761
|
+
if (typeof pathOrMid === "string" && isMiddleware(optionsOrMid)) {
|
|
1762
|
+
return this.handle("get", pathOrMid, optionsOrMid, ...middleware);
|
|
1763
|
+
}
|
|
1764
|
+
return this.handle("get", pathOrMid, ...middleware);
|
|
1765
|
+
}
|
|
1766
|
+
head(pathOrMid, optionsOrMid, ...middleware) {
|
|
1767
|
+
if (typeof pathOrMid === "string" && isMiddleware(optionsOrMid)) {
|
|
1768
|
+
return this.handle("head", pathOrMid, optionsOrMid, ...middleware);
|
|
1769
|
+
}
|
|
1770
|
+
return this.handle("head", pathOrMid, ...middleware);
|
|
1771
|
+
}
|
|
1772
|
+
post(pathOrMid, optionsOrMid, ...middleware) {
|
|
1773
|
+
if (typeof pathOrMid === "string" && isMiddleware(optionsOrMid)) {
|
|
1774
|
+
return this.handle("post", pathOrMid, optionsOrMid, ...middleware);
|
|
1775
|
+
}
|
|
1776
|
+
return this.handle("post", pathOrMid, ...middleware);
|
|
1777
|
+
}
|
|
1778
|
+
put(pathOrMid, optionsOrMid, ...middleware) {
|
|
1779
|
+
if (typeof pathOrMid === "string" && isMiddleware(optionsOrMid)) {
|
|
1780
|
+
return this.handle("put", pathOrMid, optionsOrMid, ...middleware);
|
|
1781
|
+
}
|
|
1782
|
+
return this.handle("put", pathOrMid, ...middleware);
|
|
1783
|
+
}
|
|
1784
|
+
patch(pathOrMid, optionsOrMid, ...middleware) {
|
|
1785
|
+
if (typeof pathOrMid === "string" && isMiddleware(optionsOrMid)) {
|
|
1786
|
+
return this.handle("patch", pathOrMid, optionsOrMid, ...middleware);
|
|
1787
|
+
}
|
|
1788
|
+
return this.handle("patch", pathOrMid, ...middleware);
|
|
1789
|
+
}
|
|
1790
|
+
del(pathOrMid, optionsOrMid, ...middleware) {
|
|
1791
|
+
if (typeof pathOrMid === "string" && isMiddleware(optionsOrMid)) {
|
|
1792
|
+
return this.handle("delete", pathOrMid, optionsOrMid, ...middleware);
|
|
1793
|
+
}
|
|
1794
|
+
return this.handle("delete", pathOrMid, ...middleware);
|
|
1795
|
+
}
|
|
1796
|
+
options(pathOrMid, optionsOrMid, ...middleware) {
|
|
1797
|
+
if (typeof pathOrMid === "string" && isMiddleware(optionsOrMid)) {
|
|
1798
|
+
return this.handle("options", pathOrMid, optionsOrMid, ...middleware);
|
|
1799
|
+
}
|
|
1800
|
+
return this.handle("options", pathOrMid, ...middleware);
|
|
1801
|
+
}
|
|
1802
|
+
use(...args) {
|
|
1803
|
+
const path2 = typeof args[0] === "string" ? args.shift() : "*";
|
|
1804
|
+
if (args[0] instanceof _Router) {
|
|
1805
|
+
const basePath = `/${path2.replace(/\*$/, "")}/`.replace(/^\/+/, "/").replace(/\/+$/, "/");
|
|
1806
|
+
const handlers = args[0].handlers;
|
|
1807
|
+
for (const m in handlers) {
|
|
1808
|
+
for (const [method, path3, ...middleware] of handlers[m]) {
|
|
1809
|
+
const fullPath = basePath + path3.replace(/^\//, "");
|
|
1810
|
+
this.handlers[m].push([method, fullPath, ...middleware]);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
return this.self();
|
|
1814
|
+
}
|
|
1815
|
+
return this.handle("*", path2, ...args);
|
|
1816
|
+
}
|
|
1817
|
+
};
|
|
1818
|
+
function router() {
|
|
1819
|
+
return new Router();
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
// src/ServerTest.ts
|
|
1823
|
+
function isSerializable(body) {
|
|
1824
|
+
if (!body) return false;
|
|
1825
|
+
if (typeof body === "string") return false;
|
|
1826
|
+
if (body instanceof ReadableStream) return false;
|
|
1827
|
+
if (body instanceof FormData) return false;
|
|
1828
|
+
if (body instanceof Blob) return false;
|
|
1829
|
+
if (body instanceof ArrayBuffer) return false;
|
|
1830
|
+
if (ArrayBuffer.isView(body)) return false;
|
|
1831
|
+
if (body instanceof URLSearchParams) return false;
|
|
1832
|
+
return true;
|
|
1833
|
+
}
|
|
1834
|
+
function ServerTest(app) {
|
|
1835
|
+
const port = app.settings.port;
|
|
1836
|
+
const fetch2 = async (path2, method, options = {}) => {
|
|
1837
|
+
try {
|
|
1838
|
+
if (!options.headers) options.headers = {};
|
|
1839
|
+
if (isSerializable(options.body)) {
|
|
1840
|
+
options.headers["content-type"] = "application/json";
|
|
1841
|
+
options.body = JSON.stringify(options.body);
|
|
1842
|
+
}
|
|
1843
|
+
const res = await app.fetch(
|
|
1844
|
+
new Request(`http://localhost:${port}${path2}`, {
|
|
1845
|
+
method,
|
|
1846
|
+
...options
|
|
1847
|
+
})
|
|
1848
|
+
);
|
|
1849
|
+
const headers2 = parseHeaders_default(res.headers);
|
|
1850
|
+
let body;
|
|
1851
|
+
if (headers2["content-type"]?.includes("application/json")) {
|
|
1852
|
+
body = await res.json();
|
|
1853
|
+
} else {
|
|
1854
|
+
body = await res.text();
|
|
1855
|
+
}
|
|
1856
|
+
return { status: res.status, headers: headers2, body };
|
|
1857
|
+
} catch (error) {
|
|
1858
|
+
return { status: 500, headers: {}, body: error.message };
|
|
1859
|
+
}
|
|
1860
|
+
};
|
|
1861
|
+
return {
|
|
1862
|
+
get: (path2, options) => fetch2(path2, "get", options),
|
|
1863
|
+
head: (path2, options) => fetch2(path2, "head", options),
|
|
1864
|
+
post: (path2, body, options) => fetch2(path2, "post", { body, ...options }),
|
|
1865
|
+
put: (path2, body, options) => fetch2(path2, "put", { body, ...options }),
|
|
1866
|
+
patch: (path2, body, options) => fetch2(path2, "patch", { body, ...options }),
|
|
1867
|
+
delete: (path2, options) => fetch2(path2, "delete", options),
|
|
1868
|
+
options: (path2, options) => fetch2(path2, "options", options)
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
// src/index.ts
|
|
1873
|
+
var Server = class extends Router {
|
|
1874
|
+
settings;
|
|
1875
|
+
platform;
|
|
1876
|
+
sockets;
|
|
1877
|
+
websocket;
|
|
1878
|
+
// Needed to be explicit for Bun/WinterCG
|
|
1879
|
+
port;
|
|
1880
|
+
constructor(options = {}) {
|
|
1881
|
+
super();
|
|
1882
|
+
this.settings = config(options);
|
|
1883
|
+
this.platform = getMachine();
|
|
1884
|
+
if (this.settings.port) {
|
|
1885
|
+
this.port = this.settings.port;
|
|
1886
|
+
}
|
|
1887
|
+
this.sockets = [];
|
|
1888
|
+
this.websocket = createWebsocket(this.sockets, this.handlers);
|
|
1889
|
+
if (this.platform.runtime === "node") {
|
|
1890
|
+
this.node();
|
|
1891
|
+
}
|
|
1892
|
+
this.use(timer);
|
|
1893
|
+
this.use(assets);
|
|
1894
|
+
this.use(session);
|
|
1895
|
+
if (this.settings.auth) {
|
|
1896
|
+
auth(this);
|
|
1897
|
+
}
|
|
1898
|
+
if (this.settings.openapi) {
|
|
1899
|
+
this.get(this.settings.openapi.path || "/docs", openapi_default);
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
// We need to return a function; some environment expect the default export
|
|
1903
|
+
// to be a function that is called with the request, but we also want to
|
|
1904
|
+
// allow chaining, so we return a function that "extends" the instance
|
|
1905
|
+
self() {
|
|
1906
|
+
const cb = this.callback.bind(this);
|
|
1907
|
+
const proto = Object.getPrototypeOf(this);
|
|
1908
|
+
const keys = Object.keys({ ...this.handlers, ...proto, ...this });
|
|
1909
|
+
for (const key of ["use", "node", "fetch", "callback", "test", ...keys]) {
|
|
1910
|
+
if (typeof this[key] === "function") {
|
|
1911
|
+
cb[key] = this[key].bind(this);
|
|
1912
|
+
} else {
|
|
1913
|
+
cb[key] = this[key];
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
return cb;
|
|
1917
|
+
}
|
|
1918
|
+
// The different handlers for different platforms/runtimes
|
|
1919
|
+
node() {
|
|
1920
|
+
return Node(this);
|
|
1921
|
+
}
|
|
1922
|
+
fetch(request, env3) {
|
|
1923
|
+
return Winter(this, request, env3);
|
|
1924
|
+
}
|
|
1925
|
+
callback(request, context) {
|
|
1926
|
+
return Netlify(this, request, context);
|
|
1927
|
+
}
|
|
1928
|
+
// Helper purely for testing
|
|
1929
|
+
test() {
|
|
1930
|
+
return ServerTest(this);
|
|
1931
|
+
}
|
|
1932
|
+
};
|
|
1933
|
+
function server(options = {}) {
|
|
1934
|
+
return new Server(options).self();
|
|
1935
|
+
}
|
|
1936
|
+
export {
|
|
1937
|
+
Reply,
|
|
1938
|
+
Server,
|
|
1939
|
+
ServerError_default as ServerError,
|
|
1940
|
+
cookies,
|
|
1941
|
+
server as default,
|
|
1942
|
+
download,
|
|
1943
|
+
file,
|
|
1944
|
+
headers,
|
|
1945
|
+
json,
|
|
1946
|
+
redirect,
|
|
1947
|
+
router,
|
|
1948
|
+
send,
|
|
1949
|
+
status,
|
|
1950
|
+
type,
|
|
1951
|
+
view
|
|
1952
|
+
};
|