@server/next 0.25.10 → 0.27.0
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 +2005 -0
- package/package.json +17 -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/define.js +0 -18
- package/src/helpers/getMachine.js +0 -26
- package/src/helpers/handleRequest.js +0 -34
- package/src/helpers/index.js +0 -11
- package/src/helpers/iterate.js +0 -8
- 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 -268
- 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 -111
- 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/src/middle/openapi.js
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import fsp from "node:fs/promises";
|
|
2
|
-
|
|
3
|
-
const entities = { "&": "&", "<": "<", ">": ">", '"': """ };
|
|
4
|
-
const encode = (str = "") => {
|
|
5
|
-
if (typeof str === "number") str = String(str);
|
|
6
|
-
if (typeof str !== "string") return ""; // nullify not-strings
|
|
7
|
-
return str.replace(/[&<>"]/g, (tag) => entities[tag]);
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
const getConfig = (routes) => {
|
|
11
|
-
const config = routes.find(
|
|
12
|
-
(r) =>
|
|
13
|
-
typeof r !== "string" && typeof r !== "function" && typeof r === "object",
|
|
14
|
-
);
|
|
15
|
-
if (!config) return {};
|
|
16
|
-
if (config.tags) {
|
|
17
|
-
if (typeof config.tags === "string") {
|
|
18
|
-
config.tags = config.tags.split(/\s*\,\s*/g);
|
|
19
|
-
}
|
|
20
|
-
if (!Array.isArray(config.tags)) {
|
|
21
|
-
throw new Error("invalid tags", config.tags);
|
|
22
|
-
}
|
|
23
|
-
config.tags = config.tags.map((t) => t.trim());
|
|
24
|
-
}
|
|
25
|
-
return config;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const pkgProm = fsp
|
|
29
|
-
.readFile("package.json", "utf-8")
|
|
30
|
-
.then((data) => JSON.parse(data))
|
|
31
|
-
.catch(() => ({}));
|
|
32
|
-
|
|
33
|
-
const getTag = (name, fn) => {
|
|
34
|
-
const found = fn
|
|
35
|
-
.toString()
|
|
36
|
-
.split("\n")
|
|
37
|
-
.filter((l) => /\s+\/\/\s/.test(l))
|
|
38
|
-
.map((l) => l.trim().replace("// ", ""))
|
|
39
|
-
.find((l) => l.startsWith(name));
|
|
40
|
-
if (!found) return "";
|
|
41
|
-
return encode(found.replace(name, "").trim());
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const getDescription = (fn) => getTag("@description", fn) || "";
|
|
45
|
-
const getReturn = (fn) => getTag("@returns", fn) || "200 OK";
|
|
46
|
-
|
|
47
|
-
const generateOpenApiPaths = (handlers) => {
|
|
48
|
-
const paths = {};
|
|
49
|
-
|
|
50
|
-
for (const [method, routes] of Object.entries(handlers)) {
|
|
51
|
-
for (const route of routes) {
|
|
52
|
-
const [_, path, fn, meta] = [
|
|
53
|
-
route[0],
|
|
54
|
-
route[1],
|
|
55
|
-
route.find((p) => typeof p === "function"),
|
|
56
|
-
route.find((p) => typeof p === "object"),
|
|
57
|
-
];
|
|
58
|
-
|
|
59
|
-
const config = getConfig(route);
|
|
60
|
-
|
|
61
|
-
if (typeof path !== "string" || path === "*" || !fn) continue;
|
|
62
|
-
|
|
63
|
-
// Normalize path (convert ":id" to "{id}" for OpenAPI)
|
|
64
|
-
const normalizedPath = path.replace(/:([a-zA-Z0-9_]+)/g, "{$1}");
|
|
65
|
-
|
|
66
|
-
if (!paths[normalizedPath]) {
|
|
67
|
-
paths[normalizedPath] = {};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const getTitle = (fn) => {
|
|
71
|
-
if (!fn.name) return null;
|
|
72
|
-
// Well, we shouldn't really rely on these, e.g. automatic names from export default
|
|
73
|
-
const wrongNames = ["default"];
|
|
74
|
-
if (wrongNames.includes(fn.name)) return null;
|
|
75
|
-
if (fn.name.length <= 3) return null;
|
|
76
|
-
if (fn.name.includes("_")) return fn.name.replaceAll("_", " ");
|
|
77
|
-
const name = fn.name
|
|
78
|
-
.split(/(?=[A-Z])/)
|
|
79
|
-
.join(" ")
|
|
80
|
-
.toLowerCase();
|
|
81
|
-
return name[0].toUpperCase() + name.slice(1);
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
paths[normalizedPath][method] = {
|
|
85
|
-
tags: config.tags,
|
|
86
|
-
summary:
|
|
87
|
-
config.title ||
|
|
88
|
-
getTitle(fn) ||
|
|
89
|
-
getTag("@title", fn) ||
|
|
90
|
-
`${method.toUpperCase()} ${path}`,
|
|
91
|
-
description: getDescription(fn),
|
|
92
|
-
responses: {
|
|
93
|
-
200: {
|
|
94
|
-
description: getReturn(fn),
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
...(meta
|
|
98
|
-
? {
|
|
99
|
-
parameters: Object.entries(meta).map(([key, value]) => ({
|
|
100
|
-
name: key,
|
|
101
|
-
in: "query",
|
|
102
|
-
required: false,
|
|
103
|
-
schema: { type: typeof value },
|
|
104
|
-
example: value,
|
|
105
|
-
})),
|
|
106
|
-
}
|
|
107
|
-
: {}),
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return paths;
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
export default async (ctx) => {
|
|
116
|
-
const pkg = await pkgProm;
|
|
117
|
-
const domain = pkg.homepage || ctx.url.origin;
|
|
118
|
-
const openApi = {
|
|
119
|
-
openapi: "3.0.0",
|
|
120
|
-
info: {
|
|
121
|
-
title: pkg.name || "API Documentation",
|
|
122
|
-
version: pkg.version || "1.0.0",
|
|
123
|
-
description: pkg.description || "",
|
|
124
|
-
},
|
|
125
|
-
servers: domain ? [{ url: domain }] : [],
|
|
126
|
-
paths: generateOpenApiPaths(ctx.app.handlers),
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const configuration = ctx.options.openapi.scalar || {};
|
|
130
|
-
|
|
131
|
-
return `
|
|
132
|
-
<!doctype html>
|
|
133
|
-
<html>
|
|
134
|
-
<head>
|
|
135
|
-
<title>API Reference</title>
|
|
136
|
-
<meta charset="utf-8" />
|
|
137
|
-
<meta
|
|
138
|
-
name="viewport"
|
|
139
|
-
content="width=device-width, initial-scale=1" />
|
|
140
|
-
<style>.open-api-client-button {display: none!important;}</style>
|
|
141
|
-
</head>
|
|
142
|
-
<body>
|
|
143
|
-
<script id="api-reference" type="application/json" data-configuration="${encode(JSON.stringify(configuration))}">${JSON.stringify(openApi, null, 2)}</script>
|
|
144
|
-
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
|
145
|
-
</body>
|
|
146
|
-
</html> `;
|
|
147
|
-
};
|
package/src/middle/timer.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export default function timer(ctx) {
|
|
2
|
-
const times = [["init", performance.now()]];
|
|
3
|
-
ctx.time = (name) => times.push([name, performance.now()]);
|
|
4
|
-
ctx.time.times = times;
|
|
5
|
-
ctx.time.headers = () => {
|
|
6
|
-
const r = (t) => Math.round(t);
|
|
7
|
-
const times = ctx.time.times;
|
|
8
|
-
const timing = times
|
|
9
|
-
.slice(1)
|
|
10
|
-
.map(([name, time], i) => `${name};dur=${r(time - times[i][1])}`)
|
|
11
|
-
.join(", ");
|
|
12
|
-
return timing;
|
|
13
|
-
};
|
|
14
|
-
}
|
package/src/parseResponse.js
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { cors, createId } from "./helpers/index.js";
|
|
2
|
-
import { json } from "./reply.js";
|
|
3
|
-
import ServerError from "./ServerError.js";
|
|
4
|
-
|
|
5
|
-
export default async function parseResponse(out, ctx) {
|
|
6
|
-
// undefined || null || 0 || false || ~""~ -> empty string is still 200
|
|
7
|
-
if (!out && typeof out !== "string") return null;
|
|
8
|
-
|
|
9
|
-
if (typeof out === "function") {
|
|
10
|
-
out = await out(ctx);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
if (out instanceof Blob) {
|
|
14
|
-
out = new Response(out, { headers: { "Content-Type": blob.type } });
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (out instanceof ReadableStream) {
|
|
18
|
-
out = new Response(out);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// A plain number is a status code
|
|
22
|
-
if (typeof out === "number") {
|
|
23
|
-
out = new Response(undefined, { status: out });
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// A plain string will be converted to either html or plain
|
|
27
|
-
if (typeof out === "string") {
|
|
28
|
-
const type = /^\s*\</.test(out) ? "text/html" : "text/plain";
|
|
29
|
-
out = new Response(out, { headers: { "content-type": type } });
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// https://stackoverflow.com/a/69745650/938236
|
|
33
|
-
if (out?.constructor === Object || Array.isArray(out)) {
|
|
34
|
-
out = json(out);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// The output from fetch(), create a copy of it into a new response
|
|
38
|
-
if (out instanceof Response && out.url && out.body) {
|
|
39
|
-
out = new Response(out.body, {
|
|
40
|
-
status: 200,
|
|
41
|
-
headers: out.headers,
|
|
42
|
-
});
|
|
43
|
-
if (/^(br|gzip)$/.test(out.headers.get("content-encoding"))) {
|
|
44
|
-
console.warn("Compression not yet supported for response");
|
|
45
|
-
out.headers.delete("content-encoding");
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (!(out instanceof Response)) {
|
|
50
|
-
throw new Error(`Invalid response type ${out}`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Here it should be a Response
|
|
54
|
-
|
|
55
|
-
// If we have CORS, set it up
|
|
56
|
-
if (ctx.options.cors) {
|
|
57
|
-
// Set the proper CORS headers
|
|
58
|
-
const origin = cors(ctx.options.cors.origin, ctx.headers.origin);
|
|
59
|
-
if (origin) {
|
|
60
|
-
out.headers.set("Access-Control-Allow-Origin", origin);
|
|
61
|
-
out.headers.set("Access-Control-Allow-Methods", ctx.options.cors.methods);
|
|
62
|
-
out.headers.set("Access-Control-Allow-Headers", ctx.options.cors.headers);
|
|
63
|
-
if (ctx.options.cors.credentials) {
|
|
64
|
-
out.headers.set("Access-Control-Allow-Credentials", "true");
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Only attach the headers if the user is using the timing API
|
|
70
|
-
// 1 item is the `init` so it doesn't count
|
|
71
|
-
if (ctx.time.times.length > 1) {
|
|
72
|
-
out.headers.set("Server-Timing", ctx.time.headers());
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// If we have a session, we need to persist it into a cookie
|
|
76
|
-
if (Object.keys(ctx.session || {}).length) {
|
|
77
|
-
if (!ctx.options.session?.store) {
|
|
78
|
-
throw ServerError.NO_STORE({});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Persistence is based on the Token
|
|
82
|
-
// Persistence is based on the Cookies
|
|
83
|
-
// No session cookies, generate a _persistent_ cookie
|
|
84
|
-
if (!ctx.cookies.session) {
|
|
85
|
-
ctx.res.cookies.session = createId();
|
|
86
|
-
}
|
|
87
|
-
const id = ctx.cookies.session;
|
|
88
|
-
|
|
89
|
-
// Saves the session in the session store
|
|
90
|
-
// Note that this is async but we are totally fine deferring it
|
|
91
|
-
ctx.options.session.store.set(id, ctx.session);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Cookies to headers
|
|
95
|
-
if (ctx.options.cookies) {
|
|
96
|
-
if (Object.keys(ctx.res.cookies).length) {
|
|
97
|
-
for (const cookie of ctx.res.cookies) {
|
|
98
|
-
ctx.res.headers.append("set-cookie", cookie);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Add the headers that are neeeded
|
|
104
|
-
if (ctx?.res?.headers) {
|
|
105
|
-
for (const key in ctx.res.headers) {
|
|
106
|
-
out.headers[key] = ctx.res.headers[key];
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return out;
|
|
111
|
-
}
|
package/src/pathPattern.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
export default function pathPattern(pattern, path) {
|
|
2
|
-
if (pattern === "*") return {};
|
|
3
|
-
|
|
4
|
-
pattern = `/${pattern.replace(/^\//, "")}`;
|
|
5
|
-
pattern = pattern.replace(/\/$/, "") || "/";
|
|
6
|
-
path = path.replace(/\/$/, "") || "/";
|
|
7
|
-
|
|
8
|
-
if (pattern === path) return {};
|
|
9
|
-
|
|
10
|
-
const params = {};
|
|
11
|
-
const pathParts = path.split("/").slice(1);
|
|
12
|
-
const pattParts = pattern.split("/").slice(1);
|
|
13
|
-
let allSame = true;
|
|
14
|
-
for (let i = 0; i < Math.max(pathParts.length, pattParts.length); i++) {
|
|
15
|
-
const patt = pattParts[i] || "";
|
|
16
|
-
const part = pathParts[i] || "";
|
|
17
|
-
const last = pattParts[pattParts.length - 1];
|
|
18
|
-
const key = patt
|
|
19
|
-
.replace(/^:/, "")
|
|
20
|
-
.replace(/\?$/, "")
|
|
21
|
-
.replace(/\(\w*\)/, "");
|
|
22
|
-
if (patt === part) continue;
|
|
23
|
-
if (patt.endsWith("?") && !part) continue;
|
|
24
|
-
if (patt.startsWith(":")) {
|
|
25
|
-
params[key] = part;
|
|
26
|
-
if (/\(\w*\)/.test(patt)) {
|
|
27
|
-
if (patt.includes("(number)")) {
|
|
28
|
-
const value = Number(part);
|
|
29
|
-
params[key] = Number.isNaN(value) ? undefined : value;
|
|
30
|
-
}
|
|
31
|
-
if (patt.includes("(date)")) {
|
|
32
|
-
const value = new Date(part);
|
|
33
|
-
params[key] = Number.isNaN(value) ? undefined : value;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
if ((!patt && last === "*" && part) || (patt === "*" && part)) {
|
|
39
|
-
params["*"] = params["*"] || [];
|
|
40
|
-
params["*"].push(part);
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
allSame = false;
|
|
44
|
-
}
|
|
45
|
-
if (allSame) return params;
|
|
46
|
-
return null;
|
|
47
|
-
}
|
package/src/pathPattern.test.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import pathPattern from "./pathPattern.js";
|
|
2
|
-
|
|
3
|
-
describe("pathPattern.js", () => {
|
|
4
|
-
it("matches the same string", () => {
|
|
5
|
-
expect(pathPattern("/", "/hello")).toEqual(null);
|
|
6
|
-
expect(pathPattern("/hello", "/")).toEqual(null);
|
|
7
|
-
expect(pathPattern("/hello", "/hello")).toEqual({});
|
|
8
|
-
expect(pathPattern("/hello/world", "/hello/world")).toEqual({});
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it("is trailing slash insensitive both ways", () => {
|
|
12
|
-
expect(pathPattern("/hello", "/hello")).toEqual({});
|
|
13
|
-
expect(pathPattern("/hello", "/hello/")).toEqual({});
|
|
14
|
-
expect(pathPattern("/hello/", "/hello")).toEqual({});
|
|
15
|
-
expect(pathPattern("/hello/", "/hello/")).toEqual({});
|
|
16
|
-
|
|
17
|
-
expect(pathPattern("/hello/world", "/hello/world")).toEqual({});
|
|
18
|
-
expect(pathPattern("/hello/world", "/hello/world/")).toEqual({});
|
|
19
|
-
expect(pathPattern("/hello/world/", "/hello/world")).toEqual({});
|
|
20
|
-
expect(pathPattern("/hello/world/", "/hello/world/")).toEqual({});
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("doesn't do partial matches", () => {
|
|
24
|
-
expect(pathPattern("/hello", "/hello/John")).toEqual(null);
|
|
25
|
-
expect(pathPattern("/hello/", "/hello/John")).toEqual(null);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("can capture simple params", () => {
|
|
29
|
-
expect(pathPattern("/:hello", "/john")).toEqual({ hello: "john" });
|
|
30
|
-
expect(pathPattern("/hello/:there", "/hello/john")).toEqual({
|
|
31
|
-
there: "john",
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("will parse the params as numbers", () => {
|
|
36
|
-
expect(pathPattern("/:id(number)", "/25")).toEqual({ id: 25 });
|
|
37
|
-
expect(pathPattern("/:id(number)", "/25.5")).toEqual({ id: 25.5 });
|
|
38
|
-
expect(pathPattern("/users/:id(number)", "/users/25")).toEqual({
|
|
39
|
-
id: 25,
|
|
40
|
-
});
|
|
41
|
-
expect(pathPattern("/:id(date)", "/2015-01-01")).toEqual({
|
|
42
|
-
id: new Date("2015-01-01"),
|
|
43
|
-
});
|
|
44
|
-
expect(pathPattern("/report/:id(date)", "/report/2015-01-01")).toEqual({
|
|
45
|
-
id: new Date("2015-01-01"),
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("will still match, but not parse it if it's an invalid number", () => {
|
|
50
|
-
expect(pathPattern("/:id(number)", "/hi")).toEqual({});
|
|
51
|
-
expect(pathPattern("/users/:id(number)", "/users/hi")).toEqual({});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("requires a part for the asterisk", () => {
|
|
55
|
-
expect(pathPattern("/hello/:there/*", "/hello/John")).toEqual(null);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("can make a part optional", () => {
|
|
59
|
-
expect(pathPattern("/:name?", "/")).toEqual({});
|
|
60
|
-
expect(pathPattern("/hello/:name?", "/hello/")).toEqual({});
|
|
61
|
-
expect(pathPattern("/:id(number)?", "/")).toEqual({});
|
|
62
|
-
expect(pathPattern("/:id(date)?", "/")).toEqual({});
|
|
63
|
-
expect(pathPattern("/:id(number)?", "/25")).toEqual({ id: 25 });
|
|
64
|
-
expect(pathPattern("/:id(date)?", "/2015-01-01")).toEqual({
|
|
65
|
-
id: new Date("2015-01-01"),
|
|
66
|
-
});
|
|
67
|
-
expect(pathPattern("/:name?", "/john")).toEqual({ name: "john" });
|
|
68
|
-
expect(pathPattern("/:name/*?", "/john")).toEqual({ name: "john" });
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("correctly matches the asterisk as an array of parts", () => {
|
|
72
|
-
expect(pathPattern("/*", "/john")).toEqual({ "*": ["john"] });
|
|
73
|
-
expect(pathPattern("/*", "/john/doe")).toEqual({ "*": ["john", "doe"] });
|
|
74
|
-
expect(pathPattern("/*/*", "/john/doe")).toEqual({ "*": ["john", "doe"] });
|
|
75
|
-
|
|
76
|
-
expect(pathPattern("/hello/*", "/hello/john")).toEqual({ "*": ["john"] });
|
|
77
|
-
expect(pathPattern("/hello/*", "/hello/john/doe")).toEqual({
|
|
78
|
-
"*": ["john", "doe"],
|
|
79
|
-
});
|
|
80
|
-
expect(pathPattern("/hello/*/*", "/hello/john/doe")).toEqual({
|
|
81
|
-
"*": ["john", "doe"],
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
expect(pathPattern("/:name/*", "/john/doe")).toEqual({
|
|
85
|
-
name: "john",
|
|
86
|
-
"*": ["doe"],
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
expect(pathPattern("/:name/*", "/john/doe/derek")).toEqual({
|
|
90
|
-
name: "john",
|
|
91
|
-
"*": ["doe", "derek"],
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
expect(pathPattern("/:name/*/*", "/john/doe/derek")).toEqual({
|
|
95
|
-
name: "john",
|
|
96
|
-
"*": ["doe", "derek"],
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
});
|
package/src/polyfill.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
// out = new Response(out, { headers: { "content-type": type } });
|
|
2
|
-
if (typeof Response === "undefined") {
|
|
3
|
-
global.Response = function Response(body, other = {}) {
|
|
4
|
-
return { body, ...other };
|
|
5
|
-
};
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
// Polyfill Netlify's environment variables
|
|
9
|
-
globalThis.env = {};
|
|
10
|
-
if (typeof Netlify !== "undefined") {
|
|
11
|
-
Object.assign(env, Netlify.env.toObject());
|
|
12
|
-
}
|
|
13
|
-
if (typeof process !== "undefined") {
|
|
14
|
-
Object.assign(env, process.env);
|
|
15
|
-
}
|
|
16
|
-
if (typeof import.meta.env !== "undefined") {
|
|
17
|
-
Object.assign(env, import.meta.env);
|
|
18
|
-
}
|
package/src/reply.js
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
|
|
3
|
-
import { createCookies, toWeb, types } from "./helpers/index.js";
|
|
4
|
-
|
|
5
|
-
function Reply() {
|
|
6
|
-
this.res = {
|
|
7
|
-
headers: {},
|
|
8
|
-
cookies: {},
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// INTERNAL
|
|
13
|
-
Reply.prototype.generateHeaders = function () {
|
|
14
|
-
const headers = new Headers(this.res.headers);
|
|
15
|
-
for (const cookie of createCookies(this.res.cookies)) {
|
|
16
|
-
headers.append("set-cookie", cookie);
|
|
17
|
-
}
|
|
18
|
-
return headers;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
// PARTIAL
|
|
22
|
-
Reply.prototype.status = function (status) {
|
|
23
|
-
this.res.status = status;
|
|
24
|
-
return this;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// `.html`, `html`, `text/html`
|
|
28
|
-
Reply.prototype.type = function (type) {
|
|
29
|
-
if (!type) return this;
|
|
30
|
-
type = types[type.replace(/^\./)] || type;
|
|
31
|
-
return this.headers({ "content-type": type });
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// Prompt for download from the user side
|
|
35
|
-
Reply.prototype.download = function (name, type) {
|
|
36
|
-
// filename.txt and no explicit type => add headers "text/plain"
|
|
37
|
-
if (name && !type) type = name.split(".")[1];
|
|
38
|
-
|
|
39
|
-
// Add the Content-Type if there's a type
|
|
40
|
-
if (type) this.type(type);
|
|
41
|
-
|
|
42
|
-
const filename = name ? `; filename="${name}"` : "";
|
|
43
|
-
return this.headers({ "content-disposition": `attachment${filename}` });
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// Set extra headers
|
|
47
|
-
Reply.prototype.headers = function (headers) {
|
|
48
|
-
if (!headers || typeof headers !== "object") return this;
|
|
49
|
-
for (const key in headers) {
|
|
50
|
-
this.res.headers[key] = headers[key];
|
|
51
|
-
}
|
|
52
|
-
return this;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
// Set extra cookies
|
|
56
|
-
Reply.prototype.cookies = function (cookies) {
|
|
57
|
-
if (!cookies || typeof cookies !== "object") return this;
|
|
58
|
-
for (const key in cookies) {
|
|
59
|
-
if (typeof cookies[key] === "string") {
|
|
60
|
-
this.res.cookies[key] = { value: cookies[key] };
|
|
61
|
-
} else {
|
|
62
|
-
this.res.cookies[key] = cookies[key];
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return this;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
// FINAL
|
|
69
|
-
Reply.prototype.json = function (body) {
|
|
70
|
-
return this.headers({
|
|
71
|
-
"content-type": "application/json",
|
|
72
|
-
}).send(JSON.stringify(body));
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
Reply.prototype.redirect = function (Location) {
|
|
76
|
-
return this.headers({ Location }).status(302).send();
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
Reply.prototype.file = async function (path) {
|
|
80
|
-
try {
|
|
81
|
-
const data = await fs.readFile(path);
|
|
82
|
-
const ext = path.split(".").pop();
|
|
83
|
-
return this.type(ext).send(data);
|
|
84
|
-
} catch (error) {
|
|
85
|
-
if (error.code === "ENOENT") {
|
|
86
|
-
return status(404).send();
|
|
87
|
-
}
|
|
88
|
-
throw error;
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
Reply.prototype.view = async function (path) {
|
|
93
|
-
return async (ctx) => {
|
|
94
|
-
if (!ctx.options.views) {
|
|
95
|
-
throw new Error("Views not enabled");
|
|
96
|
-
}
|
|
97
|
-
const data = await ctx.options.views.read(path);
|
|
98
|
-
if (data) return this.type(path.split(".").pop()).send(data);
|
|
99
|
-
return this.status(404).send();
|
|
100
|
-
};
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
Reply.prototype.send = function (body = "") {
|
|
104
|
-
const { status = 200 } = this.res;
|
|
105
|
-
|
|
106
|
-
if (typeof body === "string") {
|
|
107
|
-
// Not yet set, so infer the type from type of string
|
|
108
|
-
if (!this.res.headers["content-type"]) {
|
|
109
|
-
const isHtml = body.startsWith("<");
|
|
110
|
-
this.res.headers["content-type"] = isHtml ? "text/html" : "text/plain";
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const headers = this.generateHeaders();
|
|
114
|
-
return new Response(body, { status, headers });
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const name = body?.constructor?.name;
|
|
118
|
-
if (name === "Buffer") {
|
|
119
|
-
const headers = this.generateHeaders();
|
|
120
|
-
return new Response(body, { status, headers });
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// WebStream already, just pass it through
|
|
124
|
-
if (name === "ReadableStream") {
|
|
125
|
-
const headers = this.generateHeaders();
|
|
126
|
-
return new Response(body, { status, headers });
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Node stream, convert it to web stream
|
|
130
|
-
if (name === "PassThrough" || name === "Readable") {
|
|
131
|
-
return new Response(toWeb(body), { status, headers });
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// This is a bit loopy, send({}) => json({}) => send('{}')
|
|
135
|
-
return this.json(body);
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
// INTERNAL
|
|
139
|
-
export { Reply };
|
|
140
|
-
|
|
141
|
-
// PARTIAL
|
|
142
|
-
export const status = (...args) => new Reply().status(...args);
|
|
143
|
-
export const headers = (...args) => new Reply().headers(...args);
|
|
144
|
-
export const type = (...args) => new Reply().type(...args);
|
|
145
|
-
export const download = (...args) => new Reply().download(...args);
|
|
146
|
-
export const cookies = (...args) => new Reply().cookies(...args);
|
|
147
|
-
|
|
148
|
-
// FINAL
|
|
149
|
-
export const send = (...args) => new Reply().send(...args);
|
|
150
|
-
export const json = (...args) => new Reply().json(...args);
|
|
151
|
-
export const file = (...args) => new Reply().file(...args);
|
|
152
|
-
export const redirect = (...args) => new Reply().redirect(...args);
|
|
153
|
-
export const view = (...args) => new Reply().view(...args);
|
package/src/router.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
export default function router() {
|
|
2
|
-
if (!(this instanceof router)) {
|
|
3
|
-
return new router();
|
|
4
|
-
}
|
|
5
|
-
this.handlers = {
|
|
6
|
-
socket: [],
|
|
7
|
-
get: [],
|
|
8
|
-
head: [],
|
|
9
|
-
post: [],
|
|
10
|
-
put: [],
|
|
11
|
-
patch: [],
|
|
12
|
-
delete: [],
|
|
13
|
-
options: [],
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// INTERNAL
|
|
18
|
-
router.prototype.handle = function (method, path, ...middleware) {
|
|
19
|
-
this.handlers[method].push([method, path, ...middleware]);
|
|
20
|
-
return this;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
router.prototype.socket = function (path, ...middleware) {
|
|
24
|
-
return this.handle("socket", path, ...middleware);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
router.prototype.get = function (path, ...middleware) {
|
|
28
|
-
return this.handle("get", path, ...middleware);
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
router.prototype.head = function (path, ...middleware) {
|
|
32
|
-
return this.handle("head", path, ...middleware);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
router.prototype.post = function (path, ...middleware) {
|
|
36
|
-
return this.handle("post", path, ...middleware);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
router.prototype.put = function (path, ...middleware) {
|
|
40
|
-
return this.handle("put", path, ...middleware);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
router.prototype.patch = function (path, ...middleware) {
|
|
44
|
-
return this.handle("patch", path, ...middleware);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
router.prototype.del = function (path, ...middleware) {
|
|
48
|
-
return this.handle("del", path, ...middleware);
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
router.prototype.options = function (path, ...middleware) {
|
|
52
|
-
return this.handle("options", path, ...middleware);
|
|
53
|
-
};
|