@server/next 0.17.2 → 0.18.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/package.json +7 -10
- package/readme.md +122 -20
- package/src/bucket.js +5 -3
- package/src/context/node.js +28 -0
- package/src/{parseBody.js → context/parseBody.js} +8 -16
- package/src/{parseBody.test.js → context/parseBody.test.js} +3 -3
- package/src/context/parseCookies.js +9 -0
- package/src/context/winter.js +25 -0
- package/src/helpers/define.js +18 -0
- package/src/helpers/getMachine.js +13 -0
- package/src/helpers/handleRequest.js +20 -0
- package/src/helpers/index.js +5 -0
- package/src/helpers/iterate.js +8 -0
- package/src/helpers/types.js +79 -0
- package/src/index.js +104 -329
- package/src/index.test.js +45 -0
- package/src/parseResponse.js +48 -0
- package/src/pathPattern.js +27 -20
- package/src/polyfill.js +6 -0
- package/src/reply.js +95 -0
- package/src/router.js +47 -0
- package/src/RequestLogger.js +0 -123
- package/src/ServerUrl.js +0 -68
- package/src/ServerUrl.test.js +0 -63
- package/src/getMime.js +0 -48
- package/src/logger.js +0 -21
- /package/src/{color.js → helpers/color.js} +0 -0
package/src/index.js
CHANGED
|
@@ -1,368 +1,143 @@
|
|
|
1
|
-
import "
|
|
1
|
+
import "./polyfill.js";
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import zlib from "node:zlib";
|
|
8
|
-
import { pipeline } from "node:stream/promises";
|
|
9
|
-
import { Readable, PassThrough } from "node:stream";
|
|
3
|
+
import Bucket from "./bucket.js";
|
|
4
|
+
import createNodeContext from "./context/node.js";
|
|
5
|
+
import createWinterContext from "./context/winter.js";
|
|
6
|
+
import { getMachine, handleRequest, iterate } from "./helpers/index.js";
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
// Export the reply helpers
|
|
9
|
+
export * from "./reply.js";
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import pathPattern from "./pathPattern.js";
|
|
17
|
-
import parseBody from "./parseBody.js";
|
|
11
|
+
// Allow to create a sub-router
|
|
12
|
+
export { default as router } from "./router.js";
|
|
18
13
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
(req.headers["x-forwarded-for"] || "").split(",").pop() ||
|
|
24
|
-
req.connection.remoteAddress ||
|
|
25
|
-
req.socket.remoteAddress ||
|
|
26
|
-
req.connection.socket.remoteAddress;
|
|
27
|
-
|
|
28
|
-
const measure = (ctx) => {
|
|
29
|
-
const sizeUp = new PassThrough();
|
|
30
|
-
ctx.res.size = 0;
|
|
31
|
-
sizeUp.on("data", (chunk) => {
|
|
32
|
-
ctx.res.size += chunk.length;
|
|
33
|
-
});
|
|
34
|
-
return sizeUp;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const findEncoding = (acceptEncoding) => {
|
|
38
|
-
let encoding;
|
|
39
|
-
if (/\bbr\b/.test(acceptEncoding)) {
|
|
40
|
-
encoding = "br";
|
|
41
|
-
} else if (/\bgzip\b/.test(acceptEncoding)) {
|
|
42
|
-
encoding = "gzip";
|
|
43
|
-
} else if (/\bdeflate\b/.test(acceptEncoding)) {
|
|
44
|
-
encoding = "deflate";
|
|
14
|
+
// Export the main server()
|
|
15
|
+
export default function server(options = {}) {
|
|
16
|
+
if (!(this instanceof server)) {
|
|
17
|
+
return new server(options);
|
|
45
18
|
}
|
|
46
19
|
|
|
47
|
-
|
|
48
|
-
deflate: () => zlib.createDeflate(),
|
|
49
|
-
gzip: () => zlib.createGzip(),
|
|
50
|
-
br: () => zlib.createBrotliCompress(),
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const compress = methods[encoding];
|
|
54
|
-
return [encoding, compress];
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const api = {
|
|
58
|
-
get: [],
|
|
59
|
-
post: [],
|
|
60
|
-
put: [],
|
|
61
|
-
patch: [],
|
|
62
|
-
del: [],
|
|
63
|
-
options: [],
|
|
64
|
-
head: [],
|
|
65
|
-
};
|
|
20
|
+
this.platform = getMachine();
|
|
66
21
|
|
|
67
|
-
|
|
68
|
-
url: new ServerUrl(req.protocol + "://" + req.headers["host"] + req.url),
|
|
69
|
-
method: req.method.toLowerCase(),
|
|
70
|
-
headers: req.headers,
|
|
71
|
-
ip: getIp(req),
|
|
72
|
-
time: { _init: performance.now() },
|
|
73
|
-
api,
|
|
74
|
-
req,
|
|
75
|
-
});
|
|
22
|
+
this.handlers = {};
|
|
76
23
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
24
|
+
options.views = options.views ? Bucket(options.views) : null;
|
|
25
|
+
options.public = options.public ? Bucket(options.public) : null;
|
|
26
|
+
options.uploads = options.uploads ? Bucket(options.uploads) : null;
|
|
80
27
|
|
|
81
|
-
|
|
82
|
-
app.middleware.push({
|
|
83
|
-
handle: (error) => {
|
|
84
|
-
// console.clear();
|
|
85
|
-
// console.log("ERROR!", error);
|
|
86
|
-
return {
|
|
87
|
-
status: 500,
|
|
88
|
-
body: JSON.stringify({ error: error.message }),
|
|
89
|
-
type: "application/json",
|
|
90
|
-
};
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
};
|
|
28
|
+
this.options = options;
|
|
94
29
|
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
(
|
|
102
|
-
() => false
|
|
103
|
-
);
|
|
104
|
-
if (size) {
|
|
105
|
-
return fs.createReadStream(file);
|
|
106
|
-
}
|
|
30
|
+
// WEBSOCKETS stuff
|
|
31
|
+
this.sockets = [];
|
|
32
|
+
this.websocket = {
|
|
33
|
+
message: async (ws, body) => {
|
|
34
|
+
this.handlers.socket
|
|
35
|
+
?.filter((s) => s[0] === "message")
|
|
36
|
+
?.map((s) => s[1]({ socket: ws, sockets: this.sockets, body }));
|
|
107
37
|
},
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
app.events = {
|
|
111
|
-
error: [],
|
|
112
|
-
ready: [],
|
|
38
|
+
open: (ws) => this.sockets.push(ws),
|
|
39
|
+
close: (ws) => this.sockets.splice(this.sockets.indexOf(ws), 1),
|
|
113
40
|
};
|
|
114
41
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// Plain number
|
|
131
|
-
if (typeof data === "number") {
|
|
132
|
-
res.status = data;
|
|
133
|
-
res.body = "";
|
|
134
|
-
res.type = "text/plain";
|
|
135
|
-
return res;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Plain string, which can only be text or html
|
|
139
|
-
if (typeof data === "string") {
|
|
140
|
-
res.status = 200;
|
|
141
|
-
res.body = data;
|
|
142
|
-
const isHtml = data.trim().startsWith("<");
|
|
143
|
-
res.type = isHtml ? "text/html" : "text/plain";
|
|
144
|
-
return res;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// When piping the output
|
|
148
|
-
if (data.pipe) {
|
|
149
|
-
res.status = 200;
|
|
150
|
-
res.body = data;
|
|
151
|
-
// It's a file; find its mimetype
|
|
152
|
-
if (res.body.path) {
|
|
153
|
-
res.type = await getMime(res.body.path);
|
|
154
|
-
}
|
|
155
|
-
return res;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Plain object, merge it with the existing one
|
|
159
|
-
if (data.type) res.type = data.type;
|
|
160
|
-
res.headers = { ...res.headers, ...(data.headers || {}) };
|
|
161
|
-
res.body = data.body || ""; // This could also be a pipe and that's okay
|
|
162
|
-
res.status = data.status || 200; // user set > default
|
|
163
|
-
return res;
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
export default function (options = {}, plugins) {
|
|
167
|
-
if (!options.public) {
|
|
168
|
-
options.public = "public";
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const server = http.createServer(async (req, res) => {
|
|
172
|
-
const logger = new RequestLogger(req);
|
|
173
|
-
const ctx = { ...getCtx(req), bucket: options.bucket, options };
|
|
174
|
-
|
|
175
|
-
const parsed = await parseBody(
|
|
176
|
-
ctx.req,
|
|
177
|
-
ctx.headers["content-type"],
|
|
178
|
-
ctx.bucket
|
|
179
|
-
);
|
|
180
|
-
if (parsed) {
|
|
181
|
-
ctx.body = parsed.body;
|
|
182
|
-
ctx.files = parsed.files;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
let out;
|
|
186
|
-
let err;
|
|
187
|
-
// Will (hopefully) be overwritten later on!
|
|
188
|
-
ctx.res = { status: 500, headers: {} };
|
|
189
|
-
for (let cb of app.middleware) {
|
|
190
|
-
try {
|
|
191
|
-
if (err) {
|
|
192
|
-
if (cb.handle) {
|
|
193
|
-
out = await cb.handle(err);
|
|
42
|
+
if (this.platform.runtime === "node") {
|
|
43
|
+
(async () => {
|
|
44
|
+
const http = await import("http");
|
|
45
|
+
http
|
|
46
|
+
.createServer(async (request, response) => {
|
|
47
|
+
const ctx = await createNodeContext(request, options);
|
|
48
|
+
ctx.platform = this.platform;
|
|
49
|
+
|
|
50
|
+
const out = await handleRequest(this.handlers, ctx);
|
|
51
|
+
|
|
52
|
+
response.writeHead(out.status || 200, { header: out.headers });
|
|
53
|
+
if (out.body instanceof ReadableStream) {
|
|
54
|
+
await iterate(out.body, (chunk) => response.write(chunk));
|
|
55
|
+
} else {
|
|
56
|
+
response.write(out.body || "");
|
|
194
57
|
}
|
|
195
|
-
|
|
196
|
-
out = await cb(ctx);
|
|
197
|
-
}
|
|
198
|
-
if (out) {
|
|
199
|
-
ctx.res = await parseOutput(ctx.res, out);
|
|
200
|
-
break;
|
|
201
|
-
}
|
|
202
|
-
} catch (error) {
|
|
203
|
-
err = error;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (ctx.res.type) {
|
|
208
|
-
ctx.res.headers["content-type"] = ctx.res.type;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
ctx.time._total = performance.now();
|
|
212
|
-
ctx.res.headers["server-timing"] = ctx.res.headers["server-timing"] || "";
|
|
213
|
-
Object.entries(ctx.time).forEach(([name, value], i, times) => {
|
|
214
|
-
if (name === "_init") return; // Index = 0
|
|
215
|
-
ctx.res.headers["server-timing"] +=
|
|
216
|
-
name + ";dur=" + Math.round(value - times[i - 1][1]);
|
|
217
|
-
if (i !== times.length - 1) {
|
|
218
|
-
ctx.res.headers["server-timing"] += ", ";
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
const [encoding, compress] = findEncoding(ctx.headers["accept-encoding"]);
|
|
223
|
-
if (encoding) {
|
|
224
|
-
ctx.res.headers["content-encoding"] = encoding;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
res.writeHead(ctx.res.status, ctx.res.headers);
|
|
228
|
-
|
|
229
|
-
// If it's not a pipe, e.g. a String, make it a pipe
|
|
230
|
-
if (!ctx.res.body.pipe) {
|
|
231
|
-
ctx.res.body = Readable.from([ctx.res.body]);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (compress) {
|
|
235
|
-
await pipeline(ctx.res.body, compress(), measure(ctx), res);
|
|
236
|
-
} else {
|
|
237
|
-
await pipeline(ctx.res.body, measure(ctx), res);
|
|
238
|
-
}
|
|
239
|
-
res.end();
|
|
240
|
-
|
|
241
|
-
// The actual sent headers, as seen by the response
|
|
242
|
-
ctx.res.headers = Object.fromEntries(
|
|
243
|
-
res._header
|
|
244
|
-
.split("\r\n")
|
|
245
|
-
.filter(Boolean)
|
|
246
|
-
.slice(1)
|
|
247
|
-
.map((line) => {
|
|
248
|
-
const [key, ...vals] = line.split(":");
|
|
249
|
-
return [key.toLowerCase(), vals.join(":").trim()];
|
|
58
|
+
response.end();
|
|
250
59
|
})
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (err) {
|
|
255
|
-
logger.error(err);
|
|
256
|
-
}
|
|
257
|
-
});
|
|
60
|
+
.listen(options.port || 3000);
|
|
61
|
+
})();
|
|
62
|
+
}
|
|
258
63
|
|
|
259
|
-
|
|
64
|
+
this.fetch = async (request, env, fetchCtx) => {
|
|
65
|
+
if (env?.upgrade(request)) return;
|
|
260
66
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
if (error) {
|
|
264
|
-
app.events.error.forEach((cb) => cb(error));
|
|
265
|
-
if (!isProduction) {
|
|
266
|
-
console.error("Error:", error);
|
|
267
|
-
}
|
|
268
|
-
} else {
|
|
269
|
-
app.events.ready.forEach((cb) => cb({ options }));
|
|
270
|
-
logger.header({ api, options });
|
|
271
|
-
}
|
|
272
|
-
});
|
|
67
|
+
const ctx = await createWinterContext(request, options);
|
|
68
|
+
ctx.platform = this.platform;
|
|
273
69
|
|
|
274
|
-
|
|
70
|
+
return await handleRequest(this.handlers, ctx);
|
|
71
|
+
};
|
|
275
72
|
}
|
|
276
73
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
ctx.url.params = match;
|
|
285
|
-
return callback(ctx);
|
|
286
|
-
};
|
|
74
|
+
// INTERNAL
|
|
75
|
+
server.prototype.handle = function (name, ...middleware) {
|
|
76
|
+
if (!this.handlers[name]) {
|
|
77
|
+
this.handlers[name] = [];
|
|
78
|
+
}
|
|
79
|
+
this.handlers[name].push(...middleware);
|
|
80
|
+
return this;
|
|
287
81
|
};
|
|
288
82
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
return (ctx) => {
|
|
293
|
-
if (ctx.method !== "post") return;
|
|
294
|
-
const match = pathPattern(pattern, ctx.url.path);
|
|
295
|
-
if (!match) return null;
|
|
296
|
-
ctx.url.params = match;
|
|
297
|
-
return callback(ctx);
|
|
298
|
-
};
|
|
83
|
+
server.prototype.socket = function (path, ...middleware) {
|
|
84
|
+
return this.handle("socket", [path, ...middleware]);
|
|
299
85
|
};
|
|
300
86
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return (ctx) => {
|
|
305
|
-
if (ctx.method !== "put") return;
|
|
306
|
-
const match = pathPattern(pattern, ctx.url.path);
|
|
307
|
-
if (!match) return null;
|
|
308
|
-
ctx.url.params = match;
|
|
309
|
-
return callback(ctx);
|
|
310
|
-
};
|
|
87
|
+
server.prototype.get = function (path, ...middleware) {
|
|
88
|
+
return this.handle("get", [path, ...middleware]);
|
|
311
89
|
};
|
|
312
90
|
|
|
313
|
-
|
|
314
|
-
|
|
91
|
+
server.prototype.head = function (path, ...middleware) {
|
|
92
|
+
return this.handle("head", [path, ...middleware]);
|
|
93
|
+
};
|
|
315
94
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const match = pathPattern(pattern, ctx.url.path);
|
|
319
|
-
if (!match) return null;
|
|
320
|
-
ctx.url.params = match;
|
|
321
|
-
return callback(ctx);
|
|
322
|
-
};
|
|
95
|
+
server.prototype.post = function (path, ...middleware) {
|
|
96
|
+
return this.handle("post", [path, ...middleware]);
|
|
323
97
|
};
|
|
324
98
|
|
|
325
|
-
|
|
326
|
-
|
|
99
|
+
server.prototype.put = function (path, ...middleware) {
|
|
100
|
+
return this.handle("put", [path, ...middleware]);
|
|
101
|
+
};
|
|
327
102
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
const match = pathPattern(pattern, ctx.url.path);
|
|
331
|
-
if (!match) return null;
|
|
332
|
-
ctx.url.params = match;
|
|
333
|
-
return callback(ctx);
|
|
334
|
-
};
|
|
103
|
+
server.prototype.patch = function (path, ...middleware) {
|
|
104
|
+
return this.handle("patch", [path, ...middleware]);
|
|
335
105
|
};
|
|
336
106
|
|
|
337
|
-
|
|
338
|
-
|
|
107
|
+
server.prototype.del = function (path, ...middleware) {
|
|
108
|
+
return this.handle("del", [path, ...middleware]);
|
|
109
|
+
};
|
|
339
110
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const match = pathPattern(pattern, ctx.url.path);
|
|
343
|
-
if (!match) return null;
|
|
344
|
-
ctx.url.params = match;
|
|
345
|
-
return callback(ctx);
|
|
346
|
-
};
|
|
111
|
+
server.prototype.options = function (path, ...middleware) {
|
|
112
|
+
return this.handle("options", [path, ...middleware]);
|
|
347
113
|
};
|
|
348
114
|
|
|
349
|
-
|
|
350
|
-
|
|
115
|
+
server.prototype.use = function (...middleware) {
|
|
116
|
+
let path = "*";
|
|
117
|
+
if (typeof middleware[0] === "string" || middleware[0] instanceof RegExp) {
|
|
118
|
+
path = middleware.shift();
|
|
119
|
+
}
|
|
351
120
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
121
|
+
this.handle("socket", [path, ...middleware]);
|
|
122
|
+
this.handle("get", [path, ...middleware]);
|
|
123
|
+
this.handle("head", [path, ...middleware]);
|
|
124
|
+
this.handle("post", [path, ...middleware]);
|
|
125
|
+
this.handle("put", [path, ...middleware]);
|
|
126
|
+
this.handle("patch", [path, ...middleware]);
|
|
127
|
+
this.handle("del", [path, ...middleware]);
|
|
128
|
+
this.handle("options", [path, ...middleware]);
|
|
129
|
+
|
|
130
|
+
return this;
|
|
359
131
|
};
|
|
360
132
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
133
|
+
server.prototype.router = function (basePath, router) {
|
|
134
|
+
basePath = "/" + basePath.replace(/^\//, "").replace(/\/$/, "") + "/";
|
|
135
|
+
for (const method in router.handlers) {
|
|
136
|
+
const handlers = router.handlers[method].map(([path, ...callbacks]) => [
|
|
137
|
+
basePath + path.replace(/^\//, ""),
|
|
138
|
+
...callbacks,
|
|
139
|
+
]);
|
|
140
|
+
this.handle(method, ...handlers);
|
|
141
|
+
}
|
|
142
|
+
return this;
|
|
368
143
|
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import server, { status } from "./index.js";
|
|
2
|
+
|
|
3
|
+
describe("server", () => {
|
|
4
|
+
const app = server()
|
|
5
|
+
.get("/", () => "Hello world")
|
|
6
|
+
.get("/text", () => "Hello world")
|
|
7
|
+
.get("/array", () => ["Hello world"])
|
|
8
|
+
.get("/object", () => ({ hello: "world" }))
|
|
9
|
+
.get("/status", () => 201)
|
|
10
|
+
.post("/", (ctx) => status(201).send(ctx.body));
|
|
11
|
+
|
|
12
|
+
it("can render hello world", async () => {
|
|
13
|
+
const res = await app.fetch(new Request("http://localhost:3000/"));
|
|
14
|
+
|
|
15
|
+
expect(res.status).toBe(200);
|
|
16
|
+
expect(await res.text()).toBe("Hello world");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("can post new data", async () => {
|
|
20
|
+
const res = await app.fetch(
|
|
21
|
+
new Request("http://localhost:3000/", {
|
|
22
|
+
method: "POST",
|
|
23
|
+
body: "New Data",
|
|
24
|
+
})
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
expect(res.status).toBe(201);
|
|
28
|
+
expect(await res.text()).toBe("New Data");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("will return JSON", async () => {
|
|
32
|
+
const res = await app.fetch(
|
|
33
|
+
new Request("http://localhost:3000/", {
|
|
34
|
+
method: "POST",
|
|
35
|
+
body: JSON.stringify({ hello: "world" }),
|
|
36
|
+
headers: { "content-type": "application/json" },
|
|
37
|
+
})
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
expect(res.status).toBe(201);
|
|
41
|
+
expect(await res.json()).toEqual({ hello: "world" });
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("all the replies", () => {});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// import { Readable } from "node:stream";
|
|
2
|
+
|
|
3
|
+
import { json } from "./reply.js";
|
|
4
|
+
|
|
5
|
+
export default async function parseResponse(handler, ctx) {
|
|
6
|
+
let out = await handler(ctx);
|
|
7
|
+
|
|
8
|
+
// undefined || null || 0 || false || ~""~ -> empty string is still 200
|
|
9
|
+
if (!out && typeof out !== "string") return null;
|
|
10
|
+
|
|
11
|
+
if (typeof out === "function") {
|
|
12
|
+
out = out(ctx);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// A plain number is a status code
|
|
16
|
+
if (typeof out === "number") {
|
|
17
|
+
out = new Response(undefined, { status: out });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// A plain string will be converted to either html or plain
|
|
21
|
+
if (typeof out === "string") {
|
|
22
|
+
const type = /\s*\</.test(out) ? "text/html" : "text/plain";
|
|
23
|
+
out = new Response(out, { headers: { "content-type": type } });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// https://stackoverflow.com/a/69745650/938236
|
|
27
|
+
if (out?.constructor === Object || Array.isArray(out)) {
|
|
28
|
+
out = json(out);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// if (out instanceof Readable) {
|
|
32
|
+
// if (out?.prototype?.name === 'Readable') {
|
|
33
|
+
// out = new Response(Readable.toWeb(out));
|
|
34
|
+
// }
|
|
35
|
+
|
|
36
|
+
if (!(out instanceof Response)) {
|
|
37
|
+
throw new Error(`Invalid response type ${out}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Here it should be a Response
|
|
41
|
+
if (ctx?.res?.headers) {
|
|
42
|
+
for (let key in ctx.res.headers) {
|
|
43
|
+
out.headers[key] = ctx.res.headers[key];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return out;
|
|
48
|
+
}
|
package/src/pathPattern.js
CHANGED
|
@@ -1,24 +1,31 @@
|
|
|
1
|
-
import { URLPattern } from "urlpattern-polyfill";
|
|
2
|
-
|
|
3
1
|
export default function pathPattern(pattern, path) {
|
|
4
2
|
pattern = pattern.replace(/\/$/, "") || "/";
|
|
5
3
|
path = path.replace(/\/$/, "") || "/";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
4
|
+
|
|
5
|
+
if (pattern === path) return {};
|
|
6
|
+
|
|
7
|
+
const params = {};
|
|
8
|
+
const pathParts = path.split("/").slice(1);
|
|
9
|
+
const pattParts = pattern.split("/").slice(1);
|
|
10
|
+
let allSame = true;
|
|
11
|
+
for (let i = 0; i < Math.max(pathParts.length, pattParts.length); i++) {
|
|
12
|
+
const patt = pattParts[i] || "";
|
|
13
|
+
const part = pathParts[i] || "";
|
|
14
|
+
const last = pattParts[pattParts.length - 1];
|
|
15
|
+
const key = patt.replace(/^:/, "").replace(/\?$/, "");
|
|
16
|
+
if (patt === part) continue;
|
|
17
|
+
if (patt.endsWith("?") && !part) continue;
|
|
18
|
+
if (patt.startsWith(":")) {
|
|
19
|
+
params[key] = part;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if ((!patt && last === "*" && part) || (patt === "*" && part)) {
|
|
23
|
+
params["*"] = params["*"] || [];
|
|
24
|
+
params["*"].push(part);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
allSame = false;
|
|
28
|
+
}
|
|
29
|
+
if (allSame) return params;
|
|
30
|
+
return false;
|
|
24
31
|
}
|