primate 0.21.4 → 0.22.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/package.json +2 -2
- package/src/app.js +91 -70
- package/src/commands/dev.js +1 -1
- package/src/commands/serve.js +1 -1
- package/src/cwd.js +3 -0
- package/src/defaults/primate.config.js +17 -5
- package/src/errors.json +11 -4
- package/src/handlers/html.js +10 -13
- package/src/handlers/view.js +4 -5
- package/src/hooks/bundle.js +2 -17
- package/src/hooks/compile.js +26 -16
- package/src/hooks/copy_includes.js +4 -5
- package/src/hooks/exports.js +1 -0
- package/src/hooks/handle.js +20 -20
- package/src/hooks/init.js +3 -0
- package/src/hooks/parse.js +5 -2
- package/src/hooks/publish.js +39 -34
- package/src/hooks/register.js +3 -5
- package/src/hooks/route.js +26 -20
- package/src/hooks/serve.js +2 -4
- package/src/loaders/modules.js +6 -14
- package/src/loaders/routes/load.js +2 -2
- package/src/loaders/routes.js +1 -1
- package/src/run.js +13 -16
- package/src/start.js +12 -18
- /package/src/{toSorted.js → to_sorted.js} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.1",
|
|
4
4
|
"description": "Expressive, minimal and extensible web framework",
|
|
5
5
|
"homepage": "https://primatejs.com",
|
|
6
6
|
"bugs": "https://github.com/primatejs/primate/issues",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"directory": "packages/primate"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"runtime-compat": "^0.
|
|
21
|
+
"runtime-compat": "^0.25.7"
|
|
22
22
|
},
|
|
23
23
|
"engines": {
|
|
24
24
|
"node": ">=18.16"
|
package/src/app.js
CHANGED
|
@@ -2,34 +2,28 @@ import crypto from "runtime-compat/crypto";
|
|
|
2
2
|
import {tryreturn} from "runtime-compat/async";
|
|
3
3
|
import {File, Path} from "runtime-compat/fs";
|
|
4
4
|
import {bold, blue} from "runtime-compat/colors";
|
|
5
|
+
import {is} from "runtime-compat/dyndef";
|
|
5
6
|
import {transform, valmap} from "runtime-compat/object";
|
|
7
|
+
import {globify} from "runtime-compat/string";
|
|
8
|
+
import errors from "./errors.js";
|
|
9
|
+
import {print} from "./Logger.js";
|
|
10
|
+
import dispatch from "./dispatch.js";
|
|
11
|
+
import to_sorted from "./to_sorted.js";
|
|
6
12
|
import * as handlers from "./handlers/exports.js";
|
|
7
|
-
import * as hooks from "./hooks/exports.js";
|
|
8
13
|
import * as loaders from "./loaders/exports.js";
|
|
9
|
-
import dispatch from "./dispatch.js";
|
|
10
|
-
import {print} from "./Logger.js";
|
|
11
|
-
import toSorted from "./toSorted.js";
|
|
12
14
|
|
|
13
|
-
const
|
|
15
|
+
const {DoubleFileExtension} = errors;
|
|
16
|
+
|
|
14
17
|
// do not hard-depend on node
|
|
15
18
|
const packager = import.meta.runtime?.packager ?? "package.json";
|
|
16
19
|
const library = import.meta.runtime?.library ?? "node_modules";
|
|
17
20
|
|
|
18
|
-
const fallback = (app, page) =>
|
|
19
|
-
tryreturn(_ => base.join("defaults", page).text())
|
|
20
|
-
.orelse(_ => base.join("defaults", app.config.pages.index).text());
|
|
21
|
-
|
|
22
21
|
// use user-provided file or fall back to default
|
|
23
|
-
const index = (
|
|
24
|
-
tryreturn(_ => File.read(`${
|
|
25
|
-
.orelse(_ => fallback
|
|
22
|
+
const index = (base, page, fallback) =>
|
|
23
|
+
tryreturn(_ => File.read(`${base.join(page)}`))
|
|
24
|
+
.orelse(_ => File.read(`${base.join(fallback)}`));
|
|
26
25
|
|
|
27
26
|
const encoder = new TextEncoder();
|
|
28
|
-
const hash = async (string, algorithm = "sha-384") => {
|
|
29
|
-
const bytes = await crypto.subtle.digest(algorithm, encoder.encode(string));
|
|
30
|
-
const algo = algorithm.replace("-", _ => "");
|
|
31
|
-
return `${algo}-${btoa(String.fromCharCode(...new Uint8Array(bytes)))}`;
|
|
32
|
-
};
|
|
33
27
|
|
|
34
28
|
const attribute = attributes => Object.keys(attributes).length > 0
|
|
35
29
|
? " ".concat(Object.entries(attributes)
|
|
@@ -38,11 +32,13 @@ const attribute = attributes => Object.keys(attributes).length > 0
|
|
|
38
32
|
const tag = ({name, attributes = {}, code = "", close = true}) =>
|
|
39
33
|
`<${name}${attribute(attributes)}${close ? `>${code}</${name}>` : "/>"}`;
|
|
40
34
|
|
|
41
|
-
|
|
35
|
+
const base = new Path(import.meta.url).up(1);
|
|
36
|
+
|
|
37
|
+
export default async (log, root, config) => {
|
|
42
38
|
const {http} = config;
|
|
43
39
|
const secure = http?.ssl !== undefined;
|
|
44
40
|
const {name, version} = await base.up(1).join(packager).json();
|
|
45
|
-
const
|
|
41
|
+
const path = valmap(config.location, value => root.join(value));
|
|
46
42
|
|
|
47
43
|
const at = `at http${secure ? "s" : ""}://${http.host}:${http.port}\n`;
|
|
48
44
|
print(blue(bold(name)), blue(version), at);
|
|
@@ -53,33 +49,54 @@ export default async (config, root, log) => {
|
|
|
53
49
|
http.ssl.cert = root.join(http.ssl.cert);
|
|
54
50
|
}
|
|
55
51
|
|
|
56
|
-
const types = await loaders.types(log,
|
|
52
|
+
const types = await loaders.types(log, path.types);
|
|
53
|
+
const error = await path.routes.join("+error.js");
|
|
54
|
+
const routes = await loaders.routes(log, path.routes);
|
|
55
|
+
const modules = await loaders.modules(log, root, config);
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
build: {
|
|
60
|
-
paths: {
|
|
61
|
-
client: paths.build.join("client"),
|
|
62
|
-
server: paths.build.join("server"),
|
|
63
|
-
components: paths.build.join("components"),
|
|
64
|
-
},
|
|
65
|
-
},
|
|
57
|
+
return {
|
|
66
58
|
config,
|
|
67
59
|
secure,
|
|
68
60
|
name,
|
|
69
61
|
version,
|
|
70
62
|
importmaps: {},
|
|
71
63
|
assets: [],
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
exports: [],
|
|
65
|
+
path,
|
|
74
66
|
root,
|
|
75
67
|
log,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
68
|
+
error: {
|
|
69
|
+
default: await error.exists ? (await import(error)).default : undefined,
|
|
70
|
+
},
|
|
71
|
+
handlers: {...handlers},
|
|
72
|
+
types,
|
|
73
|
+
routes,
|
|
74
|
+
layout: {
|
|
75
|
+
depth: Math.max(...routes.map(({layouts}) => layouts.length)) + 1,
|
|
76
|
+
},
|
|
77
|
+
dispatch: dispatch(types),
|
|
78
|
+
modules,
|
|
79
|
+
packager,
|
|
80
|
+
library,
|
|
81
|
+
// copy files to build folder, potentially transforming them
|
|
82
|
+
async stage(source, directory, filter) {
|
|
83
|
+
const {paths, mapper} = this.config.build.transform;
|
|
84
|
+
is(paths).array();
|
|
85
|
+
is(mapper).function();
|
|
86
|
+
|
|
87
|
+
const regexs = paths.map(file => globify(file));
|
|
88
|
+
const target = this.runpath(directory);
|
|
89
|
+
|
|
90
|
+
await Promise.all((await source.collect(filter)).map(async path => {
|
|
91
|
+
const filename = new Path(directory).join(path.debase(source));
|
|
92
|
+
const to = await target.join(filename.debase(directory));
|
|
81
93
|
await to.directory.file.create();
|
|
82
|
-
|
|
94
|
+
if (regexs.some(regex => regex.test(filename))) {
|
|
95
|
+
const contents = mapper(await path.text());
|
|
96
|
+
await to.file.write(contents);
|
|
97
|
+
} else {
|
|
98
|
+
await path.file.copy(to);
|
|
99
|
+
}
|
|
83
100
|
}));
|
|
84
101
|
},
|
|
85
102
|
headers() {
|
|
@@ -101,9 +118,13 @@ export default async (config, root, log) => {
|
|
|
101
118
|
"Referrer-Policy": "same-origin",
|
|
102
119
|
};
|
|
103
120
|
},
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
121
|
+
runpath(...directories) {
|
|
122
|
+
return this.path.build.join(...directories);
|
|
123
|
+
},
|
|
124
|
+
async render({body = "", head = "", page = config.pages.index} = {}) {
|
|
125
|
+
const {location: {pages}} = this.config;
|
|
126
|
+
|
|
127
|
+
const html = await index(this.runpath(pages), page, config.pages.index);
|
|
107
128
|
// inline: <script type integrity>...</script>
|
|
108
129
|
// outline: <script type integrity src></script>
|
|
109
130
|
const script = ({inline, code, type, integrity, src}) => inline
|
|
@@ -114,37 +135,53 @@ export default async (config, root, log) => {
|
|
|
114
135
|
const style = ({inline, code, href, rel = "stylesheet"}) => inline
|
|
115
136
|
? tag({name: "style", code})
|
|
116
137
|
: tag({name: "link", attributes: {rel, href}, close: false});
|
|
117
|
-
|
|
138
|
+
|
|
139
|
+
const heads = head.concat("\n", to_sorted(this.assets,
|
|
118
140
|
({type}) => -1 * (type === "importmap"))
|
|
119
141
|
.map(({src, code, type, inline, integrity}) =>
|
|
120
142
|
type === "style"
|
|
121
143
|
? style({inline, code, href: src})
|
|
122
144
|
: script({inline, code, type, integrity, src})
|
|
123
|
-
).join("\n");
|
|
145
|
+
).join("\n"));
|
|
124
146
|
// remove inline assets
|
|
125
147
|
this.assets = this.assets.filter(({inline, type}) => !inline
|
|
126
148
|
|| type === "importmap");
|
|
127
|
-
return html.replace("%body%", _ => body).replace("%head%", _ =>
|
|
149
|
+
return html.replace("%body%", _ => body).replace("%head%", _ => heads);
|
|
128
150
|
},
|
|
129
|
-
async publish({src, code, type = "", inline = false}) {
|
|
130
|
-
if (!inline) {
|
|
131
|
-
const base = this.
|
|
151
|
+
async publish({src, code, type = "", inline = false, copy = true}) {
|
|
152
|
+
if (!inline && copy) {
|
|
153
|
+
const base = this.runpath(this.config.location.client).join(src);
|
|
132
154
|
await base.directory.file.create();
|
|
133
155
|
await base.file.write(code);
|
|
134
156
|
}
|
|
135
157
|
if (inline || type === "style") {
|
|
136
|
-
this.assets.push({
|
|
137
|
-
|
|
158
|
+
this.assets.push({
|
|
159
|
+
src: new Path(http.static.root).join(src ?? "").path,
|
|
160
|
+
code: inline ? code : "",
|
|
161
|
+
type,
|
|
162
|
+
inline,
|
|
163
|
+
integrity: await this.hash(code),
|
|
164
|
+
});
|
|
138
165
|
}
|
|
139
166
|
},
|
|
140
|
-
|
|
141
|
-
this.
|
|
167
|
+
export({type, code}) {
|
|
168
|
+
this.exports.push({type, code});
|
|
169
|
+
},
|
|
170
|
+
register(extension, handler) {
|
|
171
|
+
is(this.handlers[extension]).undefined(DoubleFileExtension.new(extension));
|
|
172
|
+
this.handlers[extension] = handler;
|
|
173
|
+
},
|
|
174
|
+
async hash(data, algorithm = "sha-384") {
|
|
175
|
+
const bytes = await crypto.subtle.digest(algorithm, encoder.encode(data));
|
|
176
|
+
const prefix = algorithm.replace("-", _ => "");
|
|
177
|
+
return `${prefix}-${btoa(String.fromCharCode(...new Uint8Array(bytes)))}`;
|
|
142
178
|
},
|
|
143
179
|
async import(module) {
|
|
144
|
-
const {
|
|
180
|
+
const {http: {static: {root}}, location: {client}} = this.config;
|
|
181
|
+
|
|
145
182
|
const parts = module.split("/");
|
|
146
|
-
const path = [library, ...parts];
|
|
147
|
-
const pkg = await Path.resolve().join(...path, packager).json();
|
|
183
|
+
const path = [this.library, ...parts];
|
|
184
|
+
const pkg = await Path.resolve().join(...path, this.packager).json();
|
|
148
185
|
const exports = pkg.exports === undefined
|
|
149
186
|
? {[module]: `/${module}/${pkg.main}`}
|
|
150
187
|
: transform(pkg.exports, entry => entry
|
|
@@ -158,27 +195,11 @@ export default async (config, root, log) => {
|
|
|
158
195
|
?? value.import?.replace(".", `./${module}`),
|
|
159
196
|
]));
|
|
160
197
|
const dependency = Path.resolve().join(...path);
|
|
161
|
-
const to = new Path(this.
|
|
198
|
+
const to = new Path(this.runpath(client), this.library, ...parts);
|
|
162
199
|
await dependency.file.copy(to);
|
|
163
200
|
this.importmaps = {
|
|
164
|
-
...valmap(exports, value => new Path(root,
|
|
201
|
+
...valmap(exports, value => new Path(root, this.library, value).path),
|
|
165
202
|
...this.importmaps};
|
|
166
203
|
},
|
|
167
|
-
types,
|
|
168
|
-
routes: await loaders.routes(log, paths.routes),
|
|
169
|
-
dispatch: dispatch(types),
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const error = await app.paths.routes.join("+error.js");
|
|
173
|
-
const modules = await loaders.modules(app, root, config);
|
|
174
|
-
|
|
175
|
-
return {...app,
|
|
176
|
-
modules,
|
|
177
|
-
error: {
|
|
178
|
-
default: await error.exists ? (await import(error)).default : undefined,
|
|
179
|
-
},
|
|
180
|
-
layoutDepth: Math.max(...app.routes.map(({layouts}) => layouts.length)) + 1,
|
|
181
|
-
route: hooks.route({...app, modules}),
|
|
182
|
-
parse: hooks.parse(dispatch(types)),
|
|
183
204
|
};
|
|
184
205
|
};
|
package/src/commands/dev.js
CHANGED
package/src/commands/serve.js
CHANGED
package/src/cwd.js
ADDED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {identity} from "runtime-compat/function";
|
|
1
2
|
import Logger from "../Logger.js";
|
|
2
3
|
|
|
3
4
|
export default {
|
|
@@ -27,20 +28,31 @@ export default {
|
|
|
27
28
|
root: "/",
|
|
28
29
|
},
|
|
29
30
|
},
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
location: {
|
|
32
|
+
// renderable components
|
|
32
33
|
components: "components",
|
|
34
|
+
// HTML pages
|
|
33
35
|
pages: "pages",
|
|
36
|
+
// hierarchical routes
|
|
34
37
|
routes: "routes",
|
|
38
|
+
// static assets
|
|
35
39
|
static: "static",
|
|
40
|
+
// runtime types
|
|
36
41
|
types: "types",
|
|
42
|
+
// build environment
|
|
43
|
+
build: "build",
|
|
44
|
+
// client build
|
|
45
|
+
client: "client",
|
|
46
|
+
// server build
|
|
47
|
+
server: "server",
|
|
37
48
|
},
|
|
38
49
|
build: {
|
|
39
50
|
includes: [],
|
|
40
|
-
static: "static",
|
|
41
|
-
app: "app",
|
|
42
|
-
modules: "modules",
|
|
43
51
|
index: "index.js",
|
|
52
|
+
transform: {
|
|
53
|
+
paths: [],
|
|
54
|
+
mapper: identity,
|
|
55
|
+
},
|
|
44
56
|
},
|
|
45
57
|
types: {
|
|
46
58
|
explicit: false,
|
package/src/errors.json
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
1
3
|
{
|
|
2
4
|
"module": "primate",
|
|
3
5
|
"errors": {
|
|
@@ -6,6 +8,11 @@
|
|
|
6
8
|
"fix": "use a different content type or fix body",
|
|
7
9
|
"level": "Warn"
|
|
8
10
|
},
|
|
11
|
+
"DoubleFileExtension": {
|
|
12
|
+
"message": "double file extension {0}",
|
|
13
|
+
"fix": "unload one of the two handlers registering the file extension",
|
|
14
|
+
"level": "Error"
|
|
15
|
+
},
|
|
9
16
|
"DoubleModule": {
|
|
10
17
|
"message": "double module {0} in {1}",
|
|
11
18
|
"fix": "load {0} only once",
|
|
@@ -58,12 +65,12 @@
|
|
|
58
65
|
},
|
|
59
66
|
"MismatchedPath": {
|
|
60
67
|
"message": "mismatched path {0}: {1}",
|
|
61
|
-
"fix": "
|
|
68
|
+
"fix": "fix the type or the caller",
|
|
62
69
|
"level": "Info"
|
|
63
70
|
},
|
|
64
71
|
"MismatchedType": {
|
|
65
72
|
"message": "mismatched type: {0}",
|
|
66
|
-
"fix": "
|
|
73
|
+
"fix": "fix the type or the caller",
|
|
67
74
|
"level": "Info"
|
|
68
75
|
},
|
|
69
76
|
"ModuleHasNoHooks": {
|
|
@@ -88,7 +95,7 @@
|
|
|
88
95
|
},
|
|
89
96
|
"NoFileForPath": {
|
|
90
97
|
"message": "no file for {0}",
|
|
91
|
-
"fix": "
|
|
98
|
+
"fix": "create a file at {1}{0}",
|
|
92
99
|
"level": "Info"
|
|
93
100
|
},
|
|
94
101
|
"NoHandlerForExtension": {
|
|
@@ -98,7 +105,7 @@
|
|
|
98
105
|
},
|
|
99
106
|
"NoRouteToPath": {
|
|
100
107
|
"message": "no {0} route to {1}",
|
|
101
|
-
"fix": "
|
|
108
|
+
"fix": "create a {0} route function at {2}.js",
|
|
102
109
|
"level": "Info"
|
|
103
110
|
},
|
|
104
111
|
"ReservedTypeName": {
|
package/src/handlers/html.js
CHANGED
|
@@ -2,22 +2,19 @@ import {Response, Status, MediaType} from "runtime-compat/http";
|
|
|
2
2
|
|
|
3
3
|
const script = /(?<=<script)>(?<code>.*?)(?=<\/script>)/gus;
|
|
4
4
|
const style = /(?<=<style)>(?<code>.*?)(?=<\/style>)/gus;
|
|
5
|
+
const remove = /<(?<tag>script|style)>.*?<\/\k<tag>>/gus;
|
|
6
|
+
const inline = true;
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
.map(({groups: {code}}) => publish({code, inline: true})));
|
|
9
|
-
await Promise.all([...html.matchAll(style)]
|
|
10
|
-
.map(({groups: {code}}) => publish({code, type: "style", inline: true})));
|
|
11
|
-
return html.replaceAll(/<(?<tag>script|style)>.*?<\/\k<tag>>/gus, _ => "");
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export default (component, options = {}) => {
|
|
15
|
-
const {status = Status.OK, partial = false, load = false} = options;
|
|
8
|
+
export default (name, options = {}) => {
|
|
9
|
+
const {status = Status.OK, partial = false} = options;
|
|
16
10
|
|
|
17
11
|
return async app => {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
const html = await app.path.components.join(name).text();
|
|
13
|
+
await Promise.all([...html.matchAll(script)]
|
|
14
|
+
.map(({groups: {code}}) => app.publish({code, inline})));
|
|
15
|
+
await Promise.all([...html.matchAll(style)]
|
|
16
|
+
.map(({groups: {code}}) => app.publish({code, type: "style", inline})));
|
|
17
|
+
const body = html.replaceAll(remove, _ => "");
|
|
21
18
|
// needs to happen before app.render()
|
|
22
19
|
const headers = app.headers();
|
|
23
20
|
|
package/src/handlers/view.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import errors from "../errors.js";
|
|
2
2
|
|
|
3
3
|
export default (name, props, options) => async (app, ...rest) => {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
?? errors.NoHandlerForExtension.throw(ending, name);
|
|
4
|
+
const extension = name.slice(name.lastIndexOf(".") + 1);
|
|
5
|
+
return app.handlers[extension]?.(name, props, options)(app, ...rest)
|
|
6
|
+
?? errors.NoHandlerForExtension.throw(extension, name);
|
|
8
7
|
};
|
package/src/hooks/bundle.js
CHANGED
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {cascade} from "runtime-compat/async";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
const {paths, config, build} = app;
|
|
5
|
-
if (await paths.static.exists) {
|
|
6
|
-
// copy static files to build/client/static
|
|
7
|
-
await File.copy(paths.static, build.paths.client.join(config.build.static));
|
|
8
|
-
}
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export default async (app, bundle) => {
|
|
12
|
-
await pre(app);
|
|
13
|
-
if (bundle) {
|
|
14
|
-
app.log.info("running bundle hooks", {module: "primate"});
|
|
15
|
-
await [...app.modules.bundle, _ => _]
|
|
16
|
-
.reduceRight((acc, handler) => input => handler(input, acc))(app);
|
|
17
|
-
}
|
|
18
|
-
};
|
|
3
|
+
export default app => cascade(app.modules.bundle)(app);
|
package/src/hooks/compile.js
CHANGED
|
@@ -1,29 +1,39 @@
|
|
|
1
|
+
import {Path} from "runtime-compat/fs";
|
|
2
|
+
import {cascade} from "runtime-compat/async";
|
|
1
3
|
import copy_includes from "./copy_includes.js";
|
|
4
|
+
import cwd from "../cwd.js";
|
|
5
|
+
|
|
6
|
+
const html = /^.*.html$/u;
|
|
7
|
+
const defaults = cwd(import.meta, 2).join("defaults");
|
|
2
8
|
|
|
3
9
|
const pre = async app => {
|
|
4
|
-
const {
|
|
10
|
+
const {config: {location}, path} = app;
|
|
5
11
|
|
|
6
12
|
// remove build directory in case exists
|
|
7
|
-
if (await
|
|
8
|
-
await
|
|
13
|
+
if (await path.build.exists) {
|
|
14
|
+
await path.build.file.remove();
|
|
9
15
|
}
|
|
10
|
-
await
|
|
11
|
-
|
|
16
|
+
await Promise.all(["server", "client", "components", "pages"]
|
|
17
|
+
.map(directory => app.runpath(directory).file.create()));
|
|
18
|
+
|
|
19
|
+
// copy framework pages
|
|
20
|
+
await app.stage(defaults, location.pages, html);
|
|
21
|
+
// overwrite transformed pages to build
|
|
22
|
+
await path.pages.exists && await app.stage(path.pages, location.pages, html);
|
|
12
23
|
|
|
13
|
-
if (await
|
|
24
|
+
if (await path.components.exists) {
|
|
14
25
|
// copy all files to build/components
|
|
15
|
-
await app.
|
|
16
|
-
// copy .js files from components to build/server
|
|
17
|
-
|
|
26
|
+
await app.stage(path.components, location.components);
|
|
27
|
+
// copy .js files from components to build/server, since frontend
|
|
28
|
+
// frameworks handle non-js files
|
|
29
|
+
const to = Path.join(location.server, location.components);
|
|
30
|
+
await app.stage(path.components, to, /^.*.js$/u);
|
|
18
31
|
}
|
|
19
32
|
|
|
20
33
|
// copy additional subdirectories to build/server
|
|
21
|
-
await copy_includes(app,
|
|
22
|
-
};
|
|
34
|
+
await copy_includes(app, location.server);
|
|
23
35
|
|
|
24
|
-
|
|
25
|
-
await pre(app);
|
|
26
|
-
app.log.info("running compile hooks", {module: "primate"});
|
|
27
|
-
await [...app.modules.compile, _ => _]
|
|
28
|
-
.reduceRight((acc, handler) => input => handler(input, acc))(app);
|
|
36
|
+
return app;
|
|
29
37
|
};
|
|
38
|
+
|
|
39
|
+
export default async app => cascade(app.modules.compile)(await pre(app));
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import {Path} from "runtime-compat/fs";
|
|
2
2
|
|
|
3
3
|
export default async (app, type, post = () => undefined) => {
|
|
4
4
|
const {config} = app;
|
|
5
5
|
const {build} = config;
|
|
6
6
|
const {includes} = build;
|
|
7
7
|
|
|
8
|
-
const reserved =
|
|
8
|
+
const reserved = Object.values(app.config.location);
|
|
9
9
|
|
|
10
10
|
if (Array.isArray(includes)) {
|
|
11
11
|
await Promise.all(includes
|
|
@@ -14,9 +14,8 @@ export default async (app, type, post = () => undefined) => {
|
|
|
14
14
|
.map(async include => {
|
|
15
15
|
const path = app.root.join(include);
|
|
16
16
|
if (await path.exists) {
|
|
17
|
-
const to =
|
|
18
|
-
await
|
|
19
|
-
await app.copy(path, to);
|
|
17
|
+
const to = Path.join(type, include);
|
|
18
|
+
await app.stage(path, to);
|
|
20
19
|
await post(to);
|
|
21
20
|
}
|
|
22
21
|
}));
|
package/src/hooks/exports.js
CHANGED
package/src/hooks/handle.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {Response, Status, MediaType} from "runtime-compat/http";
|
|
2
|
-
import {tryreturn} from "runtime-compat/async";
|
|
2
|
+
import {cascade, tryreturn} from "runtime-compat/async";
|
|
3
3
|
import {respond} from "./respond/exports.js";
|
|
4
4
|
import {invalid} from "./route.js";
|
|
5
5
|
import {error as clientError} from "../handlers/exports.js";
|
|
@@ -9,18 +9,18 @@ const {NoFileForPath} = _errors;
|
|
|
9
9
|
const guardError = Symbol("guardError");
|
|
10
10
|
|
|
11
11
|
export default app => {
|
|
12
|
-
const {config: {http: {static: {root}},
|
|
12
|
+
const {config: {http: {static: {root}}, location}} = app;
|
|
13
13
|
|
|
14
|
-
const
|
|
14
|
+
const as_route = async request => {
|
|
15
15
|
const {pathname} = request.url;
|
|
16
16
|
// if NoFileForPath is thrown, this will remain undefined
|
|
17
|
-
let
|
|
17
|
+
let error_handler = app.error.default;
|
|
18
18
|
|
|
19
19
|
return tryreturn(async _ => {
|
|
20
20
|
const {path, guards, errors, layouts, handler} = invalid(pathname)
|
|
21
|
-
? NoFileForPath.throw(pathname,
|
|
21
|
+
? NoFileForPath.throw(pathname, location.static)
|
|
22
22
|
: await app.route(request);
|
|
23
|
-
|
|
23
|
+
error_handler = errors?.at(-1);
|
|
24
24
|
|
|
25
25
|
// handle guards
|
|
26
26
|
try {
|
|
@@ -43,21 +43,20 @@ export default app => {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// handle request
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
return (await respond(await handlers({...request, path})))(app, {
|
|
46
|
+
const response = cascade(app.modules.route, handler)({...request, path});
|
|
47
|
+
return (await respond(await response))(app, {
|
|
49
48
|
layouts: await Promise.all(layouts.map(layout => layout(request))),
|
|
50
49
|
}, request);
|
|
51
50
|
}).orelse(async error => {
|
|
52
51
|
app.log.auto(error);
|
|
53
52
|
|
|
54
53
|
// the +error.js page itself could fail
|
|
55
|
-
return tryreturn(_ => respond(
|
|
54
|
+
return tryreturn(_ => respond(error_handler(request))(app, {}, request))
|
|
56
55
|
.orelse(_ => clientError()(app, {}, request));
|
|
57
56
|
});
|
|
58
57
|
};
|
|
59
58
|
|
|
60
|
-
const
|
|
59
|
+
const as_asset = async file => new Response(file.readable, {
|
|
61
60
|
status: Status.OK,
|
|
62
61
|
headers: {
|
|
63
62
|
"Content-Type": MediaType.resolve(file.name),
|
|
@@ -65,22 +64,23 @@ export default app => {
|
|
|
65
64
|
},
|
|
66
65
|
});
|
|
67
66
|
|
|
67
|
+
const client = app.runpath(location.client);
|
|
68
68
|
const handle = async request => {
|
|
69
69
|
const {pathname} = request.url;
|
|
70
70
|
if (pathname.startsWith(root)) {
|
|
71
71
|
const debased = pathname.replace(root, _ => "");
|
|
72
|
-
const {client} = app.build.paths;
|
|
73
72
|
// try static first
|
|
74
|
-
const
|
|
75
|
-
if (await
|
|
76
|
-
return asset
|
|
73
|
+
const asset = client.join(location.static, debased);
|
|
74
|
+
if (await asset.isFile) {
|
|
75
|
+
return as_asset(asset.file);
|
|
76
|
+
}
|
|
77
|
+
const path = client.join(debased);
|
|
78
|
+
if (await path.isFile) {
|
|
79
|
+
return as_asset(path.file);
|
|
77
80
|
}
|
|
78
|
-
const _app = client.join(debased);
|
|
79
|
-
return await _app.isFile ? asset(_app.file) : route(request);
|
|
80
81
|
}
|
|
81
|
-
return
|
|
82
|
+
return as_route(request);
|
|
82
83
|
};
|
|
83
84
|
|
|
84
|
-
return
|
|
85
|
-
.reduceRight((next, last) => input => last(input, next));
|
|
85
|
+
return cascade(app.modules.handle, handle);
|
|
86
86
|
};
|
package/src/hooks/parse.js
CHANGED
|
@@ -6,10 +6,13 @@ import errors from "../errors.js";
|
|
|
6
6
|
|
|
7
7
|
const {APPLICATION_FORM_URLENCODED, APPLICATION_JSON} = MediaType;
|
|
8
8
|
|
|
9
|
+
const {decodeURIComponent: decode} = globalThis;
|
|
10
|
+
const deslash = url => url.replaceAll(/(?<!http:)\/{2,}/gu, _ => "/");
|
|
11
|
+
|
|
9
12
|
const contents = {
|
|
10
13
|
[APPLICATION_FORM_URLENCODED]: body => from(body.split("&")
|
|
11
14
|
.map(part => part.split("=")
|
|
12
|
-
.map(subpart =>
|
|
15
|
+
.map(subpart => decode(subpart).replaceAll("+", " ")))),
|
|
13
16
|
[APPLICATION_JSON]: body => JSON.parse(body),
|
|
14
17
|
};
|
|
15
18
|
|
|
@@ -19,7 +22,7 @@ const content = (type, body) =>
|
|
|
19
22
|
|
|
20
23
|
export default dispatch => async original => {
|
|
21
24
|
const {headers} = original;
|
|
22
|
-
const url = new URL(original.url);
|
|
25
|
+
const url = new URL(deslash(decode(original.url)));
|
|
23
26
|
const body = await stringify(original.body);
|
|
24
27
|
const cookies = headers.get("cookie");
|
|
25
28
|
|
package/src/hooks/publish.js
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import {Path} from "runtime-compat/fs";
|
|
2
|
-
import {
|
|
2
|
+
import {cascade} from "runtime-compat/async";
|
|
3
3
|
import copy_includes from "./copy_includes.js";
|
|
4
4
|
|
|
5
5
|
const post = async app => {
|
|
6
|
-
const {config,
|
|
7
|
-
|
|
6
|
+
const {config: {location, http: {static: {root}}}, path} = app;
|
|
7
|
+
|
|
8
8
|
{
|
|
9
9
|
// after hook, publish a zero assumptions app.js (no css imports)
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
const src = new Path(root, app.config.build.index);
|
|
11
|
+
|
|
12
|
+
await app.publish({
|
|
13
|
+
code: app.exports.filter(({type}) => type === "script")
|
|
14
|
+
.map(({code}) => code).join(""),
|
|
15
|
+
src,
|
|
16
|
+
type: "module",
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (await path.components.exists) {
|
|
20
|
+
// copy .js files from components to build/client, since frontend
|
|
21
|
+
// frameworks handle non-js files
|
|
22
|
+
const to = Path.join(location.client, location.components);
|
|
23
|
+
await app.stage(path.components, to, /^.*.js$/u);
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
const imports = {...app.importmaps, app: src.path};
|
|
@@ -25,34 +31,33 @@ const post = async app => {
|
|
|
25
31
|
});
|
|
26
32
|
}
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
await
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
if (await path.static.exists) {
|
|
35
|
+
// copy static files to build/static
|
|
36
|
+
await app.stage(path.static, new Path(location.client, location.static));
|
|
37
|
+
|
|
38
|
+
// publish JavaScript and CSS files
|
|
39
|
+
const imports = await Path.collect(path.static, /\.(?:js|css)$/u);
|
|
40
|
+
await Promise.all(imports.map(async file => {
|
|
41
|
+
const code = await file.text();
|
|
42
|
+
const src = `/${file.name}`;
|
|
43
|
+
const type = file.extension === ".css" ? "style" : "module";
|
|
44
|
+
// already copied in `app.stage`
|
|
45
|
+
await app.publish({src, code, type, copy: false});
|
|
46
|
+
type === "style" && app.export({type,
|
|
47
|
+
code: `import "../${location.client}/${location.static}${src}";`});
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
41
50
|
|
|
42
|
-
const source = `${app.build.paths.client}`;
|
|
43
|
-
const {root} = app.config.http.static;
|
|
44
51
|
// copy additional subdirectories to build/client
|
|
45
|
-
|
|
52
|
+
const client = app.runpath(location.client);
|
|
53
|
+
await copy_includes(app, location.client, async to =>
|
|
46
54
|
Promise.all((await to.collect(/\.js$/u)).map(async script => {
|
|
47
|
-
const src = new Path(root, script.path.replace(
|
|
55
|
+
const src = new Path(root, script.path.replace(client, _ => ""));
|
|
48
56
|
await app.publish({src, code: await script.text(), type: "module"});
|
|
49
57
|
}))
|
|
50
58
|
);
|
|
51
|
-
};
|
|
52
59
|
|
|
53
|
-
|
|
54
|
-
app.log.info("running publish hooks", {module: "primate"});
|
|
55
|
-
await [...app.modules.publish, identity]
|
|
56
|
-
.reduceRight((acc, handler) => input => handler(input, acc))(app);
|
|
57
|
-
await post(app);
|
|
60
|
+
return app;
|
|
58
61
|
};
|
|
62
|
+
|
|
63
|
+
export default async app => post(await cascade(app.modules.publish)(app));
|
package/src/hooks/register.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
.reduceRight((acc, handler) => input => handler(input, acc))(app);
|
|
5
|
-
};
|
|
1
|
+
import {cascade} from "runtime-compat/async";
|
|
2
|
+
|
|
3
|
+
export default app => cascade(app.modules.register)(app);
|
package/src/hooks/route.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {from} from "runtime-compat/object";
|
|
2
2
|
import {tryreturn} from "runtime-compat/sync";
|
|
3
3
|
import errors from "../errors.js";
|
|
4
4
|
import validate from "../validate.js";
|
|
@@ -9,39 +9,45 @@ const ieq = (left, right) => left.toLowerCase() === right.toLowerCase();
|
|
|
9
9
|
/* routes may not contain dots */
|
|
10
10
|
export const invalid = route => /\./u.test(route);
|
|
11
11
|
|
|
12
|
+
const deroot = pathname => pathname.endsWith("/") && pathname !== "/"
|
|
13
|
+
? pathname.slice(0, -1) : pathname;
|
|
14
|
+
|
|
12
15
|
export default app => {
|
|
13
|
-
const {types, routes, config: {explicit,
|
|
16
|
+
const {types, routes, config: {types: {explicit}, location}} = app;
|
|
17
|
+
|
|
18
|
+
const to_path = (route, pathname) => app.dispatch(from(Object
|
|
19
|
+
.entries(route.pathname.exec(pathname)?.groups ?? {})
|
|
20
|
+
.map(([name, value]) =>
|
|
21
|
+
[types[name] === undefined || explicit ? name : `${name}$${name}`, value])
|
|
22
|
+
.map(([name, value]) => [name.split("$"), value])
|
|
23
|
+
.map(([[name, type], value]) =>
|
|
24
|
+
[name, type === undefined ? value : validate(types[type], value, name)]
|
|
25
|
+
)));
|
|
14
26
|
|
|
15
|
-
const
|
|
27
|
+
const is_type = (groups, pathname) => Object
|
|
16
28
|
.entries(groups ?? {})
|
|
17
29
|
.map(([name, value]) =>
|
|
18
30
|
[types[name] === undefined || explicit ? name : `${name}$${name}`, value])
|
|
19
31
|
.filter(([name]) => name.includes("$"))
|
|
20
|
-
.map(([name, value]) => [name.split("$")
|
|
21
|
-
.
|
|
22
|
-
tryreturn(_ => validate(types[
|
|
32
|
+
.map(([name, value]) => [name.split("$"), value])
|
|
33
|
+
.map(([[name, type], value]) =>
|
|
34
|
+
tryreturn(_ => [name, validate(types[type], value, name)])
|
|
23
35
|
.orelse(({message}) => errors.MismatchedPath.throw(pathname, message)));
|
|
24
|
-
const
|
|
36
|
+
const is_path = ({route, pathname}) => {
|
|
25
37
|
const result = route.pathname.exec(pathname);
|
|
26
|
-
return result === null ? false :
|
|
38
|
+
return result === null ? false : is_type(result.groups, pathname);
|
|
27
39
|
};
|
|
28
|
-
const
|
|
29
|
-
&&
|
|
40
|
+
const is_method = ({route, method, pathname}) => ieq(route.method, method)
|
|
41
|
+
&& is_path({route, pathname});
|
|
30
42
|
const find = (method, pathname) => routes.find(route =>
|
|
31
|
-
|
|
43
|
+
is_method({route, method, pathname}));
|
|
32
44
|
|
|
33
|
-
const index = path => `${
|
|
34
|
-
const deroot = pathname => pathname.endsWith("/") && pathname !== "/"
|
|
35
|
-
? pathname.slice(0, -1) : pathname;
|
|
45
|
+
const index = path => `${location.routes}${path === "/" ? "/index" : path}`;
|
|
36
46
|
|
|
37
47
|
return ({original: {method}, url}) => {
|
|
38
48
|
const pathname = deroot(url.pathname);
|
|
39
49
|
const route = find(method, pathname) ?? errors.NoRouteToPath
|
|
40
|
-
.throw(method, pathname, index(pathname)
|
|
41
|
-
|
|
42
|
-
const path = app.dispatch(keymap(route.pathname?.exec(pathname).groups,
|
|
43
|
-
key => key.split("$")[0]));
|
|
44
|
-
|
|
45
|
-
return {...route, path};
|
|
50
|
+
.throw(method.toLowerCase(), pathname, index(pathname));
|
|
51
|
+
return {...route, path: to_path(route, pathname)};
|
|
46
52
|
};
|
|
47
53
|
};
|
package/src/hooks/serve.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {cascade} from "runtime-compat/async";
|
|
2
2
|
|
|
3
3
|
export default async (app, server) => {
|
|
4
4
|
app.log.info("running serve hooks", {module: "primate"});
|
|
5
|
-
await
|
|
6
|
-
.reduceRight((next, previous) =>
|
|
7
|
-
input => previous(input, next))({...app, server});
|
|
5
|
+
await cascade(app.modules.serve)({...app, server});
|
|
8
6
|
};
|
package/src/loaders/modules.js
CHANGED
|
@@ -4,13 +4,10 @@ import errors from "../errors.js";
|
|
|
4
4
|
|
|
5
5
|
const filter = (key, array) => array?.flatMap(m => m[key] ?? []) ?? [];
|
|
6
6
|
|
|
7
|
-
const load = (
|
|
8
|
-
|
|
9
|
-
[module, load(app, module.load?.(app) ?? [])]
|
|
10
|
-
).flat();
|
|
11
|
-
};
|
|
7
|
+
const load = (modules = []) => modules.map(module =>
|
|
8
|
+
[module, load(module.load?.() ?? [])]).flat();
|
|
12
9
|
|
|
13
|
-
export default async (
|
|
10
|
+
export default async (log, root, config) => {
|
|
14
11
|
const modules = config.modules ?? [];
|
|
15
12
|
|
|
16
13
|
Array.isArray(modules) || errors.ModulesMustBeArray.throw("modules");
|
|
@@ -23,17 +20,12 @@ export default async (app, root, config) => {
|
|
|
23
20
|
errors.DoubleModule.throw(doubled(names), root.join("primate.config.js"));
|
|
24
21
|
|
|
25
22
|
const hookless = modules.filter(module => !Object.keys(module).some(key =>
|
|
26
|
-
[...Object.keys(hooks), "load"
|
|
27
|
-
hookless.length > 0 && errors.ModuleHasNoHooks.warn(
|
|
23
|
+
[...Object.keys(hooks), "load"].includes(key)));
|
|
24
|
+
hookless.length > 0 && errors.ModuleHasNoHooks.warn(log,
|
|
28
25
|
hookless.map(({name}) => name).join(", "));
|
|
29
26
|
|
|
30
27
|
// collect modules
|
|
31
|
-
const loaded = load(
|
|
32
|
-
|
|
33
|
-
// initialize modules
|
|
34
|
-
await Promise.all(loaded
|
|
35
|
-
.filter(module => module.init !== undefined)
|
|
36
|
-
.map(module => module.init(app)));
|
|
28
|
+
const loaded = load(modules).flat(2);
|
|
37
29
|
|
|
38
30
|
return Object.fromEntries(Object.keys(hooks)
|
|
39
31
|
.map(hook => [hook, filter(hook, loaded)]));
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {Path} from "runtime-compat/fs";
|
|
2
2
|
import errors from "../../errors.js";
|
|
3
|
-
import
|
|
3
|
+
import to_sorted from "../../to_sorted.js";
|
|
4
4
|
|
|
5
5
|
export default type => async (log, directory, load) => {
|
|
6
6
|
const filter = path => new RegExp(`^\\+${type}.js$`, "u").test(path.name);
|
|
7
7
|
|
|
8
8
|
const replace = new RegExp(`\\+${type}`, "u");
|
|
9
|
-
const objects =
|
|
9
|
+
const objects = to_sorted((await load({log, directory, filter, warn: false}))
|
|
10
10
|
.map(([name, object]) => [name.replace(replace, () => ""), object]),
|
|
11
11
|
([a], [b]) => a.length - b.length);
|
|
12
12
|
|
package/src/loaders/routes.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {tryreturn} from "runtime-compat/sync";
|
|
2
|
-
import {from
|
|
2
|
+
import {from} from "runtime-compat/object";
|
|
3
3
|
import errors from "../errors.js";
|
|
4
4
|
import {invalid} from "../hooks/route.js";
|
|
5
5
|
import {default as fs, doubled} from "./common.js";
|
package/src/run.js
CHANGED
|
@@ -10,7 +10,7 @@ import defaults from "./defaults/primate.config.js";
|
|
|
10
10
|
let logger = new Logger({level: Logger.Warn});
|
|
11
11
|
const {runtime = "node"} = import.meta;
|
|
12
12
|
|
|
13
|
-
const
|
|
13
|
+
const get_config = async root => {
|
|
14
14
|
const name = "primate.config.js";
|
|
15
15
|
const config = root.join(name);
|
|
16
16
|
return await config.exists
|
|
@@ -26,19 +26,16 @@ const getConfig = async root => {
|
|
|
26
26
|
: defaults;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
export default async name => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
bye();
|
|
40
|
-
} else {
|
|
41
|
-
throw error;
|
|
42
|
-
}
|
|
29
|
+
export default async name => tryreturn(async _ => {
|
|
30
|
+
// use module root if possible, fall back to current directory
|
|
31
|
+
const root = await tryreturn(_ => Path.root()).orelse(_ => Path.resolve());
|
|
32
|
+
const config = await get_config(root);
|
|
33
|
+
logger = new Logger(config.logger);
|
|
34
|
+
await command(name)(await app(logger, root, config));
|
|
35
|
+
}).orelse(error => {
|
|
36
|
+
if (error.level === Logger.Error) {
|
|
37
|
+
logger.auto(error);
|
|
38
|
+
return bye();
|
|
43
39
|
}
|
|
44
|
-
|
|
40
|
+
throw error;
|
|
41
|
+
});
|
package/src/start.js
CHANGED
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import {serve, Response, Status} from "runtime-compat/http";
|
|
2
|
-
import {tryreturn} from "runtime-compat/async";
|
|
3
|
-
import {identity} from "runtime-compat/function";
|
|
2
|
+
import {cascade, tryreturn} from "runtime-compat/async";
|
|
4
3
|
import * as hooks from "./hooks/exports.js";
|
|
5
4
|
|
|
6
|
-
export default async (app
|
|
7
|
-
//
|
|
8
|
-
await
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
export default async (app$, deactivated = []) => {
|
|
6
|
+
// run one-time hooks
|
|
7
|
+
const app = await cascade(["init", "register", "compile", "publish", "bundle"]
|
|
8
|
+
.filter(hook => !deactivated.includes(hook))
|
|
9
|
+
.map(hook => async (input, next) => {
|
|
10
|
+
app$.log.info(`running ${hook} hooks`, {module: "primate"});
|
|
11
|
+
return next(await hooks[hook](input));
|
|
12
|
+
}))(app$);
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// publish client-side code
|
|
15
|
-
await hooks.publish(app);
|
|
16
|
-
|
|
17
|
-
// bundle client-side code
|
|
18
|
-
await hooks.bundle(app, operations?.bundle);
|
|
14
|
+
app.route = hooks.route(app);
|
|
15
|
+
app.parse = hooks.parse(app.dispatch);
|
|
19
16
|
|
|
20
17
|
const server = await serve(async request =>
|
|
21
18
|
tryreturn(async _ => hooks.handle(app)(await app.parse(request)))
|
|
@@ -25,8 +22,5 @@ export default async (app, operations = {}) => {
|
|
|
25
22
|
}),
|
|
26
23
|
app.config.http);
|
|
27
24
|
|
|
28
|
-
await
|
|
29
|
-
.reduceRight((acc, handler) => input => handler(input, acc))({
|
|
30
|
-
...app, server,
|
|
31
|
-
});
|
|
25
|
+
await cascade(app.modules.serve)({...app, server});
|
|
32
26
|
};
|
|
File without changes
|