primate 0.24.0 → 0.26.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/package.json +2 -2
- package/src/Logger.js +20 -16
- package/src/app.js +75 -69
- package/src/bin.js +1 -1
- package/src/commands/dev.js +1 -1
- package/src/commands/exports.js +3 -3
- package/src/commands/serve.js +1 -1
- package/src/cwd.js +1 -1
- package/src/defaults/primate.config.js +1 -1
- package/src/dispatch.js +13 -10
- package/src/errors.js +1 -1
- package/src/errors.json +5 -0
- package/src/exports.js +2 -2
- package/src/handlers/error.js +4 -4
- package/src/handlers/exports.js +7 -7
- package/src/handlers/html.js +7 -7
- package/src/handlers/json.js +3 -3
- package/src/handlers/redirect.js +3 -3
- package/src/handlers/stream.js +4 -4
- package/src/handlers/text.js +3 -3
- package/src/hooks/bundle.js +1 -1
- package/src/hooks/copy_includes.js +8 -8
- package/src/hooks/exports.js +9 -9
- package/src/hooks/handle.js +44 -37
- package/src/hooks/init.js +1 -1
- package/src/hooks/parse.js +8 -8
- package/src/hooks/publish.js +8 -42
- package/src/hooks/register.js +73 -2
- package/src/hooks/respond/duck.js +1 -1
- package/src/hooks/respond/exports.js +2 -2
- package/src/hooks/respond/respond.js +4 -4
- package/src/hooks/route.js +11 -11
- package/src/hooks/serve.js +3 -3
- package/src/hooks/stage.js +48 -0
- package/src/loaders/common.js +3 -3
- package/src/loaders/exports.js +3 -3
- package/src/loaders/modules.js +10 -7
- package/src/loaders/routes/exports.js +6 -4
- package/src/loaders/routes/load.js +2 -2
- package/src/loaders/routes/routes.js +2 -2
- package/src/loaders/routes.js +16 -15
- package/src/loaders/types.js +11 -4
- package/src/run.js +8 -8
- package/src/start.js +19 -10
- package/src/validate.js +1 -1
- package/src/hooks/compile.js +0 -45
- package/src/loaders/routes/errors.js +0 -3
- package/src/loaders/routes/guards.js +0 -3
- package/src/loaders/routes/layouts.js +0 -3
package/src/hooks/exports.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export {default as init} from "./init.js";
|
|
2
|
-
export {default as
|
|
3
|
-
export {default as
|
|
4
|
-
export {default as publish} from "./publish.js";
|
|
5
|
-
export {default as bundle} from "./bundle.js";
|
|
6
|
-
export {default as route} from "./route.js";
|
|
7
|
-
export {default as handle} from "./handle.js";
|
|
8
|
-
export {default as parse} from "./parse.js";
|
|
9
|
-
export {default as serve} from "./serve.js";
|
|
1
|
+
export { default as init } from "./init.js";
|
|
2
|
+
export { default as stage } from "./stage.js";
|
|
3
|
+
export { default as register } from "./register.js";
|
|
4
|
+
export { default as publish } from "./publish.js";
|
|
5
|
+
export { default as bundle } from "./bundle.js";
|
|
6
|
+
export { default as route } from "./route.js";
|
|
7
|
+
export { default as handle } from "./handle.js";
|
|
8
|
+
export { default as parse } from "./parse.js";
|
|
9
|
+
export { default as serve } from "./serve.js";
|
package/src/hooks/handle.js
CHANGED
|
@@ -1,52 +1,59 @@
|
|
|
1
|
-
import {Response, Status, MediaType} from "
|
|
2
|
-
import {cascade, tryreturn} from "
|
|
3
|
-
import {respond} from "./respond/exports.js";
|
|
4
|
-
import {invalid} from "./route.js";
|
|
5
|
-
import {error as clientError} from "../handlers/exports.js";
|
|
1
|
+
import { Response, Status, MediaType } from "rcompat/http";
|
|
2
|
+
import { cascade, tryreturn } from "rcompat/async";
|
|
3
|
+
import { respond } from "./respond/exports.js";
|
|
4
|
+
import { invalid } from "./route.js";
|
|
5
|
+
import { error as clientError } from "../handlers/exports.js";
|
|
6
6
|
import _errors from "../errors.js";
|
|
7
|
-
const {NoFileForPath} = _errors;
|
|
7
|
+
const { NoFileForPath } = _errors;
|
|
8
8
|
|
|
9
|
-
const
|
|
9
|
+
const guard_error = Symbol("guard_error");
|
|
10
|
+
const guard = (app, guards) => async (request, next) => {
|
|
11
|
+
// handle guards
|
|
12
|
+
try {
|
|
13
|
+
guards.every(guard => {
|
|
14
|
+
const result = guard(request);
|
|
15
|
+
if (result === true) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
const error = new Error();
|
|
19
|
+
error.result = result;
|
|
20
|
+
error.type = guard_error;
|
|
21
|
+
throw error;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return next(request);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (error.type === guard_error) {
|
|
27
|
+
return (await respond(error.result))(app);
|
|
28
|
+
}
|
|
29
|
+
// rethrow if not guard error
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
10
33
|
|
|
11
34
|
export default app => {
|
|
12
|
-
const {config: {http: {static: {root}}, location}} = app;
|
|
35
|
+
const { config: { http: { static: { root } }, location } } = app;
|
|
13
36
|
|
|
14
37
|
const as_route = async request => {
|
|
15
|
-
const {pathname} = request.url;
|
|
38
|
+
const { pathname } = request.url;
|
|
16
39
|
// if NoFileForPath is thrown, this will remain undefined
|
|
17
40
|
let error_handler = app.error.default;
|
|
18
41
|
|
|
19
42
|
return tryreturn(async _ => {
|
|
20
|
-
const {path, guards, errors, layouts, handler} = invalid(pathname)
|
|
43
|
+
const { path, guards, errors, layouts, handler } = invalid(pathname)
|
|
21
44
|
? NoFileForPath.throw(pathname, location.static)
|
|
22
45
|
: await app.route(request);
|
|
23
46
|
error_handler = errors?.at(-1);
|
|
24
47
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const result = guard(request);
|
|
29
|
-
if (result === true) {
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
const error = new Error();
|
|
33
|
-
error.result = result;
|
|
34
|
-
error.type = guardError;
|
|
35
|
-
throw error;
|
|
36
|
-
});
|
|
37
|
-
} catch (error) {
|
|
38
|
-
if (error.type === guardError) {
|
|
39
|
-
return (await respond(error.result))(app);
|
|
40
|
-
}
|
|
41
|
-
// rethrow if not guard error
|
|
42
|
-
throw error;
|
|
43
|
-
}
|
|
48
|
+
const pathed = { ...request, path };
|
|
49
|
+
|
|
50
|
+
const hooks = [...app.modules.route, guard(app, guards)];
|
|
44
51
|
|
|
45
52
|
// handle request
|
|
46
|
-
const response = (await cascade(
|
|
53
|
+
const response = (await cascade(hooks, handler))(pathed);
|
|
47
54
|
return (await respond(await response))(app, {
|
|
48
55
|
layouts: await Promise.all(layouts.map(layout => layout(request))),
|
|
49
|
-
},
|
|
56
|
+
}, pathed);
|
|
50
57
|
}).orelse(async error => {
|
|
51
58
|
app.log.auto(error);
|
|
52
59
|
|
|
@@ -56,27 +63,27 @@ export default app => {
|
|
|
56
63
|
});
|
|
57
64
|
};
|
|
58
65
|
|
|
59
|
-
const as_asset = async
|
|
66
|
+
const as_asset = async path => new Response(path.stream(), {
|
|
60
67
|
status: Status.OK,
|
|
61
68
|
headers: {
|
|
62
|
-
"Content-Type": MediaType.resolve(
|
|
63
|
-
Etag: await
|
|
69
|
+
"Content-Type": MediaType.resolve(path.name),
|
|
70
|
+
Etag: await path.modified(),
|
|
64
71
|
},
|
|
65
72
|
});
|
|
66
73
|
|
|
67
74
|
const client = app.runpath(location.client);
|
|
68
75
|
const handle = async request => {
|
|
69
|
-
const {pathname} = request.url;
|
|
76
|
+
const { pathname } = request.url;
|
|
70
77
|
if (pathname.startsWith(root)) {
|
|
71
78
|
const debased = pathname.replace(root, _ => "");
|
|
72
79
|
// try static first
|
|
73
80
|
const asset = client.join(location.static, debased);
|
|
74
81
|
if (await asset.isFile) {
|
|
75
|
-
return as_asset(asset
|
|
82
|
+
return as_asset(asset);
|
|
76
83
|
}
|
|
77
84
|
const path = client.join(debased);
|
|
78
85
|
if (await path.isFile) {
|
|
79
|
-
return as_asset(path
|
|
86
|
+
return as_asset(path);
|
|
80
87
|
}
|
|
81
88
|
}
|
|
82
89
|
return as_route(request);
|
package/src/hooks/init.js
CHANGED
package/src/hooks/parse.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {URL, MediaType} from "
|
|
2
|
-
import {tryreturn} from "
|
|
3
|
-
import {stringify} from "
|
|
4
|
-
import {from, valmap} from "
|
|
1
|
+
import { URL, MediaType } from "rcompat/http";
|
|
2
|
+
import { tryreturn } from "rcompat/sync";
|
|
3
|
+
import { stringify } from "rcompat/streams";
|
|
4
|
+
import { from, valmap } from "rcompat/object";
|
|
5
5
|
import errors from "../errors.js";
|
|
6
6
|
|
|
7
|
-
const {APPLICATION_FORM_URLENCODED, APPLICATION_JSON} = MediaType;
|
|
7
|
+
const { APPLICATION_FORM_URLENCODED, APPLICATION_JSON } = MediaType;
|
|
8
8
|
|
|
9
|
-
const {decodeURIComponent: decode} = globalThis;
|
|
9
|
+
const { decodeURIComponent: decode } = globalThis;
|
|
10
10
|
const deslash = url => url.replaceAll(/(?<!http:)\/{2,}/gu, _ => "/");
|
|
11
11
|
|
|
12
12
|
const contents = {
|
|
@@ -21,12 +21,12 @@ const content = (type, body) =>
|
|
|
21
21
|
.orelse(_ => errors.CannotParseBody.throw(body, type));
|
|
22
22
|
|
|
23
23
|
export default dispatch => async original => {
|
|
24
|
-
const {headers} = original;
|
|
24
|
+
const { headers } = original;
|
|
25
25
|
const url = new URL(deslash(decode(original.url)));
|
|
26
26
|
const body = await stringify(original.body);
|
|
27
27
|
const cookies = headers.get("cookie");
|
|
28
28
|
|
|
29
|
-
return {original, url,
|
|
29
|
+
return { original, url,
|
|
30
30
|
...valmap({
|
|
31
31
|
body: [content(headers.get("content-type"), body), body],
|
|
32
32
|
query: [from(url.searchParams), url.search],
|
package/src/hooks/publish.js
CHANGED
|
@@ -1,60 +1,26 @@
|
|
|
1
|
-
import {Path} from "
|
|
2
|
-
import {cascade} from "
|
|
3
|
-
import {stringify} from "
|
|
4
|
-
import copy_includes from "./copy_includes.js";
|
|
1
|
+
import { Path } from "rcompat/fs";
|
|
2
|
+
import { cascade } from "rcompat/async";
|
|
3
|
+
import { stringify } from "rcompat/object";
|
|
5
4
|
|
|
6
5
|
const post = async app => {
|
|
7
|
-
const {config: {
|
|
6
|
+
const { config: { http: { static: { root } } } } = app;
|
|
8
7
|
|
|
9
8
|
{
|
|
10
9
|
// after hook, publish a zero assumptions app.js (no css imports)
|
|
11
10
|
const src = new Path(root, app.config.build.index);
|
|
12
11
|
|
|
13
12
|
await app.publish({
|
|
14
|
-
code: app.exports.filter(({type}) => type === "script")
|
|
15
|
-
.map(({code}) => code).join(""),
|
|
13
|
+
code: app.exports.filter(({ type }) => type === "script")
|
|
14
|
+
.map(({ code }) => code).join(""),
|
|
16
15
|
src,
|
|
17
16
|
type: "module",
|
|
18
17
|
});
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
// copy .js files from components to build/client/components, since
|
|
22
|
-
// frontend frameworks handle non-js files
|
|
23
|
-
const to = Path.join(location.client, location.components);
|
|
24
|
-
await app.stage(path.components, to, /^.*.js$/u);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const imports = {...app.importmaps, app: src.path};
|
|
19
|
+
const imports = { ...app.importmaps, app: src.path };
|
|
28
20
|
const type = "importmap";
|
|
29
|
-
await app.publish({inline: true, code: stringify({imports}), type});
|
|
21
|
+
await app.publish({ inline: true, code: stringify({ imports }), type });
|
|
30
22
|
}
|
|
31
23
|
|
|
32
|
-
if (await path.static.exists) {
|
|
33
|
-
// copy static files to build/client/static
|
|
34
|
-
await app.stage(path.static, new Path(location.client, location.static));
|
|
35
|
-
|
|
36
|
-
// publish JavaScript and CSS files
|
|
37
|
-
const imports = await Path.collect(path.static, /\.(?:js|css)$/u);
|
|
38
|
-
await Promise.all(imports.map(async file => {
|
|
39
|
-
const code = await file.text();
|
|
40
|
-
const src = file.debase(path.static);
|
|
41
|
-
const type = file.extension === ".css" ? "style" : "module";
|
|
42
|
-
// already copied in `app.stage`
|
|
43
|
-
await app.publish({src, code, type, copy: false});
|
|
44
|
-
type === "style" && app.export({type,
|
|
45
|
-
code: `import "./${location.static}${src}";`});
|
|
46
|
-
}));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// copy additional subdirectories to build/client
|
|
50
|
-
const client = app.runpath(location.client);
|
|
51
|
-
await copy_includes(app, location.client, async to =>
|
|
52
|
-
Promise.all((await to.collect(/\.js$/u)).map(async script => {
|
|
53
|
-
const src = new Path(root, script.path.replace(client, _ => ""));
|
|
54
|
-
await app.publish({src, code: await script.text(), type: "module"});
|
|
55
|
-
}))
|
|
56
|
-
);
|
|
57
|
-
|
|
58
24
|
return app;
|
|
59
25
|
};
|
|
60
26
|
|
package/src/hooks/register.js
CHANGED
|
@@ -1,3 +1,74 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Path } from "rcompat/fs";
|
|
2
|
+
import { cascade } from "rcompat/async";
|
|
3
|
+
import cwd from "../cwd.js";
|
|
4
|
+
import copy_includes from "./copy_includes.js";
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
const html = /^.*.html$/u;
|
|
7
|
+
const defaults = cwd(import.meta, 2).join("defaults");
|
|
8
|
+
|
|
9
|
+
const pre = async app => {
|
|
10
|
+
const { config: { location: { pages, client, components } }, path } = app;
|
|
11
|
+
|
|
12
|
+
await Promise.all(["server", "client", "pages"]
|
|
13
|
+
.map(directory => app.runpath(directory).create()));
|
|
14
|
+
|
|
15
|
+
// copy framework pages
|
|
16
|
+
await app.stage(defaults, pages, html);
|
|
17
|
+
// overwrite transformed pages to build
|
|
18
|
+
await path.pages.exists() && await app.stage(path.pages, pages, html);
|
|
19
|
+
|
|
20
|
+
if (await path.components.exists()) {
|
|
21
|
+
// copy .js files from components to build/client/components, since
|
|
22
|
+
// frontend frameworks handle non-js files
|
|
23
|
+
const target = Path.join(client, components);
|
|
24
|
+
await app.stage(path.components, target, /^.*.js$/u);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return app;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const post = async app => {
|
|
31
|
+
const { config: { location, http: { static: { root } } }, path } = app;
|
|
32
|
+
|
|
33
|
+
if (await path.static.exists()) {
|
|
34
|
+
// copy static files to build/server/static
|
|
35
|
+
await app.stage(path.static, new Path(location.server, location.static));
|
|
36
|
+
|
|
37
|
+
// copy static files to build/client/static
|
|
38
|
+
await app.stage(path.static, new Path(location.client, location.static));
|
|
39
|
+
|
|
40
|
+
// publish JavaScript and CSS files
|
|
41
|
+
const imports = await Path.collect(path.static, /\.(?:js|css)$/u);
|
|
42
|
+
await Promise.all(imports.map(async file => {
|
|
43
|
+
const code = await file.text();
|
|
44
|
+
const src = file.debase(path.static);
|
|
45
|
+
const type = file.extension === "css" ? "style" : "module";
|
|
46
|
+
// already copied in `app.stage`
|
|
47
|
+
await app.publish({ src, code, type, copy: false });
|
|
48
|
+
type === "style" && app.export({ type,
|
|
49
|
+
code: `import "./${location.static}${src}";` });
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// copy additional subdirectories to build/server
|
|
54
|
+
await copy_includes(app, location.server);
|
|
55
|
+
|
|
56
|
+
// copy additional subdirectories to build/client
|
|
57
|
+
const client = app.runpath(location.client);
|
|
58
|
+
await copy_includes(app, location.client, async to =>
|
|
59
|
+
Promise.all((await to.collect(/\.js$/u)).map(async script => {
|
|
60
|
+
const src = new Path(root, script.path.replace(client, _ => ""));
|
|
61
|
+
await app.publish({ src, code: await script.text(), type: "module" });
|
|
62
|
+
})),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const components = await app.path.components.collect();
|
|
66
|
+
|
|
67
|
+
// from the build directory, compile to server and client
|
|
68
|
+
await Promise.all(components.map(component => app.compile(component)));
|
|
69
|
+
|
|
70
|
+
return app;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export default async app =>
|
|
74
|
+
post(await (await cascade(app.modules.register))(await pre(app)));
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {isResponse} from "./duck.js";
|
|
2
|
-
export {default as respond} from "./respond.js";
|
|
1
|
+
export { isResponse } from "./duck.js";
|
|
2
|
+
export { default as respond } from "./respond.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {Blob} from "
|
|
2
|
-
import {URL} from "
|
|
3
|
-
import {text, json, stream, redirect} from "primate";
|
|
4
|
-
import {isResponse as isResponseDuck} from "./duck.js";
|
|
1
|
+
import { Blob } from "rcompat/fs";
|
|
2
|
+
import { URL } from "rcompat/http";
|
|
3
|
+
import { text, json, stream, redirect } from "primate";
|
|
4
|
+
import { isResponse as isResponseDuck } from "./duck.js";
|
|
5
5
|
|
|
6
6
|
const isText = value => {
|
|
7
7
|
if (typeof value === "string") {
|
package/src/hooks/route.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {from} from "
|
|
2
|
-
import {tryreturn} from "
|
|
1
|
+
import { from } from "rcompat/object";
|
|
2
|
+
import { tryreturn } from "rcompat/sync";
|
|
3
3
|
import errors from "../errors.js";
|
|
4
4
|
import validate from "../validate.js";
|
|
5
5
|
|
|
@@ -13,7 +13,7 @@ const deroot = pathname => pathname.endsWith("/") && pathname !== "/"
|
|
|
13
13
|
? pathname.slice(0, -1) : pathname;
|
|
14
14
|
|
|
15
15
|
export default app => {
|
|
16
|
-
const {types, routes, config: {types: {explicit}, location}} = app;
|
|
16
|
+
const { types, routes, config: { types: { explicit }, location } } = app;
|
|
17
17
|
|
|
18
18
|
const to_path = (route, pathname) => app.dispatch(from(Object
|
|
19
19
|
.entries(route.pathname.exec(pathname)?.groups ?? {})
|
|
@@ -21,7 +21,7 @@ export default app => {
|
|
|
21
21
|
[types[name] === undefined || explicit ? name : `${name}$${name}`, value])
|
|
22
22
|
.map(([name, value]) => [name.split("$"), value])
|
|
23
23
|
.map(([[name, type], value]) =>
|
|
24
|
-
[name, type === undefined ? value : validate(types[type], value, name)]
|
|
24
|
+
[name, type === undefined ? value : validate(types[type], value, name)],
|
|
25
25
|
)));
|
|
26
26
|
|
|
27
27
|
const is_type = (groups, pathname) => Object
|
|
@@ -32,22 +32,22 @@ export default app => {
|
|
|
32
32
|
.map(([name, value]) => [name.split("$"), value])
|
|
33
33
|
.map(([[name, type], value]) =>
|
|
34
34
|
tryreturn(_ => [name, validate(types[type], value, name)])
|
|
35
|
-
.orelse(({message}) => errors.MismatchedPath.throw(pathname, message)));
|
|
36
|
-
const is_path = ({route, pathname}) => {
|
|
35
|
+
.orelse(({ message }) => errors.MismatchedPath.throw(pathname, message)));
|
|
36
|
+
const is_path = ({ route, pathname }) => {
|
|
37
37
|
const result = route.pathname.exec(pathname);
|
|
38
38
|
return result === null ? false : is_type(result.groups, pathname);
|
|
39
39
|
};
|
|
40
|
-
const is_method = ({route, method, pathname}) => ieq(route.method, method)
|
|
41
|
-
&& is_path({route, pathname});
|
|
40
|
+
const is_method = ({ route, method, pathname }) => ieq(route.method, method)
|
|
41
|
+
&& is_path({ route, pathname });
|
|
42
42
|
const find = (method, pathname) => routes.find(route =>
|
|
43
|
-
is_method({route, method, pathname}));
|
|
43
|
+
is_method({ route, method, pathname }));
|
|
44
44
|
|
|
45
45
|
const index = path => `${location.routes}${path === "/" ? "/index" : path}`;
|
|
46
46
|
|
|
47
|
-
return ({original: {method}, url}) => {
|
|
47
|
+
return ({ original: { method }, url }) => {
|
|
48
48
|
const pathname = deroot(url.pathname);
|
|
49
49
|
const route = find(method, pathname) ?? errors.NoRouteToPath
|
|
50
50
|
.throw(method.toLowerCase(), pathname, index(pathname));
|
|
51
|
-
return {...route, path: to_path(route, pathname)};
|
|
51
|
+
return { ...route, path: to_path(route, pathname) };
|
|
52
52
|
};
|
|
53
53
|
};
|
package/src/hooks/serve.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {cascade} from "
|
|
1
|
+
import { cascade } from "rcompat/async";
|
|
2
2
|
|
|
3
3
|
export default async (app, server) => {
|
|
4
|
-
app.log.info("running serve hooks", {module: "primate"});
|
|
5
|
-
await (await cascade(app.modules.serve))({...app, server});
|
|
4
|
+
app.log.info("running serve hooks", { module: "primate" });
|
|
5
|
+
await (await cascade(app.modules.serve))({ ...app, server });
|
|
6
6
|
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { cascade } from "rcompat/async";
|
|
2
|
+
import dispatch from "../dispatch.js";
|
|
3
|
+
import * as loaders from "../loaders/exports.js";
|
|
4
|
+
import { doubled } from "../loaders/common.js";
|
|
5
|
+
import errors from "../errors.js";
|
|
6
|
+
|
|
7
|
+
const pre = async app => {
|
|
8
|
+
// remove build directory in case exists
|
|
9
|
+
await app.path.build.remove();
|
|
10
|
+
|
|
11
|
+
return { ...app };
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const post = async app => {
|
|
15
|
+
const { config: { location } } = app;
|
|
16
|
+
|
|
17
|
+
// stage routes
|
|
18
|
+
await app.runpath(location.routes).create();
|
|
19
|
+
const double = doubled((await app.path.routes.collect())
|
|
20
|
+
.map(path => path.debase(app.path.routes))
|
|
21
|
+
.map(path => `${path}`.slice(1, -path.extension.length - 1)));
|
|
22
|
+
double && errors.DoubleRoute.throw(double);
|
|
23
|
+
|
|
24
|
+
await app.stage(app.path.routes, location.routes);
|
|
25
|
+
if (await app.path.types.exists()) {
|
|
26
|
+
await app.stage(app.path.types, location.types);
|
|
27
|
+
}
|
|
28
|
+
const types = await loaders.types(app.log, app.runpath(location.types));
|
|
29
|
+
const staged = app.runpath(location.routes);
|
|
30
|
+
for (const path of await staged.collect()) {
|
|
31
|
+
await app.extensions[path.extension]
|
|
32
|
+
?.route(staged, path.debase(`${staged}/`), types);
|
|
33
|
+
}
|
|
34
|
+
const routes = await loaders.routes(app);
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
...app,
|
|
38
|
+
types,
|
|
39
|
+
routes,
|
|
40
|
+
dispatch: dispatch(app.types),
|
|
41
|
+
layout: {
|
|
42
|
+
depth: Math.max(...routes.map(({ layouts }) => layouts.length)) + 1,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default async app =>
|
|
48
|
+
post(await (await cascade(app.modules.stage))(await pre(app)));
|
package/src/loaders/common.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {Path} from "
|
|
2
|
-
import {identity} from "
|
|
1
|
+
import { Path } from "rcompat/fs";
|
|
2
|
+
import { identity } from "rcompat/function";
|
|
3
3
|
import errors from "../errors.js";
|
|
4
4
|
|
|
5
5
|
const ending = ".js";
|
|
@@ -17,7 +17,7 @@ export default async ({
|
|
|
17
17
|
warn = true,
|
|
18
18
|
} = {}) => {
|
|
19
19
|
const objects = directory === undefined ? [] : await Promise.all(
|
|
20
|
-
(await Path.collect(directory, /^.*.js$/u, {recursive}))
|
|
20
|
+
(await Path.collect(directory, /^.*.js$/u, { recursive }))
|
|
21
21
|
.filter(filter)
|
|
22
22
|
.map(async path => [
|
|
23
23
|
`${path}`.replace(directory, _ => "").slice(1, -ending.length),
|
package/src/loaders/exports.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {default as modules} from "./modules.js";
|
|
2
|
-
export {default as routes} from "./routes.js";
|
|
3
|
-
export {default as types} from "./types.js";
|
|
1
|
+
export { default as modules } from "./modules.js";
|
|
2
|
+
export { default as routes } from "./routes.js";
|
|
3
|
+
export { default as types } from "./types.js";
|
package/src/loaders/modules.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as hooks from "../hooks/exports.js";
|
|
2
|
-
import {doubled} from "./common.js";
|
|
2
|
+
import { doubled } from "./common.js";
|
|
3
3
|
import errors from "../errors.js";
|
|
4
4
|
|
|
5
5
|
const filter = (key, array) => array?.flatMap(m => m[key] ?? []) ?? [];
|
|
@@ -12,21 +12,24 @@ export default async (log, root, config) => {
|
|
|
12
12
|
|
|
13
13
|
Array.isArray(modules) || errors.ModulesMustBeArray.throw("modules");
|
|
14
14
|
|
|
15
|
-
modules.some((
|
|
15
|
+
modules.some(({ name }, n) => name === undefined &&
|
|
16
16
|
errors.ModuleHasNoName.throw(n));
|
|
17
17
|
|
|
18
|
-
const names = modules.map(({name}) => name);
|
|
18
|
+
const names = modules.map(({ name }) => name);
|
|
19
19
|
new Set(names).size !== modules.length &&
|
|
20
20
|
errors.DoubleModule.throw(doubled(names), root.join("primate.config.js"));
|
|
21
21
|
|
|
22
22
|
const hookless = modules.filter(module => !Object.keys(module).some(key =>
|
|
23
|
-
[...Object.keys(hooks), "load"].includes(key)));
|
|
23
|
+
[...Object.keys(hooks), "load", "context"].includes(key)));
|
|
24
24
|
hookless.length > 0 && errors.ModuleHasNoHooks.warn(log,
|
|
25
|
-
hookless.map(({name}) => name).join(", "));
|
|
25
|
+
hookless.map(({ name }) => name).join(", "));
|
|
26
26
|
|
|
27
27
|
// collect modules
|
|
28
28
|
const loaded = load(modules).flat(2);
|
|
29
29
|
|
|
30
|
-
return
|
|
31
|
-
.map(
|
|
30
|
+
return {
|
|
31
|
+
names: loaded.map(module => module.name),
|
|
32
|
+
...Object.fromEntries([...Object.keys(hooks), "context"]
|
|
33
|
+
.map(hook => [hook, filter(hook, loaded)])),
|
|
34
|
+
};
|
|
32
35
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export {default as
|
|
4
|
-
export
|
|
1
|
+
import load from "./load.js";
|
|
2
|
+
|
|
3
|
+
export { default as routes } from "./routes.js";
|
|
4
|
+
export const guards = load("guard");
|
|
5
|
+
export const errors = load("error");
|
|
6
|
+
export const layouts = load("layouts");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {Path} from "
|
|
1
|
+
import { Path } from "rcompat/fs";
|
|
2
2
|
import errors from "../../errors.js";
|
|
3
3
|
import to_sorted from "../../to_sorted.js";
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@ 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 = to_sorted((await load({log, directory, filter, warn: false}))
|
|
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
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {doubled} from "../common.js";
|
|
1
|
+
import { doubled } from "../common.js";
|
|
2
2
|
import errors from "../../errors.js";
|
|
3
3
|
|
|
4
4
|
const normalize = route => {
|
|
@@ -13,7 +13,7 @@ const deindex = path => path.endsWith("index") ?
|
|
|
13
13
|
|
|
14
14
|
export default async (log, directory, load) => {
|
|
15
15
|
const filter = path => /^[^+].*.js$/u.test(path.name);
|
|
16
|
-
const routes = (await load({log, directory, filter}))
|
|
16
|
+
const routes = (await load({ log, directory, filter }))
|
|
17
17
|
.map(([path, handler]) => [deindex(path), handler]);
|
|
18
18
|
|
|
19
19
|
const double = doubled(routes.map(([route]) => normalize(route)));
|
package/src/loaders/routes.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {tryreturn} from "
|
|
2
|
-
import {from} from "
|
|
3
|
-
import
|
|
4
|
-
import {invalid} from "../hooks/route.js";
|
|
5
|
-
import {default as fs, doubled} from "./common.js";
|
|
1
|
+
import { tryreturn } from "rcompat/sync";
|
|
2
|
+
import { from } from "rcompat/object";
|
|
3
|
+
import { default as fs, doubled } from "./common.js";
|
|
6
4
|
import * as get from "./routes/exports.js";
|
|
5
|
+
import errors from "../errors.js";
|
|
6
|
+
import { invalid } from "../hooks/route.js";
|
|
7
7
|
|
|
8
8
|
const make = path => {
|
|
9
9
|
const double = doubled(path.split("/")
|
|
@@ -13,7 +13,7 @@ const make = path => {
|
|
|
13
13
|
|
|
14
14
|
const route = path.replaceAll(/\{(?<named>.*?)\}/gu, (_, named) =>
|
|
15
15
|
tryreturn(_ => {
|
|
16
|
-
const {name, type} = /^(?<name>\w+)(?<type>=\w+)?$/u.exec(named).groups;
|
|
16
|
+
const { name, type } = /^(?<name>\w+)(?<type>=\w+)?$/u.exec(named).groups;
|
|
17
17
|
const param = type === undefined ? name : `${name}$${type.slice(1)}`;
|
|
18
18
|
return `(?<${param}>[^/]{1,}?)`;
|
|
19
19
|
}).orelse(_ => errors.InvalidPathParameter.throw(named, path)));
|
|
@@ -23,26 +23,27 @@ const make = path => {
|
|
|
23
23
|
return new RegExp(`^/${route}$`, "u");
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
const routes$ = from(await Promise.all(specials.map(async extra =>
|
|
30
|
-
[extra, await get[extra](log, directory, load)])));
|
|
26
|
+
export default async (app, load = fs) => {
|
|
27
|
+
const { log } = app;
|
|
28
|
+
const directory = app.runpath(app.config.location.routes);
|
|
31
29
|
const filter = path => ([name]) => path.includes(name);
|
|
30
|
+
const routes = from(await Promise.all(["guards", "errors", "layouts"]
|
|
31
|
+
.map(async extra => [extra, await get[extra](log, directory, load)])));
|
|
32
32
|
|
|
33
|
-
return routes.map(([path, imported]) => {
|
|
33
|
+
return (await get.routes(log, directory, load)).map(([path, imported]) => {
|
|
34
34
|
if (imported === undefined || Object.keys(imported).length === 0) {
|
|
35
35
|
errors.EmptyRouteFile.warn(log, directory.join(`${path}.js`).path);
|
|
36
36
|
return [];
|
|
37
37
|
}
|
|
38
|
+
const filtered = filter(path);
|
|
38
39
|
|
|
39
40
|
return Object.entries(imported).map(([method, handler]) => ({
|
|
40
41
|
method,
|
|
41
42
|
handler,
|
|
42
43
|
pathname: make(path.endsWith("/") ? path.slice(0, -1) : path),
|
|
43
|
-
guards: routes
|
|
44
|
-
errors: routes
|
|
45
|
-
layouts: routes
|
|
44
|
+
guards: routes.guards.filter(filtered).map(([, guard]) => guard),
|
|
45
|
+
errors: routes.errors.filter(filtered).map(([, error]) => error),
|
|
46
|
+
layouts: routes.layouts.filter(filtered).map(([, layout]) => layout),
|
|
46
47
|
}));
|
|
47
48
|
}).flat();
|
|
48
49
|
};
|