primate 0.15.0 → 0.15.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/README.md CHANGED
@@ -4,16 +4,20 @@ Expressive, minimal and extensible framework for JavaScript.
4
4
 
5
5
  ## Getting started
6
6
 
7
- Create a route in `routes/hello.js`
7
+ Run `npx -y primate@latest create` to create a project structure.
8
+
9
+ Create a route in `routes/index.js`
8
10
 
9
11
  ```js
10
- export default router => {
11
- router.get("/", () => "Hello, world!");
12
+ export default {
13
+ get() {
14
+ return "Hello, world!";
15
+ },
12
16
  };
13
17
 
14
18
  ```
15
19
 
16
- Add `{"type": "module"}` to your `package.json` and run `npx -y primate@latest`.
20
+ Run `npm i && npm start` and visit `localhost:6161` in your browser.
17
21
 
18
22
  ## Table of Contents
19
23
 
@@ -27,22 +31,22 @@ Add `{"type": "module"}` to your `package.json` and run `npx -y primate@latest`.
27
31
  - [Basic](#basic)
28
32
  - [The request object](#the-request-object)
29
33
  - [Accessing the request body](#accessing-the-request-body)
30
- - [Regular expressions](#regular-expressions)
31
- - [Named groups](#named-groups)
32
- - [Aliasing](#aliasing)
33
- - [Sharing logic across requests](#sharing-logic-across-requests)
34
+ - [Parameterized routes](#parameterized-routes)
34
35
  - [Explicit handlers](#explicit-handlers)
35
36
 
36
37
  ## Serving content
37
38
 
38
- Create a file in `routes` that exports a default function.
39
+ Create a file in `routes/index.js` to handle the special `/` route.
39
40
 
40
41
  ### Plain text
41
42
 
42
43
  ```js
43
- export default router => {
44
- // Serve strings as plain text
45
- router.get("/user", () => "Donald");
44
+ // routes/index.js handles the `/` route
45
+ export default {
46
+ get() {
47
+ // strings are served as plain text
48
+ return "Donald";
49
+ },
46
50
  };
47
51
 
48
52
  ```
@@ -50,17 +54,15 @@ export default router => {
50
54
  ### JSON
51
55
 
52
56
  ```js
53
- import {File} from "runtime-compat/filesystem";
54
-
55
- export default router => {
56
- // Serve proper JavaScript objects as JSON
57
- router.get("/users", () => [
58
- {name: "Donald"},
59
- {name: "Ryan"},
60
- ]);
61
-
62
- // Load from file and serve as JSON
63
- router.get("/users-from-file", () => File.json("users.json"));
57
+ // routes/index.js handles the `/` route
58
+ export default {
59
+ get() {
60
+ // proper JavaScript objects are served as JSON
61
+ return [
62
+ {name: "Donald"},
63
+ {name: "Ryan"},
64
+ ];
65
+ },
64
66
  };
65
67
 
66
68
  ```
@@ -70,9 +72,12 @@ export default router => {
70
72
  ```js
71
73
  import {File} from "runtime-compat/filesystem";
72
74
 
73
- export default router => {
74
- // `File` implements `readable`, which is a `ReadableStream`
75
- router.get("/users", () => new File("users.json"));
75
+ // routes/index.js handles the `/` route
76
+ export default {
77
+ get() {
78
+ // ReadableStream or Blob objects are streamed to the client
79
+ return new File("users.json");
80
+ },
76
81
  };
77
82
 
78
83
  ```
@@ -82,9 +87,12 @@ export default router => {
82
87
  ```js
83
88
  import {Response} from "runtime-compat/http";
84
89
 
85
- export default router => {
86
- // Use a Response object for custom response status
87
- router.get("/create", () => new Response("created!", {status: 201}));
90
+ // routes/index.js handles the `/` route
91
+ export default {
92
+ get() {
93
+ // use a Response object for custom response status
94
+ return new Response("created!", {status: 201});
95
+ },
88
96
  };
89
97
 
90
98
  ```
@@ -92,31 +100,43 @@ export default router => {
92
100
  ### HTML
93
101
 
94
102
  ```js
95
- // Use an explicit handler as we can't detect HTML by the return value type
96
- export default (router, {html}) => {
97
- // Embed components/hello-world.html into static/index.html and serve it. In
98
- // case a user-provided index.html doesn't exist, use a fallback index.html
99
- router.get("/hello", () => html("hello-world"));
100
-
101
- // Same as above, but without embedding
102
- router.get("/hello-partial", () => html("hello-world", {partial: true}));
103
-
104
- // Serve directly from string instead of loading a component
105
- router.get("/hello-adhoc", () => html("<p>Hello, world!</p>", {adhoc: true}));
103
+ import {html} from "primate";
104
+
105
+ // routes/index.js handles the `/` route
106
+ export default {
107
+ get() {
108
+ // to serve HTML, import and use the html handler
109
+ return html("<p>Hello, world!</p>");
110
+ },
106
111
  };
107
112
 
108
113
  ```
109
114
 
110
115
  ## Routing
111
116
 
112
- Routes map requests to responses. They are loaded from `routes`.
117
+ Primate uses filesystem-based routes. Every path a client accesses is mapped to
118
+ a route under `routes`.
119
+
120
+ * `index.js` handles the root route (`/`)
121
+ * `post.js` handles the `/post` route
122
+ * `post/{postId}.js` handles a parameterized route where `{postId}` can
123
+ be mapped to anything, such as `/post/1`
113
124
 
114
125
  ### Basic
115
126
 
116
127
  ```js
117
- export default router => {
118
- // accessing /site/login will serve `Hello, world!` as plain text
119
- router.get("/site/login", () => "Hello, world!");
128
+ import {redirect} from "primate";
129
+
130
+ // routes/site/login.js handles the `/site/login` route
131
+ export default {
132
+ get() {
133
+ // strings are served as plain text
134
+ return "Hello, world!";
135
+ },
136
+ // other HTTP verbs are also available
137
+ post() {
138
+ return redirect("/");
139
+ },
120
140
  };
121
141
 
122
142
  ```
@@ -124,9 +144,12 @@ export default router => {
124
144
  ### The request object
125
145
 
126
146
  ```js
127
- export default router => {
128
- // accessing /site/login will serve `["site", "login"]` as JSON
129
- router.get("/site/login", request => request.path);
147
+ // routes/site/login.js handles the `/site/login` route
148
+ export default {
149
+ get(request) {
150
+ // will serve `["site", "login"]` as JSON
151
+ return request.path;
152
+ },
130
153
  };
131
154
 
132
155
  ```
@@ -139,85 +162,41 @@ to the content type sent along the request. Currently supported are
139
162
  `application/json`.
140
163
 
141
164
  ```js
142
- export default router => {
143
- router.post("/site/login", ({body}) => `submitted user: ${body.username}`);
144
- };
145
-
146
- ```
147
-
148
- ### Regular expressions
149
-
150
- ```js
151
- export default router => {
152
- // accessing /user/view/1234 will serve `1234` as plain text
153
- // accessing /user/view/abcd will show a 404 error
154
- router.get("/user/view/([0-9])+", request => request[2]);
155
- };
156
-
157
- ```
158
-
159
- ### Named groups
160
-
161
- ```js
162
- export default router => {
163
- // named groups are mapped to properties of `request.named`
164
- // accessing /user/view/1234 will serve `1234` as plain text
165
- router.get("/user/view/(?<_id>[0-9])+", ({named}) => named._id);
166
- };
167
-
168
- ```
169
-
170
- ### Aliasing
171
-
172
- ```js
173
- export default router => {
174
- // will replace `"_id"` in any path with `"([0-9])+"`
175
- router.alias("_id", "([0-9])+");
176
-
177
- // equivalent to `router.get("/user/view/([0-9])+", ...)`
178
- // will return id if matched, 404 otherwise
179
- router.get("/user/view/_id", request => request.path[2]);
180
-
181
- // can be combined with named groups
182
- router.alias("_name", "(?<name>[a-z])+");
183
-
184
- // will return name if matched, 404 otherwise
185
- router.get("/user/view/_name", request => request.named.name);
165
+ // routes/site/login.js handles the `/site/login` route
166
+ export default {
167
+ get(request) {
168
+ return `username submitted: ${request.body.username}`;
169
+ },
186
170
  };
187
171
 
188
172
  ```
189
173
 
190
- ### Sharing logic across requests
174
+ ### Parameterized routes
191
175
 
192
176
  ```js
193
- export default router => {
194
- // Declare `"edit-user"` as alias of `"/user/edit/([0-9])+"`
195
- router.alias("edit-user", "/user/edit/([0-9])+");
196
-
197
- // Pass user instead of request to all verbs on this route
198
- router.map("edit-user", ({body}) => body?.name ?? "Donald");
199
-
200
- // Show user as plain text
201
- router.get("edit-user", user => user);
202
-
203
- // Verify or show error
204
- router.post("edit-user", user => user === "Donald"
205
- ? "Hi Donald!"
206
- : {message: "Error saving user"});
177
+ // routes/user/{userId}.js handles all routes of the sort `/user/{userId}`
178
+ // where {userId} can be anything
179
+ export default {
180
+ get(request) {
181
+ return `user id: ${request.named.userId}`;
182
+ },
207
183
  };
208
184
 
209
185
  ```
210
186
 
211
187
  ### Explicit handlers
212
188
 
213
- Most often we can figure out the content type to respond with based on the
214
- return type from the handler. To handle content not automatically detected, use
215
- the second argument of the exported function.
189
+ Often we can figure out the content type to respond with based on the return
190
+ type from the handler. For other cases, we need to use an explicit handler.
216
191
 
217
192
  ```js
218
- export default (router, {redirect}) => {
219
- // redirect from source to target
220
- router.get("/source", () => redirect("/target"));
193
+ import {redirect} from "primate";
194
+
195
+ // routes/source.js handles the `/source` route
196
+ export default {
197
+ get() {
198
+ return redirect("/target");
199
+ },
221
200
  };
222
201
 
223
202
  ```
package/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primate",
3
- "version": "0.13.0",
3
+ "version": "0.15.1",
4
4
  "description": "Expressive, minimal and extensible framework for JavaScript",
5
5
  "homepage": "https://primatejs.com",
6
6
  "bugs": "https://github.com/primatejs/primate/issues",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primate",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "description": "Expressive, minimal and extensible framework for JavaScript",
5
5
  "homepage": "https://primatejs.com",
6
6
  "bugs": "https://github.com/primatejs/primate/issues",
@@ -1,3 +1,5 @@
1
- export default router => {
2
- router.get("/", () => "Hello, world!");
1
+ export default {
2
+ get() {
3
+ return "Hello, world!";
4
+ },
3
5
  };
@@ -1,3 +1,6 @@
1
- export default router => {
2
- router.post("/site/login", ({body}) => `submitted user: ${body.username}`);
1
+ // routes/site/login.js handles the `/site/login` route
2
+ export default {
3
+ get(request) {
4
+ return `username submitted: ${request.body.username}`;
5
+ },
3
6
  };
@@ -1,4 +1,13 @@
1
- export default router => {
2
- // accessing /site/login will serve `Hello, world!` as plain text
3
- router.get("/site/login", () => "Hello, world!");
1
+ import {redirect} from "primate";
2
+
3
+ // routes/site/login.js handles the `/site/login` route
4
+ export default {
5
+ get() {
6
+ // strings are served as plain text
7
+ return "Hello, world!";
8
+ },
9
+ // other HTTP verbs are also available
10
+ post() {
11
+ return redirect("/");
12
+ },
4
13
  };
@@ -1,4 +1,8 @@
1
- export default (router, {redirect}) => {
2
- // redirect from source to target
3
- router.get("/source", () => redirect("/target"));
1
+ import {redirect} from "primate";
2
+
3
+ // routes/source.js handles the `/source` route
4
+ export default {
5
+ get() {
6
+ return redirect("/target");
7
+ },
4
8
  };
@@ -0,0 +1,7 @@
1
+ // routes/user/{userId}.js handles all routes of the sort `/user/{userId}`
2
+ // where {userId} can be anything
3
+ export default {
4
+ get(request) {
5
+ return `user id: ${request.named.userId}`;
6
+ },
7
+ };
@@ -1,4 +1,7 @@
1
- export default router => {
2
- // accessing /site/login will serve `["site", "login"]` as JSON
3
- router.get("/site/login", request => request.path);
1
+ // routes/site/login.js handles the `/site/login` route
2
+ export default {
3
+ get(request) {
4
+ // will serve `["site", "login"]` as JSON
5
+ return request.path;
6
+ },
4
7
  };
@@ -1,12 +1,9 @@
1
- // Use an explicit handler as we can't detect HTML by the return value type
2
- export default (router, {html}) => {
3
- // Embed components/hello-world.html into static/index.html and serve it. In
4
- // case a user-provided index.html doesn't exist, use a fallback index.html
5
- router.get("/hello", () => html("hello-world"));
1
+ import {html} from "primate";
6
2
 
7
- // Same as above, but without embedding
8
- router.get("/hello-partial", () => html("hello-world", {partial: true}));
9
-
10
- // Serve directly from string instead of loading a component
11
- router.get("/hello-adhoc", () => html("<p>Hello, world!</p>", {adhoc: true}));
3
+ // routes/index.js handles the `/` route
4
+ export default {
5
+ get() {
6
+ // to serve HTML, import and use the html handler
7
+ return html("<p>Hello, world!</p>");
8
+ },
12
9
  };
@@ -1,12 +1,10 @@
1
- import {File} from "runtime-compat/filesystem";
2
-
3
- export default router => {
4
- // Serve proper JavaScript objects as JSON
5
- router.get("/users", () => [
6
- {name: "Donald"},
7
- {name: "Ryan"},
8
- ]);
9
-
10
- // Load from file and serve as JSON
11
- router.get("/users-from-file", () => File.json("users.json"));
1
+ // routes/index.js handles the `/` route
2
+ export default {
3
+ get() {
4
+ // proper JavaScript objects are served as JSON
5
+ return [
6
+ {name: "Donald"},
7
+ {name: "Ryan"},
8
+ ];
9
+ },
12
10
  };
@@ -1,4 +1,7 @@
1
- export default router => {
2
- // Serve strings as plain text
3
- router.get("/user", () => "Donald");
1
+ // routes/index.js handles the `/` route
2
+ export default {
3
+ get() {
4
+ // strings are served as plain text
5
+ return "Donald";
6
+ },
4
7
  };
@@ -1,6 +1,9 @@
1
1
  import {Response} from "runtime-compat/http";
2
2
 
3
- export default router => {
4
- // Use a Response object for custom response status
5
- router.get("/create", () => new Response("created!", {status: 201}));
3
+ // routes/index.js handles the `/` route
4
+ export default {
5
+ get() {
6
+ // use a Response object for custom response status
7
+ return new Response("created!", {status: 201});
8
+ },
6
9
  };
@@ -1,6 +1,9 @@
1
1
  import {File} from "runtime-compat/filesystem";
2
2
 
3
- export default router => {
4
- // `File` implements `readable`, which is a `ReadableStream`
5
- router.get("/users", () => new File("users.json"));
3
+ // routes/index.js handles the `/` route
4
+ export default {
5
+ get() {
6
+ // ReadableStream or Blob objects are streamed to the client
7
+ return new File("users.json");
8
+ },
6
9
  };
@@ -4,13 +4,15 @@ Expressive, minimal and extensible framework for JavaScript.
4
4
 
5
5
  ## Getting started
6
6
 
7
- Create a route in `routes/hello.js`
7
+ Run `npx -y primate@latest create` to create a project structure.
8
+
9
+ Create a route in `routes/index.js`
8
10
 
9
11
  ```js
10
12
  // getting-started.js
11
13
  ```
12
14
 
13
- Add `{"type": "module"}` to your `package.json` and run `npx -y primate@latest`.
15
+ Run `npm i && npm start` and visit `localhost:6161` in your browser.
14
16
 
15
17
  ## Table of Contents
16
18
 
@@ -24,15 +26,12 @@ Add `{"type": "module"}` to your `package.json` and run `npx -y primate@latest`.
24
26
  - [Basic](#basic)
25
27
  - [The request object](#the-request-object)
26
28
  - [Accessing the request body](#accessing-the-request-body)
27
- - [Regular expressions](#regular-expressions)
28
- - [Named groups](#named-groups)
29
- - [Aliasing](#aliasing)
30
- - [Sharing logic across requests](#sharing-logic-across-requests)
29
+ - [Parameterized routes](#parameterized-routes)
31
30
  - [Explicit handlers](#explicit-handlers)
32
31
 
33
32
  ## Serving content
34
33
 
35
- Create a file in `routes` that exports a default function.
34
+ Create a file in `routes/index.js` to handle the special `/` route.
36
35
 
37
36
  ### Plain text
38
37
 
@@ -66,7 +65,13 @@ Create a file in `routes` that exports a default function.
66
65
 
67
66
  ## Routing
68
67
 
69
- Routes map requests to responses. They are loaded from `routes`.
68
+ Primate uses filesystem-based routes. Every path a client accesses is mapped to
69
+ a route under `routes`.
70
+
71
+ * `index.js` handles the root route (`/`)
72
+ * `post.js` handles the `/post` route
73
+ * `post/{postId}.js` handles a parameterized route where `{postId}` can
74
+ be mapped to anything, such as `/post/1`
70
75
 
71
76
  ### Basic
72
77
 
@@ -91,35 +96,16 @@ to the content type sent along the request. Currently supported are
91
96
  // routing/accessing-the-request-body.js
92
97
  ```
93
98
 
94
- ### Regular expressions
95
-
96
- ```js
97
- // routing/regular-expressions.js
98
- ```
99
-
100
- ### Named groups
101
-
102
- ```js
103
- // routing/named-groups.js
104
- ```
105
-
106
- ### Aliasing
107
-
108
- ```js
109
- // routing/aliasing.js
110
- ```
111
-
112
- ### Sharing logic across requests
99
+ ### Parameterized routes
113
100
 
114
101
  ```js
115
- // routing/sharing-logic-across-requests.js
102
+ // routing/parameterized-routes.js
116
103
  ```
117
104
 
118
105
  ### Explicit handlers
119
106
 
120
- Most often we can figure out the content type to respond with based on the
121
- return type from the handler. To handle content not automatically detected, use
122
- the second argument of the exported function.
107
+ Often we can figure out the content type to respond with based on the return
108
+ type from the handler. For other cases, we need to use an explicit handler.
123
109
 
124
110
  ```js
125
111
  // routing/explicit-handlers.js
@@ -1,14 +0,0 @@
1
- export default router => {
2
- // will replace `"_id"` in any path with `"([0-9])+"`
3
- router.alias("_id", "([0-9])+");
4
-
5
- // equivalent to `router.get("/user/view/([0-9])+", ...)`
6
- // will return id if matched, 404 otherwise
7
- router.get("/user/view/_id", request => request.path[2]);
8
-
9
- // can be combined with named groups
10
- router.alias("_name", "(?<name>[a-z])+");
11
-
12
- // will return name if matched, 404 otherwise
13
- router.get("/user/view/_name", request => request.named.name);
14
- };
@@ -1,5 +0,0 @@
1
- export default router => {
2
- // named groups are mapped to properties of `request.named`
3
- // accessing /user/view/1234 will serve `1234` as plain text
4
- router.get("/user/view/(?<_id>[0-9])+", ({named}) => named._id);
5
- };
@@ -1,5 +0,0 @@
1
- export default router => {
2
- // accessing /user/view/1234 will serve `1234` as plain text
3
- // accessing /user/view/abcd will show a 404 error
4
- router.get("/user/view/([0-9])+", request => request[2]);
5
- };
@@ -1,15 +0,0 @@
1
- export default router => {
2
- // Declare `"edit-user"` as alias of `"/user/edit/([0-9])+"`
3
- router.alias("edit-user", "/user/edit/([0-9])+");
4
-
5
- // Pass user instead of request to all verbs on this route
6
- router.map("edit-user", ({body}) => body?.name ?? "Donald");
7
-
8
- // Show user as plain text
9
- router.get("edit-user", user => user);
10
-
11
- // Verify or show error
12
- router.post("edit-user", user => user === "Donald"
13
- ? "Hi Donald!"
14
- : {message: "Error saving user"});
15
- };