primate 0.9.2 → 0.10.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/README.md +29 -13
- package/README.template.md +32 -16
- package/bin/primate.js +1 -1
- package/package.json +4 -4
- package/readme/components/edit-user-for.html +10 -0
- package/readme/components/edit-user.html +10 -0
- package/readme/components/user-index.html +4 -0
- package/readme/components/users.html +4 -0
- package/readme/domains/fields.js +12 -0
- package/readme/domains/predicates.js +14 -0
- package/readme/domains/short-field-notation.js +13 -0
- package/readme/getting-started/generate-ssl.sh +1 -0
- package/readme/getting-started/hello.js +3 -0
- package/readme/getting-started/lay-out-app.sh +1 -0
- package/readme/getting-started/site-index.html +1 -0
- package/readme/getting-started/site.js +3 -0
- package/readme/routing/aliasing.js +14 -0
- package/readme/routing/basic.js +7 -0
- package/readme/routing/named-groups.js +5 -0
- package/readme/routing/regular-expressions.js +5 -0
- package/readme/routing/sharing-logic-across-requests.js +18 -0
- package/readme/routing/the-request-object.js +4 -0
- package/readme/serving-content/html.js +13 -0
- package/readme/serving-content/json.js +12 -0
- package/readme/serving-content/plain-text.js +4 -0
- package/readme/serving-content/streams.js +6 -0
- package/readme/serving-content/user-index.html +4 -0
- package/src/bundle.js +5 -5
- package/src/conf.js +11 -9
- package/src/handlers/http404.js +1 -1
- package/src/handlers/json.js +1 -1
- package/src/handlers/stream.js +1 -1
- package/src/handlers/text.js +2 -2
- package/src/preset/primate.js +21 -0
- package/src/route.js +5 -2
- package/src/run.js +4 -0
- package/src/serve.js +71 -67
- package/TODO +0 -4
- package/src/preset/primate.json +0 -25
- /package/src/{http-codes.json → http-statuses.json} +0 -0
package/README.md
CHANGED
|
@@ -15,6 +15,24 @@ export default router => {
|
|
|
15
15
|
|
|
16
16
|
Add `{"type": "module"}` to your `package.json` and run `npx primate`.
|
|
17
17
|
|
|
18
|
+
## Table of Contents
|
|
19
|
+
|
|
20
|
+
- [Serving content](#serving-content)
|
|
21
|
+
- [Plain text](#plain-text)
|
|
22
|
+
- [JSON](#json)
|
|
23
|
+
- [Streams](#streams)
|
|
24
|
+
- [HTML](#html)
|
|
25
|
+
- [Routing](#routing)
|
|
26
|
+
- [Basic](#basic)
|
|
27
|
+
- [The request object](#the-request-object)
|
|
28
|
+
- [Regular expressions](#regular-expressions)
|
|
29
|
+
- [Named groups](#named-groups)
|
|
30
|
+
- [Aliasing](#aliasing)
|
|
31
|
+
- [Sharing logic across requests](#sharing-logic-across-requests)
|
|
32
|
+
- [Data persistance](#data-persistance)
|
|
33
|
+
- [Short field notation](#short-field-notation)
|
|
34
|
+
- [Predicates](#predicates)
|
|
35
|
+
|
|
18
36
|
## Serving content
|
|
19
37
|
|
|
20
38
|
Create a file in `routes` that exports a default function
|
|
@@ -97,7 +115,7 @@ Routes map requests to responses. They are loaded from `routes`.
|
|
|
97
115
|
The order in which routes are declared is irrelevant. Redeclaring a route
|
|
98
116
|
(same pathname and same HTTP verb) throws an error.
|
|
99
117
|
|
|
100
|
-
### Basic
|
|
118
|
+
### Basic
|
|
101
119
|
|
|
102
120
|
```js
|
|
103
121
|
import html from "@primate/html";
|
|
@@ -110,7 +128,7 @@ export default router => {
|
|
|
110
128
|
|
|
111
129
|
```
|
|
112
130
|
|
|
113
|
-
###
|
|
131
|
+
### The request object
|
|
114
132
|
|
|
115
133
|
```js
|
|
116
134
|
export default router => {
|
|
@@ -162,7 +180,7 @@ export default router => {
|
|
|
162
180
|
|
|
163
181
|
```
|
|
164
182
|
|
|
165
|
-
### Sharing logic across
|
|
183
|
+
### Sharing logic across requests
|
|
166
184
|
|
|
167
185
|
```js
|
|
168
186
|
import html from "@primate/html";
|
|
@@ -186,19 +204,15 @@ export default router => {
|
|
|
186
204
|
|
|
187
205
|
```
|
|
188
206
|
|
|
189
|
-
##
|
|
190
|
-
|
|
191
|
-
Domains represent a collection in a store, primarily with the class `fields`
|
|
192
|
-
property.
|
|
207
|
+
## Data persistance
|
|
193
208
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
Field types delimit acceptable values for a field.
|
|
209
|
+
Primate domains (via [`@primate/domains`][primate-domains]) represent a
|
|
210
|
+
collection in a store using the class `fields` property.
|
|
197
211
|
|
|
198
212
|
```js
|
|
199
213
|
import {Domain} from "@primate/domains";
|
|
200
214
|
|
|
201
|
-
// A basic domain that contains two
|
|
215
|
+
// A basic domain that contains two properies
|
|
202
216
|
export default class User extends Domain {
|
|
203
217
|
static fields = {
|
|
204
218
|
// a user's name must be a string
|
|
@@ -211,9 +225,9 @@ export default class User extends Domain {
|
|
|
211
225
|
|
|
212
226
|
```
|
|
213
227
|
|
|
214
|
-
### Short notation
|
|
228
|
+
### Short field notation
|
|
215
229
|
|
|
216
|
-
|
|
230
|
+
Value types may be any constructible JavaScript object, including other
|
|
217
231
|
domains. When using other domains as types, data integrity (on saving) is
|
|
218
232
|
ensured.
|
|
219
233
|
|
|
@@ -265,3 +279,5 @@ export default class User extends Domain {
|
|
|
265
279
|
## License
|
|
266
280
|
|
|
267
281
|
MIT
|
|
282
|
+
|
|
283
|
+
[primate-domains]: https://github.com/primatejs/primate-domains
|
package/README.template.md
CHANGED
|
@@ -12,6 +12,24 @@ Create a route in `routes/hello.js`
|
|
|
12
12
|
|
|
13
13
|
Add `{"type": "module"}` to your `package.json` and run `npx primate`.
|
|
14
14
|
|
|
15
|
+
## Table of Contents
|
|
16
|
+
|
|
17
|
+
- [Serving content](#serving-content)
|
|
18
|
+
- [Plain text](#plain-text)
|
|
19
|
+
- [JSON](#json)
|
|
20
|
+
- [Streams](#streams)
|
|
21
|
+
- [HTML](#html)
|
|
22
|
+
- [Routing](#routing)
|
|
23
|
+
- [Basic](#basic)
|
|
24
|
+
- [The request object](#the-request-object)
|
|
25
|
+
- [Regular expressions](#regular-expressions)
|
|
26
|
+
- [Named groups](#named-groups)
|
|
27
|
+
- [Aliasing](#aliasing)
|
|
28
|
+
- [Sharing logic across requests](#sharing-logic-across-requests)
|
|
29
|
+
- [Data persistance](#data-persistance)
|
|
30
|
+
- [Short field notation](#short-field-notation)
|
|
31
|
+
- [Predicates](#predicates)
|
|
32
|
+
|
|
15
33
|
## Serving content
|
|
16
34
|
|
|
17
35
|
Create a file in `routes` that exports a default function
|
|
@@ -55,16 +73,16 @@ Routes map requests to responses. They are loaded from `routes`.
|
|
|
55
73
|
The order in which routes are declared is irrelevant. Redeclaring a route
|
|
56
74
|
(same pathname and same HTTP verb) throws an error.
|
|
57
75
|
|
|
58
|
-
### Basic
|
|
76
|
+
### Basic
|
|
59
77
|
|
|
60
78
|
```js
|
|
61
|
-
// routing/basic
|
|
79
|
+
// routing/basic.js
|
|
62
80
|
```
|
|
63
81
|
|
|
64
|
-
###
|
|
82
|
+
### The request object
|
|
65
83
|
|
|
66
84
|
```js
|
|
67
|
-
// routing/
|
|
85
|
+
// routing/the-request-object.js
|
|
68
86
|
```
|
|
69
87
|
|
|
70
88
|
### Regular expressions
|
|
@@ -85,33 +103,29 @@ The order in which routes are declared is irrelevant. Redeclaring a route
|
|
|
85
103
|
// routing/aliasing.js
|
|
86
104
|
```
|
|
87
105
|
|
|
88
|
-
### Sharing logic across
|
|
106
|
+
### Sharing logic across requests
|
|
89
107
|
|
|
90
108
|
```js
|
|
91
|
-
// routing/sharing-logic-across-
|
|
109
|
+
// routing/sharing-logic-across-requests.js
|
|
92
110
|
```
|
|
93
111
|
|
|
94
|
-
##
|
|
95
|
-
|
|
96
|
-
Domains represent a collection in a store, primarily with the class `fields`
|
|
97
|
-
property.
|
|
112
|
+
## Data persistance
|
|
98
113
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
Field types delimit acceptable values for a field.
|
|
114
|
+
Primate domains (via [`@primate/domains`][primate-domains]) represent a
|
|
115
|
+
collection in a store using the class `fields` property.
|
|
102
116
|
|
|
103
117
|
```js
|
|
104
118
|
// domains/fields.js
|
|
105
119
|
```
|
|
106
120
|
|
|
107
|
-
### Short notation
|
|
121
|
+
### Short field notation
|
|
108
122
|
|
|
109
|
-
|
|
123
|
+
Value types may be any constructible JavaScript object, including other
|
|
110
124
|
domains. When using other domains as types, data integrity (on saving) is
|
|
111
125
|
ensured.
|
|
112
126
|
|
|
113
127
|
```js
|
|
114
|
-
// domains/short-notation.js
|
|
128
|
+
// domains/short-field-notation.js
|
|
115
129
|
```
|
|
116
130
|
|
|
117
131
|
### Predicates
|
|
@@ -131,3 +145,5 @@ aside from the type.
|
|
|
131
145
|
## License
|
|
132
146
|
|
|
133
147
|
MIT
|
|
148
|
+
|
|
149
|
+
[primate-domains]: https://github.com/primatejs/primate-domains
|
package/bin/primate.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"author": "Terrablue <terrablue@proton.me>",
|
|
5
5
|
"homepage": "https://primatejs.com",
|
|
6
6
|
"bugs": "https://github.com/primatejs/primate/issues",
|
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
},
|
|
13
13
|
"bin": "bin/primate.js",
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"@babel/core": "^7.
|
|
15
|
+
"@babel/core": "^7.21.0",
|
|
16
16
|
"@babel/eslint-parser": "^7.19.1",
|
|
17
17
|
"@babel/plugin-syntax-import-assertions": "^7.20.0",
|
|
18
|
-
"eslint": "^8.
|
|
18
|
+
"eslint": "^8.34.0",
|
|
19
19
|
"eslint-plugin-json": "^3.1.0"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
|
-
"docs": "npx embedme --source-root
|
|
22
|
+
"docs": "npx -y embedme --source-root readme --strip-embed-comment --stdout README.template.md > README.md"
|
|
23
23
|
},
|
|
24
24
|
"type": "module",
|
|
25
25
|
"exports": "./exports.js",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {Domain} from "@primate/domains";
|
|
2
|
+
|
|
3
|
+
// A basic domain that contains two properies
|
|
4
|
+
export default class User extends Domain {
|
|
5
|
+
static fields = {
|
|
6
|
+
// a user's name must be a string
|
|
7
|
+
name: String,
|
|
8
|
+
// a user's age must be a number
|
|
9
|
+
age: Number,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {Domain} from "@primate/domains";
|
|
2
|
+
import House from "./House.js";
|
|
3
|
+
|
|
4
|
+
export default class User extends Domain {
|
|
5
|
+
static fields = {
|
|
6
|
+
// a user's name must be a string and unique across the user collection
|
|
7
|
+
name: [String, "unique"],
|
|
8
|
+
// a user's age must be a positive integer
|
|
9
|
+
age: [Number, "integer", "positive"],
|
|
10
|
+
// a user's house must have the foreign id of a house record and no two
|
|
11
|
+
// users may have the same house
|
|
12
|
+
house_id: [House, "unique"],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {Domain} from "@primate/domains";
|
|
2
|
+
import House from "./House.js";
|
|
3
|
+
|
|
4
|
+
export default class User extends Domain {
|
|
5
|
+
static fields = {
|
|
6
|
+
// a user's name must be a string
|
|
7
|
+
name: String,
|
|
8
|
+
// a user's age must be a number
|
|
9
|
+
age: Number,
|
|
10
|
+
// a user's house must have the foreign id of a house record
|
|
11
|
+
house_id: House,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
openssl req -x509 -out ssl/default.crt -keyout ssl/default.key -newkey rsa:2048 -nodes -sha256 -batch
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mkdir -p app/{routes,components,ssl} && cd app
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Today's date is ${date}.
|
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import html from "@primate/html";
|
|
2
|
+
import redirect from "@primate/redirect";
|
|
3
|
+
|
|
4
|
+
export default router => {
|
|
5
|
+
// declare `"edit-user"` as alias of `"/user/edit/([0-9])+"`
|
|
6
|
+
router.alias("edit-user", "/user/edit/([0-9])+");
|
|
7
|
+
|
|
8
|
+
// pass user instead of request to all verbs with this route
|
|
9
|
+
router.map("edit-user", () => ({name: "Donald"}));
|
|
10
|
+
|
|
11
|
+
// show user edit form
|
|
12
|
+
router.get("edit-user", user => html`<user-edit user="${user}" />`);
|
|
13
|
+
|
|
14
|
+
// verify form and save, or show errors
|
|
15
|
+
router.post("edit-user", async user => await user.save()
|
|
16
|
+
? redirect`/users`
|
|
17
|
+
: html`<user-edit user="${user}" />`);
|
|
18
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import html from "@primate/html";
|
|
2
|
+
|
|
3
|
+
export default router => {
|
|
4
|
+
// the HTML tagged template handler loads a component from the `components`
|
|
5
|
+
// directory and serves it as HTML, passing any given data as attributes
|
|
6
|
+
router.get("/users", () => {
|
|
7
|
+
const users = [
|
|
8
|
+
{name: "Donald", email: "donald@the.duck"},
|
|
9
|
+
{name: "Joe", email: "joe@was.absent"},
|
|
10
|
+
];
|
|
11
|
+
return html`<user-index users="${users}" />`;
|
|
12
|
+
});
|
|
13
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {File} from "runtime-compat/filesystem";
|
|
2
|
+
|
|
3
|
+
export default router => {
|
|
4
|
+
// any proper JavaScript object will be served as JSON
|
|
5
|
+
router.get("/users", () => [
|
|
6
|
+
{name: "Donald"},
|
|
7
|
+
{name: "Ryan"},
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
// load from a file and serve as JSON
|
|
11
|
+
router.get("/users-from-file", () => File.json("users.json"));
|
|
12
|
+
};
|
package/src/bundle.js
CHANGED
|
@@ -2,13 +2,13 @@ import {File} from "runtime-compat/filesystem";
|
|
|
2
2
|
|
|
3
3
|
export default async conf => {
|
|
4
4
|
const {paths} = conf;
|
|
5
|
-
// remove public directory in case exists
|
|
6
|
-
if (await paths.public.exists) {
|
|
7
|
-
await paths.public.file.remove();
|
|
8
|
-
}
|
|
9
|
-
await paths.public.file.create();
|
|
10
5
|
|
|
11
6
|
if (await paths.static.exists) {
|
|
7
|
+
// remove public directory in case exists
|
|
8
|
+
if (await paths.public.exists) {
|
|
9
|
+
await paths.public.file.remove();
|
|
10
|
+
}
|
|
11
|
+
await paths.public.file.create();
|
|
12
12
|
// copy static files to public
|
|
13
13
|
await File.copy(paths.static, paths.public);
|
|
14
14
|
}
|
package/src/conf.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {Path} from "runtime-compat/filesystem";
|
|
2
|
-
import {
|
|
2
|
+
import {EagerEither} from "runtime-compat/functional";
|
|
3
3
|
import cache from "./cache.js";
|
|
4
4
|
import extend from "./extend.js";
|
|
5
|
-
import
|
|
5
|
+
import preset from "./preset/primate.js";
|
|
6
6
|
|
|
7
7
|
const qualify = (root, paths) =>
|
|
8
8
|
Object.keys(paths).reduce((sofar, key) => {
|
|
@@ -13,13 +13,15 @@ const qualify = (root, paths) =>
|
|
|
13
13
|
return sofar;
|
|
14
14
|
}, {});
|
|
15
15
|
|
|
16
|
-
export default (filename = "primate.
|
|
16
|
+
export default async (filename = "primate.js") => {
|
|
17
17
|
const root = Path.resolve();
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
.
|
|
18
|
+
const conffile = root.join(filename);
|
|
19
|
+
const conf = await EagerEither
|
|
20
|
+
.try(async () => extend(preset, (await import(conffile)).default))
|
|
21
|
+
.match({left: () => preset})
|
|
21
22
|
.get();
|
|
22
23
|
const paths = qualify(root, conf.paths);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
});
|
|
24
|
+
return cache("conf", filename, () => {
|
|
25
|
+
return {...conf, paths, root};
|
|
26
|
+
});
|
|
27
|
+
};
|
package/src/handlers/http404.js
CHANGED
package/src/handlers/json.js
CHANGED
package/src/handlers/stream.js
CHANGED
package/src/handlers/text.js
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
base: "/",
|
|
3
|
+
debug: false,
|
|
4
|
+
http: {
|
|
5
|
+
host: "localhost",
|
|
6
|
+
port: 6161,
|
|
7
|
+
csp: {
|
|
8
|
+
"default-src": "'self'",
|
|
9
|
+
"object-src": "'none'",
|
|
10
|
+
"frame-ancestors": "'none'",
|
|
11
|
+
"form-action": "'self'",
|
|
12
|
+
"base-uri": "'self'",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
paths: {
|
|
16
|
+
public: "public",
|
|
17
|
+
static: "static",
|
|
18
|
+
routes: "routes",
|
|
19
|
+
components: "components",
|
|
20
|
+
},
|
|
21
|
+
};
|
package/src/route.js
CHANGED
|
@@ -16,6 +16,9 @@ const isFile = value => value instanceof File
|
|
|
16
16
|
? stream`${value}` : isStream(value);
|
|
17
17
|
const guess = value => isFile(value);
|
|
18
18
|
|
|
19
|
+
// insensitive-case equal
|
|
20
|
+
const ieq = (left, right) => left.toLowerCase() === right.toLowerCase();
|
|
21
|
+
|
|
19
22
|
export default async definitions => {
|
|
20
23
|
const aliases = [];
|
|
21
24
|
const routes = [];
|
|
@@ -33,7 +36,7 @@ export default async definitions => {
|
|
|
33
36
|
};
|
|
34
37
|
const find = (method, path, fallback = {handler: r => r}) =>
|
|
35
38
|
routes.find(route =>
|
|
36
|
-
route.method
|
|
39
|
+
ieq(route.method, method) && route.path.test(path)) ?? fallback;
|
|
37
40
|
|
|
38
41
|
const router = {
|
|
39
42
|
map: (path, callback) => add("map", path, callback),
|
|
@@ -41,7 +44,7 @@ export default async definitions => {
|
|
|
41
44
|
post: (path, callback) => add("post", path, callback),
|
|
42
45
|
alias: (key, value) => aliases.push({key, value}),
|
|
43
46
|
process: async request => {
|
|
44
|
-
const {method} = request;
|
|
47
|
+
const {method} = request.original;
|
|
45
48
|
const url = new URL(`https://primatejs.com${request.pathname}`);
|
|
46
49
|
const {pathname, searchParams} = url;
|
|
47
50
|
const params = Object.fromEntries(searchParams);
|
package/src/run.js
CHANGED
|
@@ -4,8 +4,11 @@ import bundle from "./bundle.js";
|
|
|
4
4
|
import package_json from "../package.json" assert {type: "json"};
|
|
5
5
|
import log from "./log.js";
|
|
6
6
|
|
|
7
|
+
const extract = (modules, key) => modules.flatMap(module => module[key] ?? []);
|
|
8
|
+
|
|
7
9
|
export default async conf => {
|
|
8
10
|
log.reset("Primate").yellow(package_json.version);
|
|
11
|
+
|
|
9
12
|
const {paths} = conf;
|
|
10
13
|
const router = await route(paths.routes);
|
|
11
14
|
await bundle(conf);
|
|
@@ -14,5 +17,6 @@ export default async conf => {
|
|
|
14
17
|
paths: conf.paths,
|
|
15
18
|
from: conf.paths.public,
|
|
16
19
|
http: conf.http,
|
|
20
|
+
modules: extract(conf.modules ?? [], "serve"),
|
|
17
21
|
});
|
|
18
22
|
};
|
package/src/serve.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {Path} from "runtime-compat/filesystem";
|
|
2
2
|
import {serve, Response} from "runtime-compat/http";
|
|
3
|
-
import
|
|
3
|
+
import statuses from "./http-statuses.json" assert {type: "json"};
|
|
4
4
|
import mimes from "./mimes.json" assert {type: "json"};
|
|
5
5
|
import {http404} from "./handlers/http.js";
|
|
6
6
|
import log from "./log.js";
|
|
@@ -8,83 +8,87 @@ import log from "./log.js";
|
|
|
8
8
|
const regex = /\.([a-z1-9]*)$/u;
|
|
9
9
|
const mime = filename => mimes[filename.match(regex)[1]] ?? mimes.binary;
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const {http} = this.conf;
|
|
18
|
-
const {csp, "same-site": same_site = "Strict"} = http;
|
|
19
|
-
this.csp = Object.keys(csp).reduce((policy_string, key) =>
|
|
20
|
-
`${policy_string}${key} ${csp[key]};`, "");
|
|
21
|
-
|
|
22
|
-
const decoder = new TextDecoder();
|
|
23
|
-
serve(async request => {
|
|
24
|
-
const reader = request.body.getReader();
|
|
25
|
-
const chunks = [];
|
|
26
|
-
let result;
|
|
27
|
-
do {
|
|
28
|
-
result = await reader.read();
|
|
29
|
-
if (result.value !== undefined) {
|
|
30
|
-
chunks.push(decoder.decode(result.value));
|
|
31
|
-
}
|
|
32
|
-
} while (!result.done);
|
|
33
|
-
const body = chunks.join();
|
|
34
|
-
const payload = Object.fromEntries(decodeURI(body).replaceAll("+", " ")
|
|
35
|
-
.split("&")
|
|
36
|
-
.map(part => part.split("=")));
|
|
37
|
-
const {pathname, search} = new URL(`https://example.com${request.url}`);
|
|
38
|
-
return this.try(pathname + search, request, payload);
|
|
39
|
-
}, http);
|
|
40
|
-
const {port, host} = this.conf.http;
|
|
41
|
-
log.reset("on").yellow(`${host}:${port}`).nl();
|
|
42
|
-
}
|
|
11
|
+
const contents = {
|
|
12
|
+
"application/x-www-form-urlencoded": body =>
|
|
13
|
+
Object.fromEntries(body.split("&").map(part => part.split("=")
|
|
14
|
+
.map(subpart => decodeURIComponent(subpart).replaceAll("+", " ")))),
|
|
15
|
+
"application/json": body => JSON.parse(body),
|
|
16
|
+
};
|
|
43
17
|
|
|
44
|
-
|
|
18
|
+
export default conf => {
|
|
19
|
+
const route = async request => {
|
|
20
|
+
let result;
|
|
45
21
|
try {
|
|
46
|
-
|
|
22
|
+
result = await (await conf.router.process(request))(conf);
|
|
47
23
|
} catch (error) {
|
|
48
24
|
console.log(error);
|
|
49
|
-
|
|
25
|
+
result = http404()``;
|
|
50
26
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return await path.isFile
|
|
56
|
-
? this.resource(path.file)
|
|
57
|
-
: this.route(url, request, payload);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async resource(file) {
|
|
61
|
-
return new Response(file.readable, {
|
|
62
|
-
status: codes.OK,
|
|
27
|
+
const csp = Object.keys(conf.http.csp).reduce((policy_string, key) =>
|
|
28
|
+
`${policy_string}${key} ${conf.http.csp[key]};`, "");
|
|
29
|
+
return new Response(result.body, {
|
|
30
|
+
status: result.status,
|
|
63
31
|
headers: {
|
|
64
|
-
|
|
65
|
-
|
|
32
|
+
...result.headers,
|
|
33
|
+
"Content-Security-Policy": csp,
|
|
34
|
+
"Referrer-Policy": "same-origin",
|
|
66
35
|
},
|
|
67
36
|
});
|
|
68
|
-
}
|
|
37
|
+
};
|
|
69
38
|
|
|
70
|
-
async
|
|
71
|
-
|
|
72
|
-
|
|
39
|
+
const resource = async file => new Response(file.readable, {
|
|
40
|
+
status: statuses.OK,
|
|
41
|
+
headers: {
|
|
42
|
+
"Content-Type": mime(file.name),
|
|
43
|
+
Etag: await file.modified,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const _serve = async request => {
|
|
48
|
+
const path = new Path(conf.from, request.pathname);
|
|
49
|
+
return await path.isFile ? resource(path.file) : route(request);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const handle = async request => {
|
|
73
53
|
try {
|
|
74
|
-
|
|
54
|
+
return await _serve(request);
|
|
75
55
|
} catch (error) {
|
|
76
56
|
console.log(error);
|
|
77
|
-
|
|
57
|
+
return new Response(null, {status: statuses.InternalServerError});
|
|
78
58
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
};
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const parseContent = (request, body) => {
|
|
62
|
+
const type = contents[request.headers.get("content-type")];
|
|
63
|
+
return type === undefined ? body : type(body);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const {http, modules} = conf;
|
|
89
67
|
|
|
90
|
-
|
|
68
|
+
// handle is the last module to be executed
|
|
69
|
+
const handlers = [...modules, handle].reduceRight((acc, handler) =>
|
|
70
|
+
input => handler(input, acc));
|
|
71
|
+
|
|
72
|
+
const decoder = new TextDecoder();
|
|
73
|
+
serve(async request => {
|
|
74
|
+
// preprocess request
|
|
75
|
+
const reader = request.body.getReader();
|
|
76
|
+
const chunks = [];
|
|
77
|
+
let result;
|
|
78
|
+
do {
|
|
79
|
+
result = await reader.read();
|
|
80
|
+
if (result.value !== undefined) {
|
|
81
|
+
chunks.push(decoder.decode(result.value));
|
|
82
|
+
}
|
|
83
|
+
} while (!result.done);
|
|
84
|
+
|
|
85
|
+
const body = chunks.length === 0 ? undefined
|
|
86
|
+
: parseContent(request, chunks.join());
|
|
87
|
+
|
|
88
|
+
const {pathname, search} = new URL(`https://example.com${request.url}`);
|
|
89
|
+
|
|
90
|
+
return await handlers({original: request, pathname: pathname + search, body});
|
|
91
|
+
}, http);
|
|
92
|
+
|
|
93
|
+
log.reset("on").yellow(`${http.host}:${http.port}`).nl();
|
|
94
|
+
};
|
package/TODO
DELETED
package/src/preset/primate.json
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"base": "/",
|
|
3
|
-
"debug": false,
|
|
4
|
-
"files": {
|
|
5
|
-
"index": "index.html"
|
|
6
|
-
},
|
|
7
|
-
"http": {
|
|
8
|
-
"host": "localhost",
|
|
9
|
-
"port": 9999,
|
|
10
|
-
"csp": {
|
|
11
|
-
"default-src": "'self'",
|
|
12
|
-
"object-src": "'none'",
|
|
13
|
-
"frame-ancestors": "'none'",
|
|
14
|
-
"form-action": "'self'",
|
|
15
|
-
"base-uri": "'self'"
|
|
16
|
-
},
|
|
17
|
-
"same-site": "Strict"
|
|
18
|
-
},
|
|
19
|
-
"paths": {
|
|
20
|
-
"public": "public",
|
|
21
|
-
"static": "static",
|
|
22
|
-
"routes": "routes",
|
|
23
|
-
"components": "components"
|
|
24
|
-
}
|
|
25
|
-
}
|
|
File without changes
|