primate 0.8.1 → 0.9.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 (68) hide show
  1. package/LICENSE +17 -23
  2. package/README.md +199 -218
  3. package/README.template.md +170 -0
  4. package/bin/primate.js +5 -0
  5. package/exports.js +3 -29
  6. package/html.js +13 -0
  7. package/module.json +10 -0
  8. package/package.json +14 -16
  9. package/{source → src}/Bundler.js +2 -2
  10. package/{source → src}/cache.js +0 -0
  11. package/src/conf.js +25 -0
  12. package/{source → src}/errors/InternalServer.js +0 -0
  13. package/{source → src}/errors/Predicate.js +0 -0
  14. package/src/errors/Route.js +1 -0
  15. package/{source → src}/errors.js +0 -0
  16. package/{source/extend_object.js → src/extend.js} +3 -3
  17. package/src/extend.spec.js +111 -0
  18. package/src/handlers/http.js +1 -0
  19. package/src/handlers/http404.js +7 -0
  20. package/src/handlers/json.js +7 -0
  21. package/src/handlers/stream.js +7 -0
  22. package/src/handlers/text.js +14 -0
  23. package/{source → src}/http-codes.json +0 -0
  24. package/src/log.js +22 -0
  25. package/{source → src}/mimes.json +0 -0
  26. package/{source → src}/preset/primate.json +0 -0
  27. package/{source → src}/preset/stores/default.js +0 -0
  28. package/src/route.js +61 -0
  29. package/src/run.js +26 -0
  30. package/src/serve.js +90 -0
  31. package/source/App.js +0 -35
  32. package/source/EagerPromise.js +0 -49
  33. package/source/Router.js +0 -31
  34. package/source/Server.js +0 -93
  35. package/source/Session.js +0 -26
  36. package/source/attributes.js +0 -14
  37. package/source/conf.js +0 -26
  38. package/source/domain/Domain.js +0 -285
  39. package/source/domain/Field.js +0 -113
  40. package/source/domain/Predicate.js +0 -24
  41. package/source/handlers/DOM/Node.js +0 -179
  42. package/source/handlers/DOM/Parser.js +0 -135
  43. package/source/handlers/html.js +0 -28
  44. package/source/handlers/http.js +0 -8
  45. package/source/handlers/json.js +0 -6
  46. package/source/handlers/redirect.js +0 -14
  47. package/source/invariants.js +0 -36
  48. package/source/map_entries.js +0 -6
  49. package/source/sanitize.js +0 -8
  50. package/source/store/Memory.js +0 -60
  51. package/source/store/Store.js +0 -30
  52. package/source/types/Array.js +0 -33
  53. package/source/types/Boolean.js +0 -29
  54. package/source/types/Date.js +0 -20
  55. package/source/types/Domain.js +0 -11
  56. package/source/types/Instance.js +0 -8
  57. package/source/types/Number.js +0 -45
  58. package/source/types/Object.js +0 -12
  59. package/source/types/Primitive.js +0 -7
  60. package/source/types/Storeable.js +0 -44
  61. package/source/types/String.js +0 -49
  62. package/source/types/errors/Array.json +0 -7
  63. package/source/types/errors/Boolean.json +0 -5
  64. package/source/types/errors/Date.json +0 -3
  65. package/source/types/errors/Number.json +0 -9
  66. package/source/types/errors/Object.json +0 -3
  67. package/source/types/errors/String.json +0 -11
  68. package/source/types.js +0 -6
@@ -0,0 +1,170 @@
1
+ # Primate
2
+
3
+ Primal JavaScript framework.
4
+
5
+ ## Getting started
6
+
7
+ Lay out app
8
+
9
+ ```sh
10
+ # getting-started/lay-out-app.sh
11
+ ```
12
+
13
+ Create a route for `/` in `routes/site.js`
14
+
15
+ ```js
16
+ // getting-started/site.js
17
+ ```
18
+
19
+ Create a component in `components/site-index.html`
20
+
21
+ ```html
22
+ <!-- getting-started/site-index.html -->
23
+ ```
24
+
25
+ Generate SSL files
26
+
27
+ ```sh
28
+ # getting-started/generate-ssl.sh
29
+ ```
30
+
31
+ Run
32
+
33
+ ```sh
34
+ npx primate
35
+ ```
36
+
37
+ ## Table of contents
38
+
39
+ * [Serving content](#serving-content)
40
+ * [Routing](#routing)
41
+ * [Domains](#domains)
42
+ * [Stores](#stores)
43
+ * [Components](#components)
44
+
45
+ ## Serving content
46
+
47
+ Create a file in `routes` that exports a default function
48
+
49
+ ### Plain text
50
+
51
+ ```js
52
+ // serving-content/plain-text.js
53
+ ```
54
+
55
+ ### JSON
56
+
57
+ ```js
58
+ // serving-content/json.js
59
+ ```
60
+
61
+ ### Streams
62
+
63
+ ```js
64
+ // serving-content/streams.js
65
+ ```
66
+
67
+ ### HTML
68
+
69
+ Create an HTML component in `components/user-index.html`
70
+
71
+ ```html
72
+ <!-- serving-content/user-index.html -->
73
+ ```
74
+
75
+ Serve the component in your route
76
+
77
+ ```js
78
+ // serving-content/html.js
79
+ ```
80
+
81
+ ## Routing
82
+
83
+ Routes map requests to responses. Routes are loaded from `routes`.
84
+
85
+ The order in which routes are declared is irrelevant. Redeclaring a route
86
+ (same pathname and same HTTP verb) throws a `RouteError`.
87
+
88
+ ### Basic GET route
89
+
90
+ ```js
91
+ // routing/basic-get-request.js
92
+ ```
93
+
94
+ ### Working with the request path
95
+
96
+ ```js
97
+ // routing/working-with-the-request-path.js
98
+ ```
99
+
100
+ ### Regular expressions
101
+
102
+ ```js
103
+ // routing/regular-expressions.js
104
+ ```
105
+
106
+ ### Named groups
107
+
108
+ ```js
109
+ // routing/named-groups.js
110
+ ```
111
+
112
+ ### Aliasing
113
+
114
+ ```js
115
+ // routing/aliasing.js
116
+ ```
117
+
118
+ ### Sharing logic across HTTP verbs
119
+
120
+ ```js
121
+ // routing/sharing-logic-across-http-verbs.js
122
+ ```
123
+
124
+ ## Domains
125
+
126
+ Domains represent a collection in a store, primarily with the class `fields`
127
+ property.
128
+
129
+ ### Fields
130
+
131
+ Field types delimit acceptable values for a field.
132
+
133
+ ```js
134
+ // domains/fields.js
135
+ ```
136
+
137
+ ### Short notation
138
+
139
+ Field types may be any constructible JavaScript object, including other
140
+ domains. When using other domains as types, data integrity (on saving) is
141
+ ensured.
142
+
143
+ ```js
144
+ // domains/short-notation.js
145
+ ```
146
+
147
+ ### Predicates
148
+
149
+ Field types may also be specified as an array, to specify additional predicates
150
+ aside from the type.
151
+
152
+ ```js
153
+ // domains/predicates.js
154
+ ```
155
+
156
+ ## Stores
157
+
158
+ Stores interface data. Primate comes with volatile in-memory store used as a
159
+ default. Other stores can be imported as modules.
160
+
161
+ Stores are loaded from `stores`.
162
+
163
+ ### Resources
164
+
165
+ * Website: https://primatejs.com
166
+ * IRC: Join the `#primate` channel on `irc.libera.chat`.
167
+
168
+ ## License
169
+
170
+ MIT
package/bin/primate.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import conf from "../src/conf.js";
4
+ import run from "../src/run.js";
5
+ await run(conf());
package/exports.js CHANGED
@@ -1,31 +1,5 @@
1
- import conf from "./source/conf.js";
2
- import App from "./source/App.js";
1
+ export {default as Bundler} from "./src/Bundler.js";
3
2
 
4
- export {App};
5
- export {default as Bundler} from "./source/Bundler.js";
6
- export {default as EagerPromise, eager} from "./source/EagerPromise.js" ;
3
+ export * from "./src/errors.js";
7
4
 
8
- export {default as Domain} from "./source/domain/Domain.js";
9
- export {default as Storeable} from "./source/types/Storeable.js";
10
-
11
- export * from "./source/errors.js";
12
- export * from "./source/invariants.js";
13
-
14
- export {default as MemoryStore} from "./source/store/Memory.js";
15
- export {default as Store} from "./source/store/Store.js";
16
-
17
- export {default as extend_object} from "./source/extend_object.js";
18
- export {default as sanitize} from "./source/sanitize.js";
19
-
20
- export {default as html} from "./source/handlers/html.js";
21
- export {default as json} from "./source/handlers/json.js";
22
- export {default as redirect} from "./source/handlers/redirect.js";
23
- export {http404} from "./source/handlers/http.js";
24
-
25
- export {default as DOMParser} from "./source/handlers/DOM/Parser.js";
26
-
27
- export {default as router} from "./source/Router.js";
28
-
29
- const app = new App(conf());
30
-
31
- export {app};
5
+ export {default as extend} from "./src/extend.js";
package/html.js ADDED
@@ -0,0 +1,13 @@
1
+ import html from "../src/handlers/html.js";
2
+ const components = {
3
+ "custom-tag": "<ct></ct>",
4
+ "custom-with-attribute": "<cwa value=\"${foo}\"></cwa>",
5
+ "custom-with-object-attribute": "<cwoa value=\"${foo.bar}\"></cwoa>",
6
+ "custom-with-slot": "<cws><slot/></cws>",
7
+ "for-with-object": "<fwo for=\"${foo}\"><span value=\"${bar}\"></span></fwo>",
8
+ "slot-before-custom": "<slot/><custom-tag></custom-tag>",
9
+ "custom-before-slot": "<custom-tag></custom-tag><slot/>",
10
+ };
11
+ const index = "<body>";
12
+ const conf = {components, index};
13
+ export default () => (strings, ...keys) => html(strings, ...keys)(conf);
package/module.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "primate",
3
+ "version": "0.9.0",
4
+ "author": "Terrablue <terrablue@proton.me>",
5
+ "bugs": "https://github.com/primatejs/primate/issues",
6
+ "repository": "https://github.com/primatejs/primate",
7
+ "description": "Primal JavaScript framework",
8
+ "license": "MIT",
9
+ "bin": "bin/primate.js"
10
+ }
package/package.json CHANGED
@@ -1,27 +1,25 @@
1
1
  {
2
2
  "name": "primate",
3
- "version": "0.8.1",
4
- "author": "Primate core team <core@primatejs.com>",
3
+ "version": "0.9.1",
4
+ "author": "Terrablue <terrablue@proton.me>",
5
5
  "homepage": "https://primatejs.com",
6
6
  "bugs": "https://github.com/primatejs/primate/issues",
7
7
  "repository": "https://github.com/primatejs/primate",
8
- "description": "Cross-runtime JavaScript framework",
9
- "license": "BSD-3-Clause",
10
- "scripts": {
11
- "copy": "rm -rf output && mkdir output && cp source/* output -a",
12
- "instrument": "nyc instrument source ./output",
13
- "debris": "node --experimental-json-modules node_modules/debris debris.json",
14
- "coverage": "npm run instrument && nyc npm run debris",
15
- "test": "npm run copy && npm run debris"
16
- },
8
+ "description": "Primal JavaScript framework",
9
+ "license": "MIT",
17
10
  "dependencies": {
18
- "runtime-compat": "^0.1.6"
11
+ "runtime-compat": "^0.12.2"
19
12
  },
13
+ "bin": "bin/primate.js",
20
14
  "devDependencies": {
21
- "debris": "^0.2.2",
22
- "eslint": "^8.14.0",
23
- "eslint-plugin-json": "^3.1.0",
24
- "nyc": "^15.1.0"
15
+ "@babel/core": "^7.20.12",
16
+ "@babel/eslint-parser": "^7.19.1",
17
+ "@babel/plugin-syntax-import-assertions": "^7.20.0",
18
+ "eslint": "^8.33.0",
19
+ "eslint-plugin-json": "^3.1.0"
20
+ },
21
+ "scripts": {
22
+ "docs": "npx embedme --source-root examples --strip-embed-comment --stdout README.template.md > README.md"
25
23
  },
26
24
  "type": "module",
27
25
  "exports": "./exports.js",
@@ -1,7 +1,7 @@
1
- import {Path, File} from "runtime-compat";
1
+ import {Path, File} from "runtime-compat/filesystem";
2
2
 
3
3
  const meta_url = new Path(import.meta.url).path;
4
- const directory = Path.dirname(meta_url);
4
+ const directory = Path.directory(meta_url);
5
5
  const preset = `${directory}/preset`;
6
6
 
7
7
  export default class Bundler {
File without changes
package/src/conf.js ADDED
@@ -0,0 +1,25 @@
1
+ import {Path} from "runtime-compat/filesystem";
2
+ import {Either} from "runtime-compat/functional";
3
+ import cache from "./cache.js";
4
+ import extend from "./extend.js";
5
+ import json from "./preset/primate.json" assert {type: "json"};
6
+
7
+ const qualify = (root, paths) =>
8
+ Object.keys(paths).reduce((sofar, key) => {
9
+ const value = paths[key];
10
+ sofar[key] = typeof value === "string"
11
+ ? new Path(root, value)
12
+ : qualify(`${root}/${key}`, value);
13
+ return sofar;
14
+ }, {});
15
+
16
+ export default (filename = "primate.json") => cache("conf", filename, () => {
17
+ const root = Path.resolve();
18
+ const conf = Either
19
+ .try(() => extend(json, JSON.parse(root.join(filename).file.readSync())))
20
+ .match({left: () => json})
21
+ .get();
22
+ const paths = qualify(root, conf.paths);
23
+
24
+ return {...conf, paths, root};
25
+ });
File without changes
File without changes
@@ -0,0 +1 @@
1
+ export default class RouteError extends Error {}
File without changes
@@ -1,10 +1,10 @@
1
- const extend_object = (base = {}, extension = {}) =>
1
+ const extend = (base = {}, extension = {}) =>
2
2
  Object.keys(extension).reduce((result, property) => {
3
3
  const value = extension[property];
4
4
  result[property] = value?.constructor === Object
5
- ? extend_object(base[property], value)
5
+ ? extend(base[property], value)
6
6
  : value;
7
7
  return result;
8
8
  }, base);
9
9
 
10
- export default extend_object;
10
+ export default extend;
@@ -0,0 +1,111 @@
1
+ import extend from "./extend.js";
2
+
3
+ export default test => {
4
+ test.case("no params", assert => {
5
+ assert(extend()).equals({});
6
+ });
7
+
8
+ test.case("no base", assert => {
9
+ const extension = {key: "value"};
10
+ assert(extend(undefined, extension)).equals(extension);
11
+ });
12
+
13
+ test.case("no extension", assert => {
14
+ const base = {keys: "values"};
15
+ assert(extend(base)).equals(base);
16
+ });
17
+
18
+ test.case("base and extension same", assert => {
19
+ const object = {key: "value"};
20
+ assert(extend(object, object)).equals(object);
21
+ });
22
+
23
+ test.case("one property", assert => {
24
+ const base = {key: "value"};
25
+ const extension = {key: "value2"};
26
+ assert(extend(base, extension)).equals(extension);
27
+ });
28
+
29
+ test.case("two properties, one replaced", assert => {
30
+ const base = {key: "value", key2: "value2"};
31
+ const extension = {key: "other value"};
32
+ const extended = {key: "other value", key2: "value2"};
33
+ assert(extend(base, extension)).equals(extended);
34
+ });
35
+
36
+ test.case("arrays overwritten", assert => {
37
+ const base = {key: ["value", "value2"]};
38
+ const extension = {key: ["value3", "value4"]};
39
+ assert(extend(base, extension)).equals(extension);
40
+ });
41
+
42
+ test.case("one property of a subobject", assert => {
43
+ const base = {key: {"subkey": "subvalue"}};
44
+ const extension = {key: {"subkey": "subvalue 2"}};
45
+ assert(extend(base, extension)).equals(extension);
46
+ });
47
+
48
+ test.case("two properties of a subobject, one replaced", assert => {
49
+ const base = {key: {subkey: "subvalue", subkey2: "subvalue2"}};
50
+ const extension = {key: {subkey: "subvalue 2"}};
51
+ const extended = {key: {subkey: "subvalue 2", subkey2: "subvalue2"}};
52
+ assert(extend(base, extension)).equals(extended);
53
+ });
54
+
55
+ test.case("configuration enhancement", assert => {
56
+ const default_conf = {
57
+ base: "/",
58
+ debug: false,
59
+ defaults: {
60
+ action: "index",
61
+ context: "guest",
62
+ },
63
+ paths: {
64
+ client: "client",
65
+ data: {
66
+ domains: "domains",
67
+ stores: "stores",
68
+ },
69
+ public: "public",
70
+ },
71
+ };
72
+
73
+ const additional_conf = {
74
+ debug: true,
75
+ environment: "testing",
76
+ defaults: {
77
+ context: "user",
78
+ mode: "operational",
79
+ },
80
+ paths: {
81
+ client: "client_logic",
82
+ data: {
83
+ stores: "storage",
84
+ drivers: "drivers",
85
+ },
86
+ },
87
+ };
88
+
89
+ const extended = {
90
+ base: "/",
91
+ debug: true,
92
+ environment: "testing",
93
+ defaults: {
94
+ action: "index",
95
+ context: "user",
96
+ mode: "operational",
97
+ },
98
+ paths: {
99
+ client: "client_logic",
100
+ data: {
101
+ domains: "domains",
102
+ drivers: "drivers",
103
+ stores: "storage",
104
+ },
105
+ public: "public",
106
+ },
107
+ };
108
+
109
+ assert(extend(default_conf, additional_conf)).equals(extended);
110
+ });
111
+ };
@@ -0,0 +1 @@
1
+ export {default as http404} from "./http404.js";
@@ -0,0 +1,7 @@
1
+ const response = {
2
+ body: "Page not found",
3
+ code: 404,
4
+ headers: {"Content-Type": "text/html"},
5
+ };
6
+
7
+ export default () => () => ({...response});
@@ -0,0 +1,7 @@
1
+ const response = {
2
+ code: 200,
3
+ headers: {"Content-Type": "application/json"},
4
+ };
5
+
6
+ export default (strings, ...keys) => async () =>
7
+ ({...response, body: JSON.stringify(await keys[0])});
@@ -0,0 +1,7 @@
1
+ const response = {
2
+ code: 200,
3
+ headers: {"Content-Type": "application/octet-stream"},
4
+ };
5
+
6
+ export default (strings, ...keys) => async () =>
7
+ ({...response, body: await keys[0]});
@@ -0,0 +1,14 @@
1
+ const last = -1;
2
+ const response = {
3
+ code: 200,
4
+ headers: {"Content-Type": "text/plain"},
5
+ }
6
+
7
+ export default (strings, ...keys) => async () => {
8
+ const awaitedKeys = await Promise.all(keys);
9
+ const body = strings
10
+ .slice(0, last)
11
+ .map((string, i) => string + awaitedKeys[i])
12
+ .join("") + strings[strings.length + last];
13
+ return {...response, body};
14
+ };
File without changes
package/src/log.js ADDED
@@ -0,0 +1,22 @@
1
+ const colors = {
2
+ red: 31,
3
+ green: 32,
4
+ yellow: 33,
5
+ blue: 34,
6
+ };
7
+ const reset = 0;
8
+
9
+ const Log = {
10
+ paint: (color, message) => {
11
+ process.stdout.write(`\x1b[${color}m${message}\x1b[0m`);
12
+ return log;
13
+ },
14
+ nl: () => log.paint(reset, "\n"),
15
+ };
16
+
17
+ const log = new Proxy(Log, {
18
+ get: (target, property) => target[property] ?? (message =>
19
+ log.paint(colors[property] ?? reset, message).paint(reset, " ")),
20
+ });
21
+
22
+ export default log;
File without changes
File without changes
File without changes
package/src/route.js ADDED
@@ -0,0 +1,61 @@
1
+ import {ReadableStream} from "runtime-compat/streams";
2
+ import {Path, File} from "runtime-compat/filesystem";
3
+ import {is} from "runtime-compat/dyndef";
4
+ import {http404} from "./handlers/http.js";
5
+ import text from "./handlers/text.js";
6
+ import json from "./handlers/json.js";
7
+ import stream from "./handlers/stream.js";
8
+ import RouteError from "./errors/Route.js";
9
+
10
+ const isText = value => typeof value === "string" ? text`${value}` : http404``;
11
+ const isObject = value => typeof value === "object" && value !== null
12
+ ? json`${value}` : isText(value);
13
+ const isStream = value => value instanceof ReadableStream
14
+ ? stream`${value}` : isObject(value);
15
+ const isFile = value => value instanceof File
16
+ ? stream`${value}` : isStream(value);
17
+ const guess = value => isFile(value);
18
+
19
+ export default async definitions => {
20
+ const aliases = [];
21
+ const routes = [];
22
+ const expand = path => aliases.reduce((expanded, {key, value}) =>
23
+ expanded.replace(key, () => value), path);
24
+ const exists = (method, path) =>
25
+ routes.some(route => route.method === method && route.path === path);
26
+ const add = (method, path, handler) => {
27
+ is(path).string();
28
+ is(handler).function();
29
+ if (exists(method, path)) {
30
+ throw new RouteError(`a ${method} route for ${path} already exists`);
31
+ }
32
+ routes.push({method, path: new RegExp(`^${expand(path)}$`, "u"), handler});
33
+ };
34
+ const find = (method, path, fallback = {handler: r => r}) =>
35
+ routes.find(route =>
36
+ route.method === method && route.path.test(path)) ?? fallback;
37
+
38
+ const router = {
39
+ map: (path, callback) => add("map", path, callback),
40
+ get: (path, callback) => add("get", path, callback),
41
+ post: (path, callback) => add("post", path, callback),
42
+ alias: (key, value) => aliases.push({key, value}),
43
+ process: async request => {
44
+ const {method} = request;
45
+ const url = new URL(`https://primatejs.com${request.pathname}`);
46
+ const {pathname, searchParams} = url;
47
+ const params = Object.fromEntries(searchParams);
48
+ const verb = find(method, pathname, {handler: () => http404``});
49
+ const path = pathname.split("/").filter(part => part !== "");
50
+ const named = verb.path?.exec(pathname)?.groups ?? {};
51
+
52
+ const result = await verb.handler(await find("map", pathname)
53
+ .handler({...request, pathname, params, path, named}));
54
+
55
+ return typeof result === "function" ? result : guess(result);
56
+ },
57
+ };
58
+ const files = (await Path.list(definitions)).map(route => import(route));
59
+ await Promise.all(files.map(async route => (await route).default(router)));
60
+ return router;
61
+ };
package/src/run.js ADDED
@@ -0,0 +1,26 @@
1
+ import {Path, File} from "runtime-compat/filesystem";
2
+ import {default as Bundler, index} from "./Bundler.js";
3
+ import serve from "./serve.js";
4
+ import route from "./route.js";
5
+ import package_json from "../package.json" assert {type: "json"};
6
+ import log from "./log.js";
7
+
8
+ export default async conf => {
9
+ log.reset("Primate").yellow(package_json.version);
10
+ const {paths} = conf;
11
+ const router = await route(paths.routes);
12
+ await new Bundler(conf).bundle();
13
+
14
+ await serve({router,
15
+ paths: conf.paths,
16
+ index: await index(conf),
17
+ from: conf.paths.public,
18
+ http: {
19
+ ...conf.http,
20
+ key: await File.read(Path.resolve(conf.http.ssl.key)),
21
+ cert: await File.read(Path.resolve(conf.http.ssl.cert)),
22
+ keyFile: Path.resolve(conf.http.ssl.key),
23
+ certFile: Path.resolve(conf.http.ssl.cert),
24
+ },
25
+ });
26
+ };