primate 0.25.0 → 0.26.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.
Files changed (44) hide show
  1. package/package.json +2 -2
  2. package/src/Logger.js +5 -5
  3. package/src/app.js +45 -39
  4. package/src/bin.js +1 -1
  5. package/src/commands/dev.js +1 -1
  6. package/src/commands/serve.js +1 -1
  7. package/src/cwd.js +1 -1
  8. package/src/defaults/primate.config.js +1 -1
  9. package/src/dispatch.js +12 -9
  10. package/src/errors.js +1 -1
  11. package/src/errors.json +5 -0
  12. package/src/exports.js +1 -1
  13. package/src/handlers/error.js +1 -1
  14. package/src/handlers/html.js +1 -1
  15. package/src/handlers/json.js +1 -1
  16. package/src/handlers/redirect.js +1 -1
  17. package/src/handlers/stream.js +1 -1
  18. package/src/handlers/text.js +1 -1
  19. package/src/hooks/bundle.js +1 -1
  20. package/src/hooks/copy_includes.js +5 -5
  21. package/src/hooks/exports.js +1 -1
  22. package/src/hooks/handle.js +7 -7
  23. package/src/hooks/init.js +1 -1
  24. package/src/hooks/parse.js +4 -4
  25. package/src/hooks/publish.js +4 -38
  26. package/src/hooks/register.js +73 -2
  27. package/src/hooks/respond/duck.js +1 -1
  28. package/src/hooks/respond/respond.js +2 -2
  29. package/src/hooks/route.js +2 -2
  30. package/src/hooks/serve.js +1 -1
  31. package/src/hooks/stage.js +48 -0
  32. package/src/loaders/common.js +2 -2
  33. package/src/loaders/modules.js +1 -1
  34. package/src/loaders/routes/exports.js +5 -3
  35. package/src/loaders/routes/load.js +1 -1
  36. package/src/loaders/routes.js +14 -13
  37. package/src/loaders/types.js +11 -4
  38. package/src/run.js +4 -4
  39. package/src/start.js +17 -8
  40. package/src/validate.js +1 -1
  41. package/src/hooks/compile.js +0 -45
  42. package/src/loaders/routes/errors.js +0 -3
  43. package/src/loaders/routes/guards.js +0 -3
  44. package/src/loaders/routes/layouts.js +0 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primate",
3
- "version": "0.25.0",
3
+ "version": "0.26.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.32.3"
21
+ "rcompat": "^0.3.3"
22
22
  },
23
23
  "engines": {
24
24
  "node": ">=18"
package/src/Logger.js CHANGED
@@ -1,8 +1,8 @@
1
- import { assert, is } from "runtime-compat/invariant";
2
- import { blue, bold, green, red, yellow, dim } from "runtime-compat/colors";
3
- import { map } from "runtime-compat/object";
4
- import console from "runtime-compat/console";
5
- import { stdout } from "runtime-compat/stdio";
1
+ import { assert, is } from "rcompat/invariant";
2
+ import { blue, bold, green, red, yellow, dim } from "rcompat/colors";
3
+ import { map } from "rcompat/object";
4
+ import console from "rcompat/console";
5
+ import { stdout } from "rcompat/stdio";
6
6
 
7
7
  const levels = {
8
8
  Error: 0,
package/src/app.js CHANGED
@@ -1,15 +1,12 @@
1
- import crypto from "runtime-compat/crypto";
2
- import { tryreturn } from "runtime-compat/async";
3
- import { File, Path } from "runtime-compat/fs";
4
- import { bold, blue } from "runtime-compat/colors";
5
- import { is } from "runtime-compat/invariant";
6
- import { transform, valmap } from "runtime-compat/object";
7
- import { globify } from "runtime-compat/string";
8
- import * as runtime from "runtime-compat/meta";
1
+ import crypto from "rcompat/crypto";
2
+ import { tryreturn } from "rcompat/async";
3
+ import { Path } from "rcompat/fs";
4
+ import { is } from "rcompat/invariant";
5
+ import { transform, valmap } from "rcompat/object";
6
+ import { globify } from "rcompat/string";
7
+ import * as runtime from "rcompat/meta";
9
8
 
10
9
  import errors from "./errors.js";
11
- import { print } from "./Logger.js";
12
- import dispatch from "./dispatch.js";
13
10
  import to_sorted from "./to_sorted.js";
14
11
  import * as handlers from "./handlers/exports.js";
15
12
  import * as loaders from "./loaders/exports.js";
@@ -18,8 +15,8 @@ const { DoubleFileExtension } = errors;
18
15
 
19
16
  // use user-provided file or fall back to default
20
17
  const index = (base, page, fallback) =>
21
- tryreturn(_ => File.read(`${base.join(page)}`))
22
- .orelse(_ => File.read(`${base.join(fallback)}`));
18
+ tryreturn(_ => Path.read(`${base.join(page)}`))
19
+ .orelse(_ => Path.read(`${base.join(fallback)}`));
23
20
 
24
21
  const encoder = new TextEncoder();
25
22
 
@@ -54,18 +51,13 @@ export default async (log, root, config) => {
54
51
  const secure = http?.ssl !== undefined;
55
52
  const path = valmap(config.location, value => root.join(value));
56
53
 
57
- const at = `at http${secure ? "s" : ""}://${http.host}:${http.port}\n`;
58
- print(blue(bold(name)), blue(version), at);
59
-
60
54
  // if ssl activated, resolve key and cert early
61
55
  if (secure) {
62
56
  http.ssl.key = root.join(http.ssl.key);
63
57
  http.ssl.cert = root.join(http.ssl.cert);
64
58
  }
65
59
 
66
- const types = await loaders.types(log, path.types);
67
60
  const error = await path.routes.join("+error.js");
68
- const routes = await loaders.routes(log, path.routes);
69
61
 
70
62
  return {
71
63
  config,
@@ -79,15 +71,10 @@ export default async (log, root, config) => {
79
71
  root,
80
72
  log,
81
73
  error: {
82
- default: await error.exists ? (await import(error)).default : undefined,
74
+ default: await error.exists() ? (await import(error)).default : undefined,
83
75
  },
84
76
  handlers: { ...handlers },
85
- types,
86
- routes,
87
- layout: {
88
- depth: Math.max(...routes.map(({ layouts }) => layouts.length)) + 1,
89
- },
90
- dispatch: dispatch(types),
77
+ extensions: {},
91
78
  modules: await loaders.modules(log, root, config),
92
79
  ...runtime,
93
80
  // copy files to build folder, potentially transforming them
@@ -97,21 +84,39 @@ export default async (log, root, config) => {
97
84
  is(mapper).function();
98
85
 
99
86
  const regexs = paths.map(file => globify(file));
100
- const target = this.runpath(directory);
87
+ const target_base = this.runpath(directory);
101
88
 
102
89
  await Promise.all((await source.collect(filter)).map(async path => {
103
90
  const debased = path.debase(this.root).path.slice(1);
104
91
  const filename = new Path(directory).join(path.debase(source));
105
- const to = await target.join(filename.debase(directory));
106
- await to.directory.file.create();
107
- if (regexs.some(regex => regex.test(debased))) {
108
- const contents = mapper(await path.text());
109
- await to.file.write(contents);
110
- } else {
111
- await path.file.copy(to);
112
- }
92
+ const target = await target_base.join(filename.debase(directory));
93
+ await target.directory.create();
94
+ await (regexs.some(regex => regex.test(debased))
95
+ ? target.write(mapper(await path.text()))
96
+ : path.copy(target));
113
97
  }));
114
98
  },
99
+ async compile(component) {
100
+ const { location: { server, client, components } } = this.config;
101
+
102
+ const source = this.path.components;
103
+ const compile = this.extensions[component.extension]?.compile;
104
+ if (compile === undefined) {
105
+ const debased = `${component.path}`.replace(source, "");
106
+
107
+ const server_target = this.runpath(server, components);
108
+ await component.copy(server_target.join(debased));
109
+
110
+ const client_target = this.runpath(client, components);
111
+ await component.copy(client_target.join(debased));
112
+ } else {
113
+ // compile server components
114
+ await compile.server(component);
115
+
116
+ // compile client components
117
+ await compile.client(component);
118
+ }
119
+ },
115
120
  headers({ script = "", style = "" } = {}) {
116
121
  const csp = Object.keys(http.csp).reduce((policy, key) =>
117
122
  `${policy}${key} ${http.csp[key]};`, "")
@@ -154,8 +159,8 @@ export default async (log, root, config) => {
154
159
  async publish({ src, code, type = "", inline = false, copy = true }) {
155
160
  if (!inline && copy) {
156
161
  const base = this.runpath(this.config.location.client).join(src);
157
- await base.directory.file.create();
158
- await base.file.write(code);
162
+ await base.directory.create();
163
+ await base.write(code);
159
164
  }
160
165
  if (inline || type === "style") {
161
166
  this.assets.push({
@@ -170,9 +175,10 @@ export default async (log, root, config) => {
170
175
  export({ type, code }) {
171
176
  this.exports.push({ type, code });
172
177
  },
173
- register(extension, handler) {
178
+ register(extension, operations) {
174
179
  is(this.handlers[extension]).undefined(DoubleFileExtension.new(extension));
175
- this.handlers[extension] = handler;
180
+ this.handlers[extension] = operations.handle;
181
+ this.extensions[extension] = operations;
176
182
  },
177
183
  async hash(data, algorithm = "sha-384") {
178
184
  const bytes = await crypto.subtle.digest(algorithm, encoder.encode(data));
@@ -202,8 +208,8 @@ export default async (log, root, config) => {
202
208
  ?? value.import?.replace(".", `./${module}`),
203
209
  ]));
204
210
  const dependency = Path.resolve().join(...path);
205
- const to = new Path(this.runpath(client), this.library, ...parts);
206
- await dependency.file.copy(to);
211
+ const target = new Path(this.runpath(client), this.library, ...parts);
212
+ await dependency.copy(target);
207
213
  this.importmaps = {
208
214
  ...valmap(exports, value => new Path(root, this.library, value).path),
209
215
  ...this.importmaps };
package/src/bin.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import args from "runtime-compat/args";
2
+ import args from "rcompat/args";
3
3
  import run from "./run.js";
4
4
  await run(args[0]);
@@ -1,3 +1,3 @@
1
1
  import start from "../start.js";
2
2
 
3
- export default app => start(app, ["bundle"]);
3
+ export default app => start(app, "development");
@@ -1,3 +1,3 @@
1
1
  import start from "../start.js";
2
2
 
3
- export default app => start(app);
3
+ export default app => start(app, "production");
package/src/cwd.js CHANGED
@@ -1,3 +1,3 @@
1
- import { Path } from "runtime-compat/fs";
1
+ import { Path } from "rcompat/fs";
2
2
 
3
3
  export default (meta, up = 1) => new Path(meta.url).up(up);
@@ -1,4 +1,4 @@
1
- import { identity } from "runtime-compat/function";
1
+ import { identity } from "rcompat/function";
2
2
  import Logger from "../Logger.js";
3
3
 
4
4
  export default {
package/src/dispatch.js CHANGED
@@ -1,21 +1,24 @@
1
- import { is, maybe } from "runtime-compat/invariant";
2
- import { tryreturn } from "runtime-compat/sync";
3
- import { map } from "runtime-compat/object";
4
- import { camelcased } from "runtime-compat/string";
1
+ import { is } from "rcompat/invariant";
2
+ import { tryreturn } from "rcompat/sync";
3
+ import { map } from "rcompat/object";
4
+ import { camelcased } from "rcompat/string";
5
5
  import errors from "./errors.js";
6
6
  import validate from "./validate.js";
7
7
 
8
- export default (patches = {}) => (value, raw, cased = true) => {
8
+ export default (patches = {}) => (object, raw, cased = true) => {
9
9
  return Object.assign(Object.create(null), {
10
10
  ...map(patches, ([name, patch]) => [`get${camelcased(name)}`, property => {
11
11
  is(property).defined(`\`${name}\` called without property`);
12
- return tryreturn(_ => validate(patch, value[property], property))
12
+ return tryreturn(_ => validate(patch, object[property], property))
13
13
  .orelse(({ message }) => errors.MismatchedType.throw(message));
14
14
  }]),
15
15
  get(property) {
16
- maybe(property).string();
17
- return property === undefined ? value :
18
- value[cased ? property : property.toLowerCase()];
16
+ is(property).string();
17
+
18
+ return object[cased ? property : property.toLowerCase()];
19
+ },
20
+ getAll() {
21
+ return object;
19
22
  },
20
23
  raw,
21
24
  });
package/src/errors.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Path } from "runtime-compat/fs";
1
+ import { Path } from "rcompat/fs";
2
2
  import Logger from "./Logger.js";
3
3
 
4
4
  const json = await new Path(import.meta.url).up(1).join("errors.json").json();
package/src/errors.json CHANGED
@@ -56,6 +56,11 @@
56
56
  "fix": "do not use dots in route names",
57
57
  "level": "Error"
58
58
  },
59
+ "InvalidTypeExport": {
60
+ "message": "invalid type export at {0}",
61
+ "fix": "export object with a `base` string and a `validate` function",
62
+ "level": "Error"
63
+ },
59
64
  "InvalidTypeName": {
60
65
  "message": "invalid type name {0}",
61
66
  "fix": "use lowercase-first latin letters and decimals in type names",
package/src/exports.js CHANGED
@@ -4,6 +4,6 @@ export * from "./handlers/exports.js";
4
4
 
5
5
  export { default as Logger } from "./Logger.js";
6
6
 
7
- export { URL, Response, Status, MediaType } from "runtime-compat/http";
7
+ export { URL, Response, Status, MediaType } from "rcompat/http";
8
8
 
9
9
  export default command => run(command);
@@ -1,4 +1,4 @@
1
- import { Response, Status, MediaType } from "runtime-compat/http";
1
+ import { Response, Status, MediaType } from "rcompat/http";
2
2
 
3
3
  export default (body = "Not Found", { status = Status.NOT_FOUND, page } = {}) =>
4
4
  async app => new Response(await app.render({
@@ -1,4 +1,4 @@
1
- import { Response, Status, MediaType } from "runtime-compat/http";
1
+ import { Response, Status, MediaType } from "rcompat/http";
2
2
 
3
3
  const script_re = /(?<=<script)>(?<code>.*?)(?=<\/script>)/gus;
4
4
  const style_re = /(?<=<style)>(?<code>.*?)(?=<\/style>)/gus;
@@ -1,4 +1,4 @@
1
- import { Response, Status, MediaType } from "runtime-compat/http";
1
+ import { Response, Status, MediaType } from "rcompat/http";
2
2
 
3
3
  export default (body, { status = Status.OK } = {}) => app =>
4
4
  new Response(JSON.stringify(body), {
@@ -1,4 +1,4 @@
1
- import { Response, Status } from "runtime-compat/http";
1
+ import { Response, Status } from "rcompat/http";
2
2
 
3
3
  export default (Location, { status = Status.FOUND } = {}) => app =>
4
4
  /* no body */
@@ -1,4 +1,4 @@
1
- import { Response, Status, MediaType } from "runtime-compat/http";
1
+ import { Response, Status, MediaType } from "rcompat/http";
2
2
 
3
3
  export default (body, { status = Status.OK } = {}) => app =>
4
4
  new Response(body, {
@@ -1,4 +1,4 @@
1
- import { Response, Status, MediaType } from "runtime-compat/http";
1
+ import { Response, Status, MediaType } from "rcompat/http";
2
2
 
3
3
  export default (body, { status = Status.OK } = {}) => app =>
4
4
  new Response(body, {
@@ -1,3 +1,3 @@
1
- import { cascade } from "runtime-compat/async";
1
+ import { cascade } from "rcompat/async";
2
2
 
3
3
  export default async app => (await cascade(app.modules.bundle))(app);
@@ -1,4 +1,4 @@
1
- import { Path } from "runtime-compat/fs";
1
+ import { Path } from "rcompat/fs";
2
2
 
3
3
  export default async (app, type, post = () => undefined) => {
4
4
  const { config } = app;
@@ -13,10 +13,10 @@ export default async (app, type, post = () => undefined) => {
13
13
  .filter(include => /^[^/]*$/u.test(include))
14
14
  .map(async include => {
15
15
  const path = app.root.join(include);
16
- if (await path.exists) {
17
- const to = Path.join(type, include);
18
- await app.stage(path, to);
19
- await post(to);
16
+ if (await path.exists()) {
17
+ const target = Path.join(type, include);
18
+ await app.stage(path, target);
19
+ await post(target);
20
20
  }
21
21
  }));
22
22
  }
@@ -1,6 +1,6 @@
1
1
  export { default as init } from "./init.js";
2
+ export { default as stage } from "./stage.js";
2
3
  export { default as register } from "./register.js";
3
- export { default as compile } from "./compile.js";
4
4
  export { default as publish } from "./publish.js";
5
5
  export { default as bundle } from "./bundle.js";
6
6
  export { default as route } from "./route.js";
@@ -1,5 +1,5 @@
1
- import { Response, Status, MediaType } from "runtime-compat/http";
2
- import { cascade, tryreturn } from "runtime-compat/async";
1
+ import { Response, Status, MediaType } from "rcompat/http";
2
+ import { cascade, tryreturn } from "rcompat/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";
@@ -63,11 +63,11 @@ export default app => {
63
63
  });
64
64
  };
65
65
 
66
- const as_asset = async file => new Response(file.readable, {
66
+ const as_asset = async path => new Response(path.stream(), {
67
67
  status: Status.OK,
68
68
  headers: {
69
- "Content-Type": MediaType.resolve(file.name),
70
- Etag: await file.modified,
69
+ "Content-Type": MediaType.resolve(path.name),
70
+ Etag: await path.modified(),
71
71
  },
72
72
  });
73
73
 
@@ -79,11 +79,11 @@ export default app => {
79
79
  // try static first
80
80
  const asset = client.join(location.static, debased);
81
81
  if (await asset.isFile) {
82
- return as_asset(asset.file);
82
+ return as_asset(asset);
83
83
  }
84
84
  const path = client.join(debased);
85
85
  if (await path.isFile) {
86
- return as_asset(path.file);
86
+ return as_asset(path);
87
87
  }
88
88
  }
89
89
  return as_route(request);
package/src/hooks/init.js CHANGED
@@ -1,3 +1,3 @@
1
- import { cascade } from "runtime-compat/async";
1
+ import { cascade } from "rcompat/async";
2
2
 
3
3
  export default async app => (await cascade(app.modules.init))(app);
@@ -1,7 +1,7 @@
1
- import { URL, MediaType } from "runtime-compat/http";
2
- import { tryreturn } from "runtime-compat/sync";
3
- import { stringify } from "runtime-compat/streams";
4
- import { from, valmap } from "runtime-compat/object";
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
7
  const { APPLICATION_FORM_URLENCODED, APPLICATION_JSON } = MediaType;
@@ -1,10 +1,9 @@
1
- import { Path } from "runtime-compat/fs";
2
- import { cascade } from "runtime-compat/async";
3
- import { stringify } from "runtime-compat/object";
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: { location, http: { static: { root } } }, path } = app;
6
+ const { config: { http: { static: { root } } } } = app;
8
7
 
9
8
  {
10
9
  // after hook, publish a zero assumptions app.js (no css imports)
@@ -17,44 +16,11 @@ const post = async app => {
17
16
  type: "module",
18
17
  });
19
18
 
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 to = Path.join(location.client, location.components);
24
- await app.stage(path.components, to, /^.*.js$/u);
25
- }
26
-
27
19
  const imports = { ...app.importmaps, app: src.path };
28
20
  const type = "importmap";
29
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
 
@@ -1,3 +1,74 @@
1
- import { cascade } from "runtime-compat/async";
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
- export default async app => (await cascade(app.modules.register))(app);
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,4 +1,4 @@
1
- import { Headers } from "runtime-compat/http";
1
+ import { Headers } from "rcompat/http";
2
2
 
3
3
  export const isResponse = value =>
4
4
  value.body !== undefined && value.headers instanceof Headers;
@@ -1,5 +1,5 @@
1
- import { Blob } from "runtime-compat/fs";
2
- import { URL } from "runtime-compat/http";
1
+ import { Blob } from "rcompat/fs";
2
+ import { URL } from "rcompat/http";
3
3
  import { text, json, stream, redirect } from "primate";
4
4
  import { isResponse as isResponseDuck } from "./duck.js";
5
5
 
@@ -1,5 +1,5 @@
1
- import { from } from "runtime-compat/object";
2
- import { tryreturn } from "runtime-compat/sync";
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
 
@@ -1,4 +1,4 @@
1
- import { cascade } from "runtime-compat/async";
1
+ import { cascade } from "rcompat/async";
2
2
 
3
3
  export default async (app, server) => {
4
4
  app.log.info("running serve hooks", { module: "primate" });
@@ -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)));
@@ -1,5 +1,5 @@
1
- import { Path } from "runtime-compat/fs";
2
- import { identity } from "runtime-compat/function";
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";
@@ -12,7 +12,7 @@ export default async (log, root, config) => {
12
12
 
13
13
  Array.isArray(modules) || errors.ModulesMustBeArray.throw("modules");
14
14
 
15
- modules.some((module, n) => module.name === undefined &&
15
+ modules.some(({ name }, n) => name === undefined &&
16
16
  errors.ModuleHasNoName.throw(n));
17
17
 
18
18
  const names = modules.map(({ name }) => name);
@@ -1,4 +1,6 @@
1
+ import load from "./load.js";
2
+
1
3
  export { default as routes } from "./routes.js";
2
- export { default as guards } from "./guards.js";
3
- export { default as errors } from "./errors.js";
4
- export { default as layouts } from "./layouts.js";
4
+ export const guards = await load("guard");
5
+ export const errors = await load("error");
6
+ export const layouts = await load("layout");
@@ -1,4 +1,4 @@
1
- import { Path } from "runtime-compat/fs";
1
+ import { Path } from "rcompat/fs";
2
2
  import errors from "../../errors.js";
3
3
  import to_sorted from "../../to_sorted.js";
4
4
 
@@ -1,9 +1,9 @@
1
- import { tryreturn } from "runtime-compat/sync";
2
- import { from } from "runtime-compat/object";
3
- import errors from "../errors.js";
4
- import { invalid } from "../hooks/route.js";
1
+ import { tryreturn } from "rcompat/sync";
2
+ import { from } from "rcompat/object";
5
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("/")
@@ -23,26 +23,27 @@ const make = path => {
23
23
  return new RegExp(`^/${route}$`, "u");
24
24
  };
25
25
 
26
- const specials = ["guards", "errors", "layouts"];
27
- export default async (log, directory, load = fs) => {
28
- const routes = await get.routes(log, directory, load);
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$.guards.filter(filter(path)).map(([, guard]) => guard),
44
- errors: routes$.errors.filter(filter(path)).map(([, error]) => error),
45
- layouts: routes$.layouts.filter(filter(path)).map(([, layout]) => layout),
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
  };
@@ -1,15 +1,22 @@
1
- import { Path } from "runtime-compat/fs";
1
+ import { Path } from "rcompat/fs";
2
+ import { is } from "rcompat/invariant";
3
+ import { tryreturn } from "rcompat/sync";
2
4
  import errors from "../errors.js";
3
5
  import fs from "./common.js";
4
6
 
5
7
  const filter = path => /^[a-z]/u.test(path.name);
6
8
 
7
9
  export default async (log, directory, load = fs) => {
8
- const types = await load({ log, directory, name: "types", filter });
10
+ const types = await fs({ log, directory, name: "types", filter });
9
11
 
10
12
  const resolve = name => new Path(directory, name);
11
- types.some(([name, type]) => typeof type !== "function"
12
- && errors.InvalidDefaultExport.throw(resolve(`${name}.js`)));
13
+ types.every(([name, type]) => tryreturn(_ => {
14
+ is(type).object();
15
+ is(type.base).string();
16
+ is(type.validate).function();
17
+ return true;
18
+ }).orelse(_ => errors.InvalidTypeExport.throw(resolve(`${name}.js`))),
19
+ );
13
20
 
14
21
  types.every(([name]) =>
15
22
  /^(?:[a-z][^\W_]*)$/u.test(name) || errors.InvalidTypeName.throw(name));
package/src/run.js CHANGED
@@ -1,6 +1,6 @@
1
- import { tryreturn } from "runtime-compat/async";
2
- import { Path } from "runtime-compat/fs";
3
- import { extend } from "runtime-compat/object";
1
+ import { tryreturn } from "rcompat/async";
2
+ import { Path } from "rcompat/fs";
3
+ import { extend } from "rcompat/object";
4
4
  import app from "./app.js";
5
5
  import { default as Logger, bye } from "./Logger.js";
6
6
  import errors from "./errors.js";
@@ -13,7 +13,7 @@ const { runtime = "node" } = import.meta;
13
13
  const get_config = async root => {
14
14
  const name = "primate.config.js";
15
15
  const config = root.join(name);
16
- return await config.exists
16
+ return await config.exists()
17
17
  ? tryreturn(async _ => {
18
18
  const imported = (await import(config)).default;
19
19
 
package/src/start.js CHANGED
@@ -1,15 +1,24 @@
1
- import { serve, Response, Status } from "runtime-compat/http";
2
- import { cascade, tryreturn } from "runtime-compat/async";
1
+ import { serve, Response, Status } from "rcompat/http";
2
+ import { cascade, tryreturn } from "rcompat/async";
3
+ import { bold, blue } from "rcompat/colors";
3
4
  import * as hooks from "./hooks/exports.js";
5
+ import { print } from "./Logger.js";
4
6
 
5
- export default async (app$, deactivated = []) => {
7
+ const base_hooks = ["init", "stage", "register", "publish", "bundle"];
8
+
9
+ export default async (app$, mode = "development") => {
10
+ app$.mode = mode;
6
11
  // run one-time hooks
7
12
  let app = app$;
8
- for (const hook of ["init", "register", "compile", "publish", "bundle"]) {
9
- if (deactivated.includes(hook)) {
10
- continue;
11
- }
12
- app.log.info(`running ${hook} hooks`, { module: "primate" });
13
+
14
+ const { http } = app.config;
15
+ const address = `http${app.secure ? "s" : ""}://${http.host}:${http.port}`;
16
+ print(blue(bold(app.name)), blue(app.version), `at ${address}\n`);
17
+
18
+ app.log.info(`in ${bold(mode)} mode`, { module: "primate" });
19
+
20
+ for (const hook of base_hooks) {
21
+ app.log.info(`running ${bold(hook)} hooks`, { module: "primate" });
13
22
  app = await hooks[hook](app);
14
23
  }
15
24
 
package/src/validate.js CHANGED
@@ -1,4 +1,4 @@
1
- import { is, maybe } from "runtime-compat/invariant";
1
+ import { is, maybe } from "rcompat/invariant";
2
2
 
3
3
  export default (type, value, name) => {
4
4
  maybe(type.validate).function();
@@ -1,45 +0,0 @@
1
- import { Path } from "runtime-compat/fs";
2
- import { cascade } from "runtime-compat/async";
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");
8
-
9
- const pre = async app => {
10
- const { config: { location }, path } = app;
11
-
12
- // remove build directory in case exists
13
- if (await path.build.exists) {
14
- await path.build.file.remove();
15
- }
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);
23
-
24
- if (await path.components.exists) {
25
- // copy all files to build/components
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);
31
- }
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
-
38
- // copy additional subdirectories to build/server
39
- await copy_includes(app, location.server);
40
-
41
- return app;
42
- };
43
-
44
- export default async app =>
45
- (await cascade(app.modules.compile))(await pre(app));
@@ -1,3 +0,0 @@
1
- import load from "./load.js";
2
-
3
- export default load("error");
@@ -1,3 +0,0 @@
1
- import load from "./load.js";
2
-
3
- export default load("guard");
@@ -1,3 +0,0 @@
1
- import load from "./load.js";
2
-
3
- export default load("layout");