primate 0.13.0 → 0.13.2
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 +12 -15
- package/package.json +1 -1
- package/readme/routing/sharing-logic-across-requests.js +9 -12
- package/readme/template.md +3 -3
- package/src/bundle.js +3 -1
- package/src/config.js +28 -3
- package/src/handlers/html.js +2 -2
- package/src/index.html +1 -0
- package/src/publish.js +5 -0
- package/src/run.js +7 -0
- package/src/serve.js +39 -4
package/README.md
CHANGED
|
@@ -190,32 +190,29 @@ export default router => {
|
|
|
190
190
|
### Sharing logic across requests
|
|
191
191
|
|
|
192
192
|
```js
|
|
193
|
-
import html from "@primate/html";
|
|
194
|
-
import redirect from "@primate/redirect";
|
|
195
|
-
|
|
196
193
|
export default router => {
|
|
197
|
-
//
|
|
194
|
+
// Declare `"edit-user"` as alias of `"/user/edit/([0-9])+"`
|
|
198
195
|
router.alias("edit-user", "/user/edit/([0-9])+");
|
|
199
196
|
|
|
200
|
-
//
|
|
201
|
-
router.map("edit-user", () =>
|
|
197
|
+
// Pass user instead of request to all verbs on this route
|
|
198
|
+
router.map("edit-user", ({body}) => body?.name ?? "Donald");
|
|
202
199
|
|
|
203
|
-
//
|
|
204
|
-
router.get("edit-user", user =>
|
|
200
|
+
// Show user as plain text
|
|
201
|
+
router.get("edit-user", user => user);
|
|
205
202
|
|
|
206
|
-
//
|
|
207
|
-
router.post("edit-user",
|
|
208
|
-
?
|
|
209
|
-
:
|
|
203
|
+
// Verify or show error
|
|
204
|
+
router.post("edit-user", user => user === "Donald"
|
|
205
|
+
? "Hi Donald!"
|
|
206
|
+
: {message: "Error saving user"});
|
|
210
207
|
};
|
|
211
208
|
|
|
212
209
|
```
|
|
213
210
|
|
|
214
211
|
### Explicit handlers
|
|
215
212
|
|
|
216
|
-
Most often we can figure the content type to respond with based on the
|
|
217
|
-
type from the handler. To handle content not automatically detected, use
|
|
218
|
-
second argument of the exported function.
|
|
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.
|
|
219
216
|
|
|
220
217
|
```js
|
|
221
218
|
export default (router, {redirect}) => {
|
package/package.json
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import html from "@primate/html";
|
|
2
|
-
import redirect from "@primate/redirect";
|
|
3
|
-
|
|
4
1
|
export default router => {
|
|
5
|
-
//
|
|
2
|
+
// Declare `"edit-user"` as alias of `"/user/edit/([0-9])+"`
|
|
6
3
|
router.alias("edit-user", "/user/edit/([0-9])+");
|
|
7
4
|
|
|
8
|
-
//
|
|
9
|
-
router.map("edit-user", () =>
|
|
5
|
+
// Pass user instead of request to all verbs on this route
|
|
6
|
+
router.map("edit-user", ({body}) => body?.name ?? "Donald");
|
|
10
7
|
|
|
11
|
-
//
|
|
12
|
-
router.get("edit-user", user =>
|
|
8
|
+
// Show user as plain text
|
|
9
|
+
router.get("edit-user", user => user);
|
|
13
10
|
|
|
14
|
-
//
|
|
15
|
-
router.post("edit-user",
|
|
16
|
-
?
|
|
17
|
-
:
|
|
11
|
+
// Verify or show error
|
|
12
|
+
router.post("edit-user", user => user === "Donald"
|
|
13
|
+
? "Hi Donald!"
|
|
14
|
+
: {message: "Error saving user"});
|
|
18
15
|
};
|
package/readme/template.md
CHANGED
|
@@ -117,9 +117,9 @@ to the content type sent along the request. Currently supported are
|
|
|
117
117
|
|
|
118
118
|
### Explicit handlers
|
|
119
119
|
|
|
120
|
-
Most often we can figure the content type to respond with based on the
|
|
121
|
-
type from the handler. To handle content not automatically detected, use
|
|
122
|
-
second argument of the exported function.
|
|
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.
|
|
123
123
|
|
|
124
124
|
```js
|
|
125
125
|
// routing/explicit-handlers.js
|
package/src/bundle.js
CHANGED
|
@@ -16,6 +16,8 @@ const makePublic = async env => {
|
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
export default async env =>
|
|
19
|
+
export default async env => {
|
|
20
|
+
await makePublic(env);
|
|
20
21
|
[...filter("bundle", env.modules), _ => _].reduceRight((acc, handler) =>
|
|
21
22
|
input => handler(input, acc))(env);
|
|
23
|
+
};
|
package/src/config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import crypto from "runtime-compat/crypto";
|
|
2
2
|
import {is} from "runtime-compat/dyndef";
|
|
3
|
+
import {File, Path} from "runtime-compat/fs";
|
|
3
4
|
import cache from "./cache.js";
|
|
4
5
|
import extend from "./extend.js";
|
|
5
6
|
import defaults from "./primate.config.js";
|
|
@@ -45,11 +46,19 @@ const index = async env => {
|
|
|
45
46
|
}
|
|
46
47
|
};
|
|
47
48
|
|
|
49
|
+
const hash = async (string, algorithm = "sha-384") => {
|
|
50
|
+
const encoder = new TextEncoder();
|
|
51
|
+
const bytes = await crypto.subtle.digest(algorithm, encoder.encode(string));
|
|
52
|
+
const algo = algorithm.replace("-", () => "");
|
|
53
|
+
return `${algo}-${btoa(String.fromCharCode(...new Uint8Array(bytes)))}`;
|
|
54
|
+
};
|
|
55
|
+
|
|
48
56
|
export default async (filename = "primate.config.js") => {
|
|
49
57
|
is(filename).string();
|
|
50
58
|
const root = await getRoot();
|
|
51
59
|
const config = await getConfig(root, filename);
|
|
52
60
|
|
|
61
|
+
const resources = [];
|
|
53
62
|
const env = {
|
|
54
63
|
...config,
|
|
55
64
|
paths: qualify(root, config.paths),
|
|
@@ -59,7 +68,23 @@ export default async (filename = "primate.config.js") => {
|
|
|
59
68
|
env.handlers[name] = handler;
|
|
60
69
|
},
|
|
61
70
|
handlers: {...handlers},
|
|
62
|
-
render: async
|
|
71
|
+
render: async ({body = "", head = ""} = {}) => {
|
|
72
|
+
const html = await index(env);
|
|
73
|
+
const heads = resources.map(({src, code, type, inline, integrity}) => {
|
|
74
|
+
const tag = "script";
|
|
75
|
+
const pre = `<${tag} type="${type}" integrity="${integrity}"`;
|
|
76
|
+
const post = `</${tag}>`;
|
|
77
|
+
return inline ? `${pre}>${code}${post}` : `${pre} src="${src}">${post}`;
|
|
78
|
+
}).join("\n");
|
|
79
|
+
return html
|
|
80
|
+
.replace("%body%", () => body)
|
|
81
|
+
.replace("%head%", () => `${head}${heads}`);
|
|
82
|
+
},
|
|
83
|
+
publish: async ({src, code, type = "", inline = false}) => {
|
|
84
|
+
const integrity = await hash(code);
|
|
85
|
+
resources.push({src, code, type, inline, integrity});
|
|
86
|
+
return integrity;
|
|
87
|
+
},
|
|
63
88
|
};
|
|
64
89
|
env.log.info(`${package_json.name} \x1b[34m${package_json.version}\x1b[0m`);
|
|
65
90
|
const modules = await Promise.all(config.modules.map(module => module(env)));
|
|
@@ -68,6 +93,6 @@ export default async (filename = "primate.config.js") => {
|
|
|
68
93
|
.filter(module => module.load !== undefined)
|
|
69
94
|
.map(module => module.load()(env)));
|
|
70
95
|
|
|
71
|
-
return cache("config", filename, () => ({...env,
|
|
96
|
+
return cache("config", filename, () => ({...env, resources,
|
|
72
97
|
modules: modules.concat(loads)}));
|
|
73
98
|
};
|
package/src/handlers/html.js
CHANGED
|
@@ -8,9 +8,9 @@ const getContent = async (env, name) => {
|
|
|
8
8
|
|
|
9
9
|
export default (content, {status = 200, partial = false, adhoc = false} = {}) =>
|
|
10
10
|
async (env, headers) => {
|
|
11
|
-
const
|
|
11
|
+
const body = adhoc ? content : await getContent(env, content);
|
|
12
12
|
return [
|
|
13
|
-
partial ?
|
|
13
|
+
partial ? body : await env.render({body}), {
|
|
14
14
|
status,
|
|
15
15
|
headers: {...headers, "Content-Type": "text/html"},
|
|
16
16
|
},
|
package/src/index.html
CHANGED
package/src/publish.js
ADDED
package/src/run.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import config from "./config.js";
|
|
2
2
|
import register from "./register.js";
|
|
3
3
|
import compile from "./compile.js";
|
|
4
|
+
import publish from "./publish.js";
|
|
4
5
|
import bundle from "./bundle.js";
|
|
5
6
|
import route from "./route.js";
|
|
6
7
|
import serve from "./serve.js";
|
|
7
8
|
|
|
8
9
|
export default async () => {
|
|
9
10
|
const env = await config();
|
|
11
|
+
// register handlers
|
|
10
12
|
await register(env);
|
|
13
|
+
// compile server-side code
|
|
11
14
|
await compile(env);
|
|
15
|
+
// publish client-side code
|
|
16
|
+
await publish(env);
|
|
17
|
+
// bundle client-side code
|
|
12
18
|
await bundle(env);
|
|
19
|
+
// serve
|
|
13
20
|
serve({router: await route(env.paths.routes, env.handlers), ...env});
|
|
14
21
|
};
|
package/src/serve.js
CHANGED
|
@@ -22,8 +22,19 @@ export default env => {
|
|
|
22
22
|
const _respond = async request => {
|
|
23
23
|
const csp = Object.keys(env.http.csp).reduce((policy_string, key) =>
|
|
24
24
|
`${policy_string}${key} ${env.http.csp[key]};`, "");
|
|
25
|
+
const scripts = env.resources
|
|
26
|
+
.map(resource => `'${resource.integrity}'`).join(" ");
|
|
27
|
+
const _csp = scripts === "" ? csp : `${csp}script-src 'self' ${scripts};`;
|
|
28
|
+
// remove inline resources
|
|
29
|
+
for (let i = env.resources.length - 1; i >= 0; i--) {
|
|
30
|
+
const resource = env.resources[i];
|
|
31
|
+
if (resource.inline) {
|
|
32
|
+
env.resources.splice(i, 1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
25
36
|
const headers = {
|
|
26
|
-
"Content-Security-Policy":
|
|
37
|
+
"Content-Security-Policy": _csp,
|
|
27
38
|
"Referrer-Policy": "same-origin",
|
|
28
39
|
};
|
|
29
40
|
|
|
@@ -53,9 +64,24 @@ export default env => {
|
|
|
53
64
|
},
|
|
54
65
|
});
|
|
55
66
|
|
|
67
|
+
const publishedResource = request => {
|
|
68
|
+
const published = env.resources.find(resource =>
|
|
69
|
+
`/${resource.src}` === request.pathname);
|
|
70
|
+
if (published !== undefined) {
|
|
71
|
+
return new Response(published.code, {
|
|
72
|
+
status: statuses.OK,
|
|
73
|
+
headers: {
|
|
74
|
+
"Content-Type": mime(published.src),
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return route(request);
|
|
80
|
+
};
|
|
81
|
+
|
|
56
82
|
const _serve = async request => {
|
|
57
83
|
const path = new Path(env.paths.public, request.pathname);
|
|
58
|
-
return await path.isFile ? resource(path.file) :
|
|
84
|
+
return await path.isFile ? resource(path.file) : publishedResource(request);
|
|
59
85
|
};
|
|
60
86
|
|
|
61
87
|
const handle = async request => {
|
|
@@ -67,11 +93,20 @@ export default env => {
|
|
|
67
93
|
}
|
|
68
94
|
};
|
|
69
95
|
|
|
70
|
-
const
|
|
71
|
-
const type = contents[
|
|
96
|
+
const parseContentType = (contentType, body) => {
|
|
97
|
+
const type = contents[contentType];
|
|
72
98
|
return type === undefined ? body : type(body);
|
|
73
99
|
};
|
|
74
100
|
|
|
101
|
+
const parseContent = (request, body) => {
|
|
102
|
+
try {
|
|
103
|
+
return parseContentType(request.headers.get("content-type"), body);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
env.log.warn(error);
|
|
106
|
+
return body;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
75
110
|
const {http} = env;
|
|
76
111
|
const modules = filter("serve", env.modules);
|
|
77
112
|
|