primate 0.31.12 → 0.32.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.
Files changed (46) hide show
  1. package/LICENSE +19 -0
  2. package/package.json +13 -6
  3. package/src/bin.js +3 -3
  4. package/src/commands/build.js +4 -0
  5. package/src/commands/dev.js +7 -2
  6. package/src/commands/exports.js +2 -1
  7. package/src/commands/serve.js +7 -2
  8. package/src/handlers/error.js +1 -0
  9. package/src/handlers/json.js +1 -0
  10. package/src/handlers/redirect.js +1 -0
  11. package/src/handlers/sse.js +1 -0
  12. package/src/handlers/stream.js +1 -0
  13. package/src/handlers/text.js +1 -0
  14. package/src/handlers/view.js +1 -0
  15. package/src/handlers/ws.js +1 -0
  16. package/src/init.js +12 -0
  17. package/README.md +0 -14
  18. package/src/Logger.js +0 -111
  19. package/src/app.js +0 -220
  20. package/src/defaults/app.html +0 -9
  21. package/src/defaults/error.html +0 -14
  22. package/src/defaults/primate.config.js +0 -55
  23. package/src/dispatch.js +0 -28
  24. package/src/errors.js +0 -8
  25. package/src/errors.json +0 -130
  26. package/src/exports.js +0 -9
  27. package/src/handlers.js +0 -93
  28. package/src/hooks/copy_includes.js +0 -20
  29. package/src/hooks/exports.js +0 -7
  30. package/src/hooks/handle.js +0 -113
  31. package/src/hooks/init.js +0 -3
  32. package/src/hooks/parse.js +0 -17
  33. package/src/hooks/publish.js +0 -3
  34. package/src/hooks/register.js +0 -60
  35. package/src/hooks/respond.js +0 -29
  36. package/src/hooks/route.js +0 -49
  37. package/src/hooks/stage.js +0 -64
  38. package/src/loaders/common.js +0 -32
  39. package/src/loaders/exports.js +0 -2
  40. package/src/loaders/modules.js +0 -33
  41. package/src/loaders/types.js +0 -29
  42. package/src/run.js +0 -42
  43. package/src/start.js +0 -66
  44. package/src/to_sorted.js +0 -1
  45. package/src/validate.js +0 -10
  46. package/types/index.d.ts +0 -69
package/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) Terrablue <terrablue@proton.me> and contributors.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
package/package.json CHANGED
@@ -1,14 +1,12 @@
1
1
  {
2
2
  "name": "primate",
3
- "version": "0.31.12",
3
+ "version": "0.32.0",
4
4
  "description": "Polymorphic development platform",
5
5
  "homepage": "https://primatejs.com",
6
6
  "bugs": "https://github.com/primatejs/primate/issues",
7
7
  "license": "MIT",
8
8
  "files": [
9
9
  "src/**/*.js",
10
- "src/errors.json",
11
- "src/defaults/*.html",
12
10
  "!src/**/*.spec.js",
13
11
  "types/*.ts"
14
12
  ],
@@ -19,12 +17,21 @@
19
17
  "directory": "packages/primate"
20
18
  },
21
19
  "dependencies": {
22
- "rcompat": "^0.11.1"
20
+ "@rcompat/args": "^0.3.0",
21
+ "@rcompat/async": "^0.3.0",
22
+ "@rcompat/cli": "^0.5.1",
23
+ "@rcompat/fs": "^0.4.0",
24
+ "@rcompat/package": "^0.7.0",
25
+ "@primate/core": "^0.1.0"
23
26
  },
24
27
  "engines": {
25
28
  "node": ">=18"
26
29
  },
27
30
  "type": "module",
28
31
  "types": "./types/index.d.ts",
29
- "exports": "./src/exports.js"
30
- }
32
+ "exports": {
33
+ "./handler/*": {
34
+ "default": "./src/handlers/*.js"
35
+ }
36
+ }
37
+ }
package/src/bin.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import args from "rcompat/args";
3
- import run from "./run.js";
4
- await run(args[0]);
2
+ import args from "@rcompat/args";
3
+ import init from "./init.js";
4
+ await init(...args);
@@ -0,0 +1,4 @@
1
+ import build from "@primate/core/build";
2
+
3
+ // build for production
4
+ export default (target = "web", mode = "production") => build(mode, target);
@@ -1,3 +1,8 @@
1
- import start from "../start.js";
1
+ import build from "./build.js";
2
+ import serve from "./serve.js";
2
3
 
3
- export default app => start(app, "development");
4
+ // build for development and serve
5
+ export default async () => {
6
+ // will only serve is build is successful
7
+ await build("web", "development") === true && serve();
8
+ };
@@ -1,4 +1,5 @@
1
+ import { default as build } from "./build.js";
1
2
  import { default as dev } from "./dev.js";
2
3
  import { default as serve } from "./serve.js";
3
4
 
4
- export default name => ({ dev, serve })[name] ?? dev;
5
+ export default name => ({ build, dev, serve })[name] ?? dev;
@@ -1,3 +1,8 @@
1
- import start from "../start.js";
1
+ import root from "@rcompat/package/root";
2
+ import tryreturn from "@rcompat/async/tryreturn";
3
+ import resolve from "@rcompat/fs/resolve";
2
4
 
3
- export default app => start(app, "production");
5
+ // serve from build directory
6
+ export default async (from = "build") =>
7
+ (await tryreturn(_ => root()).orelse(_ => resolve()))
8
+ .join(`./${from}/serve.js`).import();
@@ -0,0 +1 @@
1
+ export { default } from "@primate/core/handler/error";
@@ -0,0 +1 @@
1
+ export { default } from "@primate/core/handler/json";
@@ -0,0 +1 @@
1
+ export { default } from "@primate/core/handler/redirect";
@@ -0,0 +1 @@
1
+ export { default } from "@primate/core/handler/sse";
@@ -0,0 +1 @@
1
+ export { default } from "@primate/core/handler/stream";
@@ -0,0 +1 @@
1
+ export { default } from "@primate/core/handler/text";
@@ -0,0 +1 @@
1
+ export { default } from "@primate/core/handler/view";
@@ -0,0 +1 @@
1
+ export { default } from "@primate/core/handler/ws";
package/src/init.js ADDED
@@ -0,0 +1,12 @@
1
+ import blue from "@rcompat/cli/color/blue";
2
+ import bold from "@rcompat/cli/color/bold";
3
+ import print from "@rcompat/cli/print";
4
+ import manifest from "@rcompat/package/manifest";
5
+ import find from "./commands/exports.js";
6
+
7
+ export default async (...args) => {
8
+ const [command, ...flags] = args;
9
+ const primate = await manifest(import.meta.url);
10
+ print(blue(bold(primate.name)), blue(primate.version), "\n");
11
+ find(command)(...flags);
12
+ };
package/README.md DELETED
@@ -1,14 +0,0 @@
1
- # Primate
2
-
3
- Polymorphic development platform. To start [read guide].
4
-
5
- ## Resources
6
-
7
- * Website: https://primatejs.com
8
- * IRC: Join the `#primate` channel on `irc.libera.chat`
9
-
10
- ## License
11
-
12
- MIT
13
-
14
- [read guide]: https://primatejs.com/guide/getting-started
package/src/Logger.js DELETED
@@ -1,111 +0,0 @@
1
- import { assert, is } from "rcompat/invariant";
2
- import { blue, bold, green, red, yellow, dim } from "rcompat/colors";
3
- import o from "rcompat/object";
4
- import console from "rcompat/console";
5
- import { stdout } from "rcompat/stdio";
6
-
7
- const levels = {
8
- Error: 0,
9
- Warn: 1,
10
- Info: 2,
11
- };
12
-
13
- const print = (...messages) => stdout.write(messages.join(" "));
14
- const bye = _ => print(dim(yellow("~~ bye\n")));
15
- const mark = (format, ...params) => params.reduce((formatted, param, i) =>
16
- formatted.replace(`{${i}}`, bold(param)), format);
17
-
18
- const reference = (module, error) => {
19
- const base = module === "primate" ? "guide/logging" : `modules/${module}`;
20
- return `https://primatejs.com/${base}#${hyphenate(error)}`;
21
- };
22
-
23
- const hyphenate = class_cased => class_cased
24
- .split("")
25
- .map(letter => letter.replace(/[A-Z]/u, upper => `-${upper.toLowerCase()}`))
26
- .join("")
27
- .slice(1);
28
-
29
- const throwable = ({ message, level, fix }, name, module) => ({
30
- new(...args) {
31
- const error = new Error(mark(message, ...args));
32
- error.level = Logger[level];
33
- error.fix = mark(fix, ...args);
34
- error.name = name;
35
- error.module = module;
36
- return error;
37
- },
38
- throw(...args) {
39
- throw this.new(...args);
40
- },
41
- warn(logger, ...args) {
42
- const error = { level: Logger[level], message: mark(message, ...args),
43
- fix: mark(fix, ...args) };
44
- logger.auto({ ...error, name, module });
45
- },
46
- });
47
-
48
- const Logger = class Logger {
49
- #level; #trace;
50
-
51
- static err(errors, module) {
52
- return o.map(errors, ([key, value]) => [key, throwable(value, key, module)]);
53
- }
54
-
55
- constructor({ level = levels.Error, trace = false } = {}) {
56
- assert(level !== undefined && level <= levels.Info);
57
- is(trace).boolean();
58
- this.#level = level;
59
- this.#trace = trace;
60
- }
61
-
62
- static get Error() {
63
- return levels.Error;
64
- }
65
-
66
- static get Warn() {
67
- return levels.Warn;
68
- }
69
-
70
- static get Info() {
71
- return levels.Info;
72
- }
73
-
74
- #print(pre, color, message, error = {}) {
75
- const { fix, module, name, level } = error;
76
- print(color(pre), `${module !== undefined ? `${color(module)} ` : ""}${message}`, "\n");
77
- if (fix) {
78
- print(blue("++"), fix);
79
- name && print(dim(`\n -> ${reference(module, name)}`), "\n");
80
- }
81
- if (level === levels.Error || level === undefined && error.message) {
82
- this.#trace && console.log(error);
83
- }
84
- }
85
-
86
- get level() {
87
- return this.#level;
88
- }
89
-
90
- info(...args) {
91
- this.level >= levels.Info && this.#print("--", green, ...args);
92
- }
93
-
94
- warn(...args) {
95
- this.level >= levels.Warn && this.#print("??", yellow, ...args);
96
- }
97
-
98
- error(...args) {
99
- this.level >= levels.Warn && this.#print("!!", red, ...args);
100
- }
101
-
102
- auto(error) {
103
- const { message } = error;
104
- const matches = o.map(levels, ([key, level]) => [level, key.toLowerCase()]);
105
- return this[matches[error.level] ?? "error"](message, error);
106
- }
107
- };
108
-
109
- export default Logger;
110
-
111
- export { print, bye, mark };
package/src/app.js DELETED
@@ -1,220 +0,0 @@
1
- import crypto from "rcompat/crypto";
2
- import { tryreturn } from "rcompat/async";
3
- import FS from "rcompat/fs";
4
- import { is } from "rcompat/invariant";
5
- import o from "rcompat/object";
6
- import { globify } from "rcompat/string";
7
- import * as runtime from "rcompat/meta";
8
- import { identity } from "rcompat/function";
9
- import { Response, Status, MediaType } from "rcompat/http";
10
-
11
- import errors from "./errors.js";
12
- import to_sorted from "./to_sorted.js";
13
- import * as handlers from "./handlers.js";
14
- import * as loaders from "./loaders/exports.js";
15
-
16
- const { DoubleFileExtension } = errors;
17
-
18
- const to_csp = (config_csp, assets, csp) => config_csp
19
- // only csp entries in the config will be enriched
20
- .map(([key, directives]) =>
21
- // enrich with application assets
22
- [key, assets[key] ? directives.concat(...assets[key]) : directives])
23
- .map(([key, directives]) =>
24
- // enrich with explicit csp
25
- [key, csp[key] ? directives.concat(...csp[key]) : directives])
26
- .map(([key, directives]) => `${key} ${directives.join(" ")}`)
27
- .join(";");
28
-
29
- // use user-provided file or fall back to default
30
- const load = (base, page, fallback) =>
31
- tryreturn(_ => FS.File.text(`${base.join(page)}`))
32
- .orelse(_ => FS.File.text(`${base.join(fallback)}`));
33
-
34
- const encoder = new TextEncoder();
35
-
36
- const attribute = attributes => Object.keys(attributes).length > 0
37
- ? " ".concat(Object.entries(attributes)
38
- .map(([key, value]) => `${key}="${value}"`).join(" "))
39
- : "";
40
- const tag = ({ name, attributes = {}, code = "", close = true }) =>
41
- `<${name}${attribute(attributes)}${close ? `>${code}</${name}>` : "/>"}`;
42
- const tags = {
43
- // inline: <script type integrity>...</script>
44
- // outline: <script type integrity src></script>
45
- script({ inline, code, type, integrity, src }) {
46
- return inline
47
- ? tag({ name: "script", attributes: { type, integrity }, code })
48
- : tag({ name: "script", attributes: { type, integrity, src } });
49
- },
50
- // inline: <style>...</style>
51
- // outline: <link rel="stylesheet" href/>
52
- style({ inline, code, href, rel = "stylesheet" }) {
53
- return inline
54
- ? tag({ name: "style", code })
55
- : tag({ name: "link", attributes: { rel, href }, close: false });
56
- },
57
- };
58
-
59
- const render_head = (assets, head) =>
60
- to_sorted(assets, ({ type }) => -1 * (type === "importmap"))
61
- .map(({ src, code, type, inline, integrity }) =>
62
- type === "style"
63
- ? tags.style({ inline, code, href: src })
64
- : tags.script({ inline, code, type, integrity, src }),
65
- ).join("\n").concat("\n", head ?? "");
66
-
67
- export default async (log, root, config) => {
68
- const { http } = config;
69
- const secure = http?.ssl !== undefined;
70
- const path = o.valmap(config.location, value => root.join(value));
71
-
72
- // if ssl activated, resolve key and cert early
73
- if (secure) {
74
- http.ssl.key = root.join(http.ssl.key);
75
- http.ssl.cert = root.join(http.ssl.cert);
76
- }
77
-
78
- const error = await path.routes.join("+error.js");
79
-
80
- return {
81
- secure,
82
- importmaps: {},
83
- assets: [],
84
- path,
85
- root,
86
- log,
87
- // pseudostatic thus arrowbound
88
- get: (config_key, fallback) => o.get(config, config_key) ?? fallback,
89
- error: {
90
- default: await error.exists() ? await error.import("default") : undefined,
91
- },
92
- handlers: { ...handlers },
93
- extensions: {
94
- ".html": {
95
- handle: handlers.html,
96
- },
97
- },
98
- modules: await loaders.modules(log, root, config.modules ?? []),
99
- ...runtime,
100
- // copy files to build folder, potentially transforming them
101
- async stage(source, directory, filter) {
102
- const { paths = [], mapper = identity } = this.get("build.transform", {});
103
- is(paths).array();
104
- is(mapper).function();
105
-
106
- const regexs = paths.map(file => globify(file));
107
- const target_base = this.runpath(directory);
108
-
109
- await source.copy(target_base);
110
-
111
- await Promise.all((await source.collect(filter)).map(async path => {
112
- const debased = path.debase(this.root).path.slice(1);
113
- const filename = FS.File.join(directory, path.debase(source));
114
- const target = await target_base.join(filename.debase(directory));
115
- await target.directory.create();
116
- regexs.some(regex => regex.test(debased))
117
- && target.write(mapper(await path.text()));
118
- }));
119
- },
120
- async compile(component) {
121
- const { server, client, components } = this.get("location");
122
-
123
- const source = this.path.components;
124
- const compile = this.extensions[component.fullExtension]?.compile
125
- ?? this.extensions[component.extension]?.compile;
126
- if (compile === undefined) {
127
- const debased = `${component.path}`.replace(source, "");
128
-
129
- const server_target = this.runpath(server, components, debased);
130
- await server_target.directory.create();
131
- await component.copy(server_target);
132
-
133
- const client_target = this.runpath(client, components, debased);
134
- await client_target.directory.create();
135
- await component.copy(client_target);
136
- } else {
137
- // compile server components
138
- await compile.server(component);
139
-
140
- // compile client components
141
- await compile.client(component);
142
- }
143
- },
144
- headers(csp = {}) {
145
- const http_csp = Object.entries(this.get("http.csp", {}));
146
-
147
- return {
148
- ...this.get("http.headers", {}),
149
- ...http_csp.length === 0 ? {} : {
150
- "Content-Security-Policy": to_csp(http_csp, this.asset_csp, csp),
151
- },
152
- };
153
- },
154
- runpath(...directories) {
155
- return this.path.build.join(...directories);
156
- },
157
- async render(content) {
158
- const index = this.get("pages.index");
159
- const { body, head, partial, placeholders = {}, page = index } = content;
160
- ["body", "head"].every(used => is(placeholders[used]).undefined());
161
-
162
- return partial ? body : Object.entries(placeholders)
163
- // replace given placeholders, defaulting to ""
164
- .reduce((html, [key, value]) => html.replace(`%${key}%`, value ?? ""),
165
- await load(this.runpath(this.get("location.pages")), page, index))
166
- // replace non-given placeholders, aside from %body% / %head%
167
- .replaceAll(/(?<keep>%(?:head|body)%)|%.*?%/gus, "$1")
168
- // replace body and head
169
- .replace("%body%", body)
170
- .replace("%head%", render_head(this.assets, head));
171
- },
172
- respond(body, { status = Status.OK, headers = {} } = {}) {
173
- return new Response(body, { status, headers: {
174
- "Content-Type": MediaType.TEXT_HTML, ...this.headers(), ...headers },
175
- });
176
- },
177
- async view(options) {
178
- // split render and respond options
179
- const { status, headers, ...rest } = options;
180
- return this.respond(await this.render(rest), { status, headers });
181
- },
182
- media(type, { status, headers } = {}) {
183
- return { status, headers: { ...headers, "Content-Type": type } };
184
- },
185
- async inline(code, type) {
186
- const integrity = await this.hash(code);
187
- const tag_name = type === "style" ? "style" : "script";
188
- const head = tags[tag_name]({ code, type, inline: true, integrity });
189
- return { head, integrity: `'${integrity}'` };
190
- },
191
- async publish({ src, code, type = "", inline = false }) {
192
- if (inline || type === "style") {
193
- this.assets.push({
194
- src: FS.File.join(http.static.root, src ?? "").path,
195
- code: inline ? code : "",
196
- type,
197
- inline,
198
- integrity: await this.hash(code),
199
- });
200
- }
201
- // rehash assets_csp
202
- this.asset_csp = this.assets.map(({ type: directive, integrity }) => [
203
- `${directive === "style" ? "style" : "script"}-src`, integrity])
204
- .reduce((csp, [directive, hash]) =>
205
- ({ ...csp, [directive]: csp[directive].concat(`'${hash}'`) } ),
206
- { "style-src": [], "script-src": [] },
207
- );
208
- },
209
- register(extension, operations) {
210
- is(this.handlers[extension]).undefined(DoubleFileExtension.new(extension));
211
- this.handlers[extension] = operations.handle;
212
- this.extensions[extension] = operations;
213
- },
214
- async hash(data, algorithm = "sha-384") {
215
- const bytes = await crypto.subtle.digest(algorithm, encoder.encode(data));
216
- const prefix = algorithm.replace("-", _ => "");
217
- return `${prefix}-${btoa(String.fromCharCode(...new Uint8Array(bytes)))}`;
218
- },
219
- };
220
- };
@@ -1,9 +0,0 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <title>Primate app</title>
5
- <meta charset="utf-8" />
6
- %head%
7
- </head>
8
- <body>%body%</body>
9
- </html>
@@ -1,14 +0,0 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <title>Error page</title>
5
- <meta charset="utf-8" />
6
- %head%
7
- </head>
8
- <body>
9
- <h1>Error page</h1>
10
- <p>
11
- %body%
12
- </p>
13
- </body>
14
- </html>
@@ -1,55 +0,0 @@
1
- import { identity } from "rcompat/function";
2
- import Logger from "../Logger.js";
3
-
4
- export default {
5
- base: "/",
6
- modules: [],
7
- pages: {
8
- index: "app.html",
9
- error: "error.html",
10
- },
11
- logger: {
12
- level: Logger.Warn,
13
- trace: false,
14
- },
15
- http: {
16
- host: "localhost",
17
- port: 6161,
18
- csp: {},
19
- static: {
20
- root: "/",
21
- },
22
- },
23
- request: {
24
- body: {
25
- parse: true,
26
- },
27
- },
28
- location: {
29
- // renderable components
30
- components: "components",
31
- // HTML pages
32
- pages: "pages",
33
- // hierarchical routes
34
- routes: "routes",
35
- // static assets
36
- static: "static",
37
- // runtime types
38
- types: "types",
39
- // build environment
40
- build: "build",
41
- // client build
42
- client: "client",
43
- // server build
44
- server: "server",
45
- },
46
- build: {
47
- name: "app",
48
- includes: [],
49
- excludes: [],
50
- transform: {
51
- paths: [],
52
- mapper: identity,
53
- },
54
- },
55
- };
package/src/dispatch.js DELETED
@@ -1,28 +0,0 @@
1
- import { is } from "rcompat/invariant";
2
- import { tryreturn } from "rcompat/sync";
3
- import o from "rcompat/object";
4
- import { camelcased } from "rcompat/string";
5
- import errors from "./errors.js";
6
- import validate from "./validate.js";
7
-
8
- export default (patches = {}) => (object, raw, cased = true) => {
9
- return Object.assign(Object.create(null), {
10
- ...o.map(patches, ([name, patch]) => [`get${camelcased(name)}`, key => {
11
- is(key).defined(`\`${name}\` called without key`);
12
- return tryreturn(_ => validate(patch, object[key], key))
13
- .orelse(({ message }) => errors.MismatchedType.throw(message));
14
- }]),
15
- get(key) {
16
- is(key).string();
17
-
18
- return object[cased ? key : key.toLowerCase()];
19
- },
20
- json() {
21
- return JSON.parse(JSON.stringify(object));
22
- },
23
- toString() {
24
- return JSON.stringify(object);
25
- },
26
- raw,
27
- });
28
- };
package/src/errors.js DELETED
@@ -1,8 +0,0 @@
1
- import FS from "rcompat/fs";
2
- import Logger from "./Logger.js";
3
-
4
- const json = await new FS.File(import.meta.url).up(1).join("errors.json").json();
5
-
6
- const errors = Logger.err(json.errors, json.module);
7
-
8
- export default errors;