@server/next 0.20.42 → 0.20.44

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@server/next",
3
- "version": "0.20.42",
3
+ "version": "0.20.44",
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",
@@ -8,6 +8,14 @@
8
8
  "funding": "https://www.paypal.me/franciscopresencia/19",
9
9
  "author": "Francisco Presencia <public@francisco.io> (https://francisco.io/)",
10
10
  "license": "UNLICENSED",
11
+ "documentation": {
12
+ "title": "Server JS - A modern web server for Bun and Node.js",
13
+ "menu": {
14
+ "About": "/about",
15
+ "Documentation": "/documentation",
16
+ "Github": "https://github.com/franciscop/server-next"
17
+ }
18
+ },
11
19
  "scripts": {
12
20
  "start": "bun test --watch",
13
21
  "test": "bun test",
@@ -0,0 +1,27 @@
1
+ const entities = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" };
2
+ const encode = (str = "") => str.replace(/[&<>"]/g, (tag) => entities[tag]);
3
+
4
+ const SELFCLOSE = new Set(
5
+ "area,base,br,col,embed,hr,img,input,link,meta,source,track,wbr".split(","),
6
+ );
7
+
8
+ export const jsx = (tag, { children, ...props }) => {
9
+ if (typeof tag === "function") return tag({ children, ...props });
10
+
11
+ if (SELFCLOSE.has(tag)) return `<${tag} />`;
12
+ if (props.dangerouslySetInnerHTML)
13
+ children = props.dangerouslySetInnerHTML.__html;
14
+ if (!children) children = [];
15
+ if (typeof children === "string") children = [children];
16
+ children = (children || [])
17
+ .map((c) => (typeof c === "function" ? c() : encode(c)))
18
+ .join("");
19
+ if (!tag) return () => children;
20
+ const attrStr = Object.entries(props || {})
21
+ .filter(([k, v]) => !/on[A-Z]/.test(k) && typeof v !== "function")
22
+ .map(([k, v]) => `${encode(k)}="${encode(v)}"`)
23
+ .join(" ");
24
+ return () => `<${tag} ${attrStr}>${children}</${tag}>`;
25
+ };
26
+ export const jsxDEV = jsx;
27
+ export const Fragment = "";
package/src/index.js CHANGED
@@ -79,11 +79,11 @@ server.prototype.self = function () {
79
79
  // #region Runtimes
80
80
  // Node.js
81
81
  server.prototype.node = async function () {
82
+ const options = config(this.opts);
82
83
  const http = await import("http");
83
84
  http
84
85
  .createServer(async (request, response) => {
85
86
  try {
86
- const options = config(this.opts);
87
87
  const ctx = await createNodeContext(request, options, this);
88
88
  const out = await handleRequest(this.handlers, ctx);
89
89
 
@@ -100,7 +100,7 @@ server.prototype.node = async function () {
100
100
  response.end();
101
101
  }
102
102
  })
103
- .listen(this.opts.port);
103
+ .listen(options.port);
104
104
  };
105
105
 
106
106
  // Netlify
@@ -210,7 +210,7 @@ server.prototype.test = function () {
210
210
  options.headers.cookie = cookie;
211
211
  }
212
212
  const res = await this.fetch(
213
- new Request("http://localhost:3000" + path, options)
213
+ new Request("http://localhost:3000" + path, options),
214
214
  );
215
215
 
216
216
  const headers = parseHeaders(res.headers);
package/readme.md DELETED
@@ -1,275 +0,0 @@
1
- # Documentation
2
-
3
- > **⚠️ WIP** This is an **experimental library** right now!
4
-
5
- A fully-fledged web server for Bun and Node.js, with all the basics built-in:
6
-
7
- ```js
8
- import server from "@server/next";
9
-
10
- export default server(options)
11
- .get("/books", () => Book.list())
12
- .post("/books", BookSchema, (ctx) => {
13
- return Book.create(ctx.body).save();
14
- });
15
- ```
16
-
17
- It includes all the things you would expect from a modern Server framework, like routing, static file serving, body+file parsing, gzip+brotli, streaming, testing, error handling, websockets, etc.
18
-
19
- We also have integrations and adaptors for these:
20
-
21
- - KV Stores: in-memory, Redis, Consul, DynamoDB, [Level](https://github.com/Level/level).
22
- - Buckets: AWS S3, Cloudflare R2, Backblaze B2.
23
- - Validation libraries: [Zod](https://zod.dev/), [Joi](https://joi.dev/), [Yup](https://github.com/jquense/yup), [Validate](https://validatejs.org), etc.
24
- - Auth: JWT, Session, Cookies, Social login.
25
-
26
- ```js
27
- // How to test your server - index.test.js
28
- import app from "./";
29
- const api = app.test(); // Very convenient helper, AXIOS-like interface
30
-
31
- it("can retrieve the book list", async () => {
32
- const { data: books } = await api.get("/books/");
33
- expect(books[0]).toEqual({ id: 0, name: ... });
34
- });
35
- ```
36
-
37
- ## Getting started
38
-
39
- First install it:
40
-
41
- ```
42
- npm install @server/next
43
- yarn add @server/next
44
- bun install @server/next
45
- ```
46
-
47
- Now you can create your first simple server:
48
-
49
- ```js
50
- // index.js
51
- import server from "@server/next";
52
-
53
- export default server()
54
- .get("/", () => "Hello world")
55
- .post("/", (ctx) => {
56
- console.log(ctx.body);
57
- return 201;
58
- });
59
- ```
60
-
61
- Then run `node .` or `bun .` and open your browser on http://localhost:3000/ to see the message.
62
-
63
- There are some major configuration options that you might want to set up though, enumerated in the [Basic Usage](#basic-usage) and explained through the docs.
64
-
65
- ## Basic usage
66
-
67
- Now that you know how to create a barebones server, there are some important bits that you might want to update.
68
-
69
- `SECRET`: create an `.env` file (ignored in git with `.gitignore`) with the `SECRET=` and then a long, unique random secret. It will be used to sign and/or encrypt things as needed.
70
-
71
- `store`: almost anything you want to persist will need a KV store to do so. For dev you can use an in-memory or a file-based (easy for debugging!) store, but for production systems you would normally use something like Redis. Can be as easy as `const store = new Map();` for dev.
72
-
73
- > Note: `bucket` is still _not_ available
74
-
75
- `bucket`: if you want to accept user files you will need a place to persist them. By default they are put in the filesystem, but since most cloud providers are ephemeral, provide a `bucket` and it will be used to handle the files directly.
76
-
77
- An example of how that works in practice:
78
-
79
- ```js
80
- // index.js
81
- import server from "@server/next";
82
-
83
- import Bucket from "bucket/b2";
84
- import { createClient } from "redis";
85
-
86
- const bucket = Bucket("mybucketname", { id, key });
87
- const store = createClient("...").connect();
88
-
89
- export default server({ bucket, store })
90
- .get("/", () => "Hello world")
91
- .post("/", (ctx) => {
92
- console.log(ctx.body);
93
- return 201;
94
- });
95
- ```
96
-
97
- ## Guides
98
-
99
- ### Middleware
100
-
101
- ### Validation
102
-
103
- ### Stores
104
-
105
- ### File handling
106
-
107
- To manage files, you need to install and use the library [`bucket`](http://bucketjs.com/), which is a very thin wrapper for file management systems. It is also created by the makers of Server.js.
108
-
109
- The easiest and default is to set a folder in your filesystem:
110
-
111
- ```js
112
- import FileSystem from "bucket/fs";
113
-
114
- const uploads = FileSystem("./uploads");
115
-
116
- // All paths are relative to the CWD
117
- export default server({ uploads })
118
- .get("/", () => "Hello")
119
- .put("/users/:id", async (ctx) => {
120
- // This is the plain string as the file name, already in our FS
121
- const fileName = ctx.body.profile;
122
- // 'yOuZEdSsNLq8PgZyLhSz0Llh.jpg'
123
-
124
- // Convert it into a File instance
125
- const file = ctx.uploads.file(fileName);
126
-
127
- // Now we can use other methods if we want
128
- // .info(), .read(), .write(), .pipe(), .pipeTo(), etc
129
- const info = await file.info();
130
- // {
131
- // id: "yOuZEdSsNLq8PgZyLhSz0Llh.jpg",
132
- // type: "jpg",
133
- // size: 435435,
134
- // timestamp: "2024-08-07T14:26:37Z",
135
- // // Note: this can be customized providing the option "domain"
136
- // url: "file:///Users/me/my-project/uploads/yOuZEdSsNLq8PgZyLhSz0Llh.jpg",
137
- // }
138
-
139
- return 200;
140
- });
141
- ```
142
-
143
- To upload the files to a 3rd party system, you just need to use the corresponding `bucket` implementation (or write a thin compatibility layer). Let's see an example with Backblaze's B2:
144
-
145
- ```js
146
- import server from "@server/next";
147
- import Backblaze from "bucket/b2";
148
-
149
- const uploads = Backblaze("bucket-name", {
150
- id: process.env.BACKBLAZE_ID,
151
- key: process.env.BACKBLAZE_KEY,
152
- });
153
-
154
- export default server({ uploads })
155
- .put("/users/:id", async (ctx) => {
156
- const fileName = ctx.body.profile;
157
- // 'yOuZEdSsNLq8PgZyLhSz0Llh.jpg'
158
-
159
- // Convert it into a File instance
160
- const file = ctx.uploads.file(fileName);
161
-
162
- // Now we can use other methods if we want
163
- const info = await file.info();
164
- // {
165
- // id: "yOuZEdSsNLq8PgZyLhSz0Llh.jpg",
166
- // type: "jpg",
167
- // size: 435435,
168
- // timestamp: "2024-08-07T14:26:37Z",
169
- // url: "https://f???.backblazeb2.com/???/yOuZEdSsNLq8PgZyLhSz0Llh.jpg",
170
- // }
171
-
172
- return 200;
173
- });
174
- .;
175
- ```
176
-
177
- #### Example: resizing the user profile picture
178
-
179
- Let's see a complete example of uploading and resizing a user profile picture:
180
-
181
- ```js
182
- import server, { status } from "@server/next";
183
- import sharp from "sharp";
184
- import FileSystem from "bucket/fs";
185
-
186
- const uploads = FileSystem("./uploads");
187
-
188
- // All paths are relative to the CWD
189
- export default server({ uploads })
190
- .get("/", () => "Hello")
191
- .put("/users/:id", async (ctx) => {
192
- // Create the instance of the file to read and write
193
- const src = ctx.uploads.file(ctx.body.profile);
194
- const dst = ctx.uploads.file("/profile/" + ctx.url.params.id + ".jpg");
195
-
196
- const ext = src.id.split(".").pop();
197
- if (!["jpg", "jpeg", "png", "webp", "avif"].includes(ext)) {
198
- await src.remove(); // Don't store it
199
- return status(400).json({ error: "Invalid file format" });
200
- }
201
-
202
- // Create the Readable, Transform and Writable Node streams
203
- await pipeline(
204
- src.readable("node"),
205
- sharp().resize(200, 200).jpg(),
206
- dst.writable("node")
207
- );
208
-
209
- // We no longer need the original file
210
- await src.remove();
211
-
212
- return status(200).json({ updated: true });
213
- });
214
- ```
215
-
216
- Note that the option `uploads` gets converted into a `Bucket` instance and passed as `ctx.uploads`. Th
217
-
218
- ## Options
219
-
220
- Options docs here
221
-
222
- ## Router
223
-
224
- Router docs here
225
-
226
- ## Context
227
-
228
- Context docs here
229
-
230
- ## Reply
231
-
232
- Reply docs here
233
-
234
- ## Runtimes
235
-
236
- There are many runtimes where Server works! We put a lot of work to make sure it works the same way with minimal changes in them, this includes:
237
-
238
- - Node.js of course, including everywhere that Node is supported (VPS, Heroku, Render, etc).
239
- - Bun, and it will be even faster!
240
- - Cloudflare Worker
241
- - Netlify Function + Edge function
242
-
243
- ## FAQ
244
-
245
- #### How is it different from Hono?
246
-
247
- Server.js attempts to run your code unmodified in all runtimes. With Hono, despite the claims in their homepage, you need to change the code for different runtimes:
248
-
249
- ```js
250
- // Server.js code for Node.js, Bun and Netlify
251
- import server from "@server/next";
252
- export default server().get("/", () => "Hello server!");
253
- ```
254
-
255
- ```js
256
- // Hono code for Node.js
257
- import { serve } from '@hono/node-server'
258
- import { Hono } from 'hono'
259
- const app = new Hono()
260
- app.get('/', (c) => c.text('Hello Node.js!'))
261
- serve(app)
262
-
263
- // Hono code for Bun
264
- import { Hono } from 'hono'
265
- const app = new Hono()
266
- app.get('/', (c) => c.text('Hello Bun!'))
267
- export default app
268
-
269
- // Hono code for Netlify
270
- import { Hono } from 'jsr:@hono/hono'
271
- import { handle } from 'jsr:@hono/hono/netlify'
272
- const app = new Hono()
273
- app.get('/', (c) => c.text('Hello Hono!'))
274
- export default handle(app)
275
- ```