@server/next 0.25.9 → 0.26.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 +2 -3
- package/src/helpers/bucket.js +7 -2
- package/src/helpers/bucket.test.js +11 -0
- package/src/helpers/createWebsocket.js +21 -0
- package/src/helpers/index.js +3 -0
- package/src/helpers/iteratorAsyncToReadable.js +20 -0
- package/src/helpers/iteratorToReadable.js +10 -0
- package/src/index.d.ts +15 -1
- package/src/index.js +4 -22
- package/src/parseResponse.js +19 -3
- package/src/reply.js +5 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@server/next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "A fully-fledged web server with routing, file uploads, sessions, static files, schema validation, websockets, testing, etc.",
|
|
5
5
|
"homepage": "https://server-js.com/",
|
|
6
6
|
"repository": "https://github.com/franciscop/server-next.git",
|
|
@@ -32,8 +32,7 @@
|
|
|
32
32
|
"main": "src/index.js",
|
|
33
33
|
"types": "src/index.d.ts",
|
|
34
34
|
"files": [
|
|
35
|
-
"src/"
|
|
36
|
-
"jsx-dev-runtime.js"
|
|
35
|
+
"src/"
|
|
37
36
|
],
|
|
38
37
|
"exports": {
|
|
39
38
|
".": "./src/index.js",
|
package/src/helpers/bucket.js
CHANGED
|
@@ -33,9 +33,14 @@ function thinLocalBucket(root) {
|
|
|
33
33
|
}
|
|
34
34
|
return fs.createWriteStream(fullPath);
|
|
35
35
|
},
|
|
36
|
-
delete: (name) => {
|
|
36
|
+
delete: async (name) => {
|
|
37
37
|
const fullPath = absolute(name);
|
|
38
|
-
|
|
38
|
+
try {
|
|
39
|
+
await fsp.unlink(fullPath);
|
|
40
|
+
return true;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
39
44
|
},
|
|
40
45
|
};
|
|
41
46
|
}
|
|
@@ -37,4 +37,15 @@ describe("bucket", () => {
|
|
|
37
37
|
const stream = await localBucket.read("testFile.txt");
|
|
38
38
|
expect(stream).toBeNull();
|
|
39
39
|
});
|
|
40
|
+
|
|
41
|
+
it("removes an existing file", async () => {
|
|
42
|
+
await localBucket.write("testFile.txt", "To be deleted");
|
|
43
|
+
const isDeleted = await localBucket.delete("testFile.txt");
|
|
44
|
+
expect(isDeleted).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("tries to remove a non-existing file", async () => {
|
|
48
|
+
const isDeleted = await localBucket.delete("nonExistentFile.txt");
|
|
49
|
+
expect(isDeleted).toBe(false);
|
|
50
|
+
});
|
|
40
51
|
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default function createWebsocket(sockets, handlers) {
|
|
2
|
+
return {
|
|
3
|
+
message: async (socket, body) => {
|
|
4
|
+
handlers.socket
|
|
5
|
+
?.filter((s) => s[1] === "message")
|
|
6
|
+
?.map((s) => s[2]({ socket, sockets, body }));
|
|
7
|
+
},
|
|
8
|
+
open: (socket) => {
|
|
9
|
+
sockets.push(socket);
|
|
10
|
+
handlers.socket
|
|
11
|
+
?.filter((s) => s[1] === "open")
|
|
12
|
+
?.map((s) => s[2]({ socket, sockets, body }));
|
|
13
|
+
},
|
|
14
|
+
close: (socket) => {
|
|
15
|
+
sockets.splice(sockets.indexOf(socket), 1);
|
|
16
|
+
handlers.socket
|
|
17
|
+
?.filter((s) => s[1] === "close")
|
|
18
|
+
?.map((s) => s[2]({ socket, sockets, body }));
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
package/src/helpers/index.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
export { default as createCookies } from "./createCookies.js";
|
|
2
2
|
export { default as createId } from "./createId.js";
|
|
3
|
+
export { default as createWebsocket } from "./createWebsocket.js";
|
|
3
4
|
export { default as config } from "./config.js";
|
|
4
5
|
export { default as cors } from "./cors.js";
|
|
5
6
|
export { default as define } from "./define.js";
|
|
6
7
|
export { default as getMachine } from "./getMachine.js";
|
|
7
8
|
export { default as handleRequest } from "./handleRequest.js";
|
|
8
9
|
export { default as iterate } from "./iterate.js";
|
|
10
|
+
export { default as iteratorToReadable } from "./iteratorToReadable.js";
|
|
11
|
+
export { default as iteratorAsyncToReadable } from "./iteratorAsyncToReadable.js";
|
|
9
12
|
export { default as parseHeaders } from "./parseHeaders.js";
|
|
10
13
|
export { default as toWeb } from "./toWeb.js";
|
|
11
14
|
export { default as types } from "./types.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export default function iteratorAsyncToReadable(asyncGenerator) {
|
|
2
|
+
return new ReadableStream({
|
|
3
|
+
async pull(controller) {
|
|
4
|
+
try {
|
|
5
|
+
const { value, done } = await asyncGenerator.next();
|
|
6
|
+
if (done) {
|
|
7
|
+
controller.close();
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
controller.enqueue(new TextEncoder().encode(value));
|
|
11
|
+
} catch (err) {
|
|
12
|
+
console.error("Stream error:", err);
|
|
13
|
+
controller.error(err);
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
cancel() {
|
|
17
|
+
console.log("Stream cancelled");
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -17,7 +17,21 @@ type Store = {
|
|
|
17
17
|
) => Promise<any>;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
type Bucket =
|
|
20
|
+
type Bucket =
|
|
21
|
+
| string
|
|
22
|
+
| {
|
|
23
|
+
read: (key: string) => ReadableStream;
|
|
24
|
+
write: (
|
|
25
|
+
key: string,
|
|
26
|
+
data:
|
|
27
|
+
| string
|
|
28
|
+
| NodeJS.ArrayBufferView
|
|
29
|
+
| Iterable<string | NodeJS.ArrayBufferView>
|
|
30
|
+
| AsyncIterable<string | NodeJS.ArrayBufferView>,
|
|
31
|
+
type: BufferEncoding,
|
|
32
|
+
) => Promise<Boolean>;
|
|
33
|
+
delete: (key: string) => Promise<Boolean>;
|
|
34
|
+
};
|
|
21
35
|
|
|
22
36
|
type Auth = {
|
|
23
37
|
type: "cookie" | "token";
|
package/src/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import createNodeContext from "./context/node.js";
|
|
|
5
5
|
import createWinterContext from "./context/winter.js";
|
|
6
6
|
import {
|
|
7
7
|
config,
|
|
8
|
+
createWebsocket,
|
|
8
9
|
getMachine,
|
|
9
10
|
handleRequest,
|
|
10
11
|
iterate,
|
|
@@ -31,9 +32,6 @@ export default function server(options = {}) {
|
|
|
31
32
|
this.opts = config(options);
|
|
32
33
|
this.platform = getMachine();
|
|
33
34
|
|
|
34
|
-
// TODO: find a way to remove this hack
|
|
35
|
-
this.extended = false;
|
|
36
|
-
|
|
37
35
|
// Skip "forbidden methods" https://fetch.spec.whatwg.org/#concept-method
|
|
38
36
|
this.handlers = {
|
|
39
37
|
socket: [],
|
|
@@ -46,27 +44,11 @@ export default function server(options = {}) {
|
|
|
46
44
|
options: [],
|
|
47
45
|
};
|
|
48
46
|
|
|
47
|
+
// Keep a reference of the currently connected sockets
|
|
49
48
|
this.sockets = [];
|
|
49
|
+
|
|
50
50
|
// Note: required by Bun
|
|
51
|
-
this.websocket =
|
|
52
|
-
message: async (socket, body) => {
|
|
53
|
-
this.handlers.socket
|
|
54
|
-
?.filter((s) => s[1] === "message")
|
|
55
|
-
?.map((s) => s[2]({ socket, sockets: this.sockets, body }));
|
|
56
|
-
},
|
|
57
|
-
open: (ws) => {
|
|
58
|
-
this.sockets.push(ws);
|
|
59
|
-
this.handlers.socket
|
|
60
|
-
?.filter((s) => s[1] === "open")
|
|
61
|
-
?.map((s) => s[2]({ socket, sockets: this.sockets, body }));
|
|
62
|
-
},
|
|
63
|
-
close: (ws) => {
|
|
64
|
-
this.sockets.splice(this.sockets.indexOf(ws), 1);
|
|
65
|
-
this.handlers.socket
|
|
66
|
-
?.filter((s) => s[1] === "close")
|
|
67
|
-
?.map((s) => s[2]({ socket, sockets: this.sockets, body }));
|
|
68
|
-
},
|
|
69
|
-
};
|
|
51
|
+
this.websocket = createWebsocket(this.sockets, this.handlers);
|
|
70
52
|
|
|
71
53
|
// Initialize it right away for Node.js
|
|
72
54
|
if (this.platform.runtime === "node") {
|
package/src/parseResponse.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
cors,
|
|
3
|
+
createId,
|
|
4
|
+
iteratorToReadable,
|
|
5
|
+
iteratorAsyncToReadable,
|
|
6
|
+
} from "./helpers/index.js";
|
|
2
7
|
import { json } from "./reply.js";
|
|
3
8
|
import ServerError from "./ServerError.js";
|
|
4
9
|
|
|
@@ -34,14 +39,25 @@ export default async function parseResponse(out, ctx) {
|
|
|
34
39
|
out = json(out);
|
|
35
40
|
}
|
|
36
41
|
|
|
42
|
+
// Sync and Async iterators
|
|
43
|
+
if (out[Symbol.iterator]) {
|
|
44
|
+
out = new Response(iteratorToReadable(out));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// The ReadableStream seems to be an asyncIterator, but we don't want to handle that yet
|
|
48
|
+
if (out[Symbol.asyncIterator] && !(out instanceof Response)) {
|
|
49
|
+
out = new Response(iteratorAsyncToReadable(out));
|
|
50
|
+
}
|
|
51
|
+
|
|
37
52
|
// The output from fetch(), create a copy of it into a new response
|
|
38
53
|
if (out instanceof Response && out.url && out.body) {
|
|
39
54
|
out = new Response(out.body, {
|
|
40
|
-
status:
|
|
55
|
+
status: out.status,
|
|
41
56
|
headers: out.headers,
|
|
42
57
|
});
|
|
58
|
+
|
|
59
|
+
// Compression not supported for streaming response, stripping header
|
|
43
60
|
if (/^(br|gzip)$/.test(out.headers.get("content-encoding"))) {
|
|
44
|
-
console.warn("Compression not yet supported for response");
|
|
45
61
|
out.headers.delete("content-encoding");
|
|
46
62
|
}
|
|
47
63
|
}
|
package/src/reply.js
CHANGED
|
@@ -76,11 +76,11 @@ Reply.prototype.redirect = function (Location) {
|
|
|
76
76
|
return this.headers({ Location }).status(302).send();
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
-
Reply.prototype.file = async function (path) {
|
|
79
|
+
Reply.prototype.file = async function (path, renderer = (data) => data) {
|
|
80
80
|
try {
|
|
81
81
|
const data = await fs.readFile(path);
|
|
82
82
|
const ext = path.split(".").pop();
|
|
83
|
-
return this.type(ext).send(data);
|
|
83
|
+
return this.type(ext).send(await renderer(data));
|
|
84
84
|
} catch (error) {
|
|
85
85
|
if (error.code === "ENOENT") {
|
|
86
86
|
return status(404).send();
|
|
@@ -89,14 +89,14 @@ Reply.prototype.file = async function (path) {
|
|
|
89
89
|
}
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
-
Reply.prototype.view = async function (path) {
|
|
92
|
+
Reply.prototype.view = async function (path, renderer = (data) => data) {
|
|
93
93
|
return async (ctx) => {
|
|
94
94
|
if (!ctx.options.views) {
|
|
95
95
|
throw new Error("Views not enabled");
|
|
96
96
|
}
|
|
97
97
|
const data = await ctx.options.views.read(path);
|
|
98
|
-
if (data) return this.
|
|
99
|
-
return this.
|
|
98
|
+
if (!data) return this.status(404).send();
|
|
99
|
+
return this.type(path.split(".").pop()).send(await renderer(data));
|
|
100
100
|
};
|
|
101
101
|
};
|
|
102
102
|
|