primate 0.5.2 → 0.6.2

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 (80) hide show
  1. package/README.md +21 -36
  2. package/debris.json +1 -2
  3. package/package.json +7 -10
  4. package/source/App.js +36 -0
  5. package/source/Bundler.js +51 -0
  6. package/source/{server/Directory.js → Directory.js} +0 -0
  7. package/source/{server/EagerPromise.js → EagerPromise.js} +5 -7
  8. package/source/{server/File.js → File.js} +0 -0
  9. package/source/Router.js +28 -0
  10. package/source/Server.js +105 -0
  11. package/source/{server/Session.js → Session.js} +2 -21
  12. package/source/{server/attributes.js → attributes.js} +0 -0
  13. package/source/{server/cache.js → cache.js} +0 -0
  14. package/source/{server/conf.js → conf.js} +1 -1
  15. package/source/{server/crypto.js → crypto.js} +0 -0
  16. package/source/{server/domain → domain}/Domain.js +7 -9
  17. package/source/{server/domain → domain}/Field.js +0 -0
  18. package/source/{server/domain → domain}/Predicate.js +0 -0
  19. package/source/{server/errors → errors}/InternalServer.js +0 -0
  20. package/source/{server/errors → errors}/Predicate.js +0 -0
  21. package/source/{server/errors.js → errors.js} +0 -0
  22. package/source/{server/exports.js → exports.js} +6 -3
  23. package/source/{server/extend_object.js → extend_object.js} +0 -0
  24. package/source/handlers/DOM/Node.js +184 -0
  25. package/source/{server/view → handlers/DOM}/Parser.js +22 -18
  26. package/source/handlers/html.js +30 -0
  27. package/source/handlers/http.js +6 -0
  28. package/source/handlers/json.js +6 -0
  29. package/source/handlers/redirect.js +10 -0
  30. package/source/{server/servers/http-codes.json → http-codes.json} +0 -0
  31. package/source/{server/invariants.js → invariants.js} +0 -0
  32. package/source/{server/log.js → log.js} +0 -0
  33. package/source/{server/servers/mimes.json → mimes.json} +0 -0
  34. package/source/preset/primate.json +3 -7
  35. package/source/preset/static/index.html +0 -2
  36. package/source/preset/stores/default.js +1 -1
  37. package/source/{server/sanitize.js → sanitize.js} +0 -0
  38. package/source/{server/store → store}/Memory.js +0 -0
  39. package/source/{server/store → store}/Store.js +1 -1
  40. package/source/{server/types → types}/Array.js +0 -0
  41. package/source/{server/types → types}/Boolean.js +0 -0
  42. package/source/{server/types → types}/Date.js +0 -0
  43. package/source/{server/types → types}/Domain.js +0 -0
  44. package/source/{server/types → types}/Instance.js +0 -0
  45. package/source/{server/types → types}/Number.js +0 -0
  46. package/source/{server/types → types}/Object.js +0 -0
  47. package/source/{server/types → types}/Primitive.js +0 -0
  48. package/source/{server/types → types}/Storeable.js +0 -0
  49. package/source/{server/types → types}/String.js +0 -0
  50. package/source/{server/types → types}/errors/Array.json +0 -0
  51. package/source/{server/types → types}/errors/Boolean.json +0 -0
  52. package/source/{server/types → types}/errors/Date.json +0 -0
  53. package/source/{server/types → types}/errors/Number.json +0 -0
  54. package/source/{server/types → types}/errors/Object.json +0 -0
  55. package/source/{server/types → types}/errors/String.json +0 -0
  56. package/source/{server/types.js → types.js} +0 -0
  57. package/source/client/Action.js +0 -157
  58. package/source/client/App.js +0 -16
  59. package/source/client/Client.js +0 -61
  60. package/source/client/Context.js +0 -47
  61. package/source/client/Element.js +0 -249
  62. package/source/client/Node.js +0 -13
  63. package/source/client/Session.js +0 -27
  64. package/source/client/View.js +0 -89
  65. package/source/client/document.js +0 -6
  66. package/source/client/exports.js +0 -15
  67. package/source/preset/client/Element.js +0 -2
  68. package/source/preset/client/app.js +0 -2
  69. package/source/server/Action.js +0 -100
  70. package/source/server/App.js +0 -62
  71. package/source/server/Bundler.js +0 -177
  72. package/source/server/Context.js +0 -97
  73. package/source/server/Projector.js +0 -86
  74. package/source/server/Router.js +0 -52
  75. package/source/server/domain/domains.js +0 -31
  76. package/source/server/servers/Dynamic.js +0 -57
  77. package/source/server/servers/Server.js +0 -5
  78. package/source/server/servers/Static.js +0 -117
  79. package/source/server/view/TreeNode.js +0 -197
  80. package/source/server/view/View.js +0 -35
@@ -1,47 +0,0 @@
1
- import Action from "./Action.js";
2
- import {routes} from "./exports.js";
3
-
4
- export default class Context {
5
- constructor(name, session) {
6
- this.name = name;
7
- this.actions = {};
8
- this.used_actions = [];
9
- this.session = session;
10
- }
11
-
12
- get Class() {
13
- return this.constructor;
14
- }
15
-
16
- async run(data) {
17
- const {namespace, action} = data;
18
- const route = `${namespace}/${action}`;
19
- const Module = routes?.[namespace]?.[action] ?? Action;
20
- this.actions[route] = new Module(action, this.session);
21
- const nextaction = this.actions[route];
22
- nextaction.enter(data);
23
- await this.Class.before(nextaction);
24
- nextaction.before();
25
- this.action = nextaction;
26
- }
27
-
28
- static before(action) {
29
- return action;
30
- }
31
-
32
- static prepare(action, data) {
33
- return data;
34
- }
35
-
36
- on(key, event) {
37
- this.action[`on${key}`](event);
38
- }
39
-
40
- push_action() {
41
- this.used_actions.push(this.action);
42
- }
43
-
44
- pop_action() {
45
- this.action = this.used_actions.pop();
46
- }
47
- }
@@ -1,249 +0,0 @@
1
- const reader = new FileReader();
2
- const file_listener = event => reader.readAsDataURL(event.target.files[0]);
3
-
4
- const replace = (attribute, source) => {
5
- if (attribute.includes(".")) {
6
- const index = attribute.indexOf(".");
7
- const left = attribute.slice(0, index);
8
- const rest = attribute.slice(index+1);
9
- if (source[left] !== undefined) {
10
- return replace(rest, source[left]);
11
- }
12
- } else {
13
- return source[attribute];
14
- }
15
- };
16
-
17
- const data_prefix_length = 5;
18
- const data_regex = /\${([^}]*)}/g;
19
- const fulfill = (attribute, source) => {
20
- if (source === undefined) {
21
- return undefined;
22
- }
23
- let {value} = attribute;
24
- const matches = [...value.matchAll(data_regex)];
25
- if (matches.length > 0) {
26
- for (const match of matches) {
27
- const [key] = match;
28
- const new_value = replace(match[1], source);
29
- value = value.replace(key, new_value);
30
- }
31
- } else {
32
- value = replace(value, source);
33
- }
34
- return value;
35
- };
36
-
37
- const $selector = Symbol("$selector");
38
- const $element = Symbol("$element");
39
- const $action = Symbol("$action");
40
-
41
- const proxy_handler = {
42
- "get": (target, property, receiver) => {
43
- if (property === "element") {
44
- return target.element;
45
- }
46
- if (property === "on" || property === "insert") {
47
- return target[property].bind(target);
48
- }
49
- if (target.element === null) {
50
- return () => receiver;
51
- }
52
-
53
- return (...args) => target[property] === undefined
54
- ? receiver
55
- : target[property](...args);
56
- },
57
- };
58
-
59
- export default class Element {
60
- constructor(selector, action) {
61
- this[$action] = action;
62
- if (typeof selector === "string") {
63
- this.construct_by_selector(selector);
64
- } else {
65
- this.construct_by_element(selector);
66
- }
67
- return new Proxy(this, proxy_handler);
68
- }
69
-
70
- construct_by_selector(selector) {
71
- this[$selector] = selector;
72
- }
73
-
74
- construct_by_element(element) {
75
- this[$element] = element;
76
- }
77
-
78
- static value(element, name) {
79
- return element?.attributes.getNamedItem(name)?.value;
80
- }
81
-
82
- static evaluate(attribute, source, data) {
83
- const element = new this(source);
84
- const attribute_name = attribute.name.slice(data_prefix_length);
85
- element.resolve(attribute_name)(fulfill(attribute, data), attribute_name);
86
- }
87
-
88
- resolve(name) {
89
- return (this[name] ?? this.default).bind(this);
90
- }
91
-
92
- get element() {
93
- return this[$element] ?? document.querySelector(this[$selector]);
94
- }
95
-
96
- each(callback) {
97
- document.querySelectorAll(this[$selector]).forEach(callback);
98
- }
99
-
100
- get(name) {
101
- return this.element.getAttribute(name);
102
- }
103
-
104
- has(attribute) {
105
- return this.element[attribute] !== undefined;
106
- }
107
-
108
- show() {
109
- return this.class("show");
110
- }
111
-
112
- hide() {
113
- return this.class("show", false);
114
- }
115
-
116
- insert() {
117
- const element = document.createElement("div");
118
- if (this[$selector].startsWith(".")) {
119
- element.className = this[$selector].slice(1);
120
- }
121
- document.body.append(element);
122
- return this;
123
- }
124
-
125
- replace(child) {
126
- this.element.innerHTML = "";
127
- return this.append(child);
128
- }
129
-
130
- append(child) {
131
- if (child instanceof HTMLElement) {
132
- this.element.appendChild(child);
133
- } else {
134
- this.element.innerHTML += child;
135
- }
136
- return this;
137
- }
138
-
139
- set(name, value) {
140
- this.element.setAttribute(name, value);
141
- return this;
142
- }
143
-
144
- class(classes, set) {
145
- classes.split(" ").forEach(name => {
146
- this.element.classList.toggle(name, set !== false);
147
- });
148
- return this;
149
- }
150
-
151
- remove() {
152
- this.element.remove();
153
- return this;
154
- }
155
-
156
- remove_attribute(name) {
157
- this.element.removeAttribute(name);
158
- return this;
159
- }
160
-
161
- click() {
162
- this.element.click();
163
- return this;
164
- }
165
-
166
- on(event, handler) {
167
- this[$action].on(this[$selector], event, handler);
168
- return this;
169
- }
170
-
171
- defined(value) {
172
- return value === undefined && this.remove();
173
- }
174
-
175
- html(value) {
176
- if (value !== undefined) {
177
- this.element.innerHTML = value;
178
- }
179
- return this;
180
- }
181
-
182
- options(value) {
183
- value instanceof Array && value.forEach(option => {
184
- const children = [...this.element.children];
185
- if (!children.find(child => child.value === option._id)) {
186
- const newElement = document.createElement("option");
187
- newElement.setAttribute("value", option._id);
188
- newElement.innerHTML = option.name;
189
- this.append(newElement);
190
- }
191
- });
192
- }
193
-
194
- if(value) {
195
- return value !== true && this.remove();
196
- }
197
-
198
- value(value) {
199
- const name = `${this.element.localName}_value`;
200
- this[name]?.(value) ?? this.default_value(value);
201
- }
202
-
203
- select_value(value) {
204
- this.element.value = value === undefined ? "" : value;
205
- return this;
206
- }
207
-
208
- input_value(value) {
209
- const type = Element.value(this.element, "type");
210
- this[`input_${type}_value`]?.(value) ?? this.input_input_value(value);
211
- return this;
212
- }
213
-
214
- textarea_value(value) {
215
- return this.input_value(value);
216
- }
217
-
218
- input_checkbox_value(value) {
219
- if (value === true) {
220
- this.element.checked = true;
221
- }
222
- return this;
223
- }
224
-
225
- input_file_value(value) {
226
- const {element} = this;
227
- if (value !== undefined) {
228
- element.setAttribute("base64", value);
229
- }
230
- reader.onload = ({target}) => element.setAttribute("base64", target.result);
231
- element.addEventListener("change", file_listener);
232
- return this;
233
- }
234
-
235
- input_input_value(value) {
236
- if (value !== undefined) {
237
- this.element.value = value;
238
- }
239
- return this;
240
- }
241
-
242
- default_value(value) {
243
- this.element.innerText = value ?? "";
244
- }
245
-
246
- default(value, attribute) {
247
- value !== undefined && this.element.setAttribute(attribute, value);
248
- }
249
- }
@@ -1,13 +0,0 @@
1
- export default class Node {
2
- constructor(node) {
3
- this.node = node;
4
- }
5
-
6
- get attributes() {
7
- return [...this.node.attributes];
8
- }
9
-
10
- get children() {
11
- return [...this.node.children];
12
- }
13
- }
@@ -1,27 +0,0 @@
1
- import Context from "./Context.js";
2
- import {contexts} from "./exports.js";
3
-
4
- export default class Session {
5
- constructor(client) {
6
- this.client = client;
7
- }
8
-
9
- async run(data) {
10
- const {context} = data;
11
- this.context = new (contexts[context] ?? Context)(context, this);
12
-
13
- await this.context.run(data);
14
- }
15
-
16
- on(key, event) {
17
- this.context.on(key, event);
18
- }
19
-
20
- read(pathname) {
21
- return this.client.read(pathname);
22
- }
23
-
24
- write(pathname, payload) {
25
- return this.client.write(pathname, payload);
26
- }
27
- }
@@ -1,89 +0,0 @@
1
- // We do not import Element directly from "./Element.js", rather the otherwise
2
- // preset user version of it, to allow for the user overriding attributes.
3
- import Element from "../Element.js";
4
- import Node from "./Node.js";
5
-
6
- export default class View {
7
- constructor(data, action, root) {
8
- this.sources = {};
9
- this.store = data.payload;
10
- this.action = action;
11
- this.paint_into(data, root);
12
- }
13
-
14
- paint_into(data, root = document.body) {
15
- root.innerHTML = data.view;
16
- root.classList.add(data.context);
17
- this.unfold(root);
18
- if (root === document.body) {
19
- window.scrollTo(0, 0);
20
- }
21
- }
22
-
23
- unfold(node) {
24
- this.expand_attributes(new Node(node));
25
- new Node(node).children.forEach(child => this.unfold(child));
26
- }
27
-
28
- expand_attributes(node) {
29
- node.attributes.filter(attribute => attribute.name === "data-scope")
30
- .map(attribute => attribute.value)
31
- .forEach(scope => this.expand(node.node, this.store[scope], scope));
32
- }
33
-
34
- expand(node, data, name, from_update) {
35
- if (data instanceof Array) {
36
- if (from_update) {
37
- return;
38
- }
39
- const blueprint = document.createElement("div");
40
- while (node.children.length > 0) {
41
- blueprint.appendChild(node.firstChild);
42
- }
43
- for (const datum of data) {
44
- const element = blueprint.cloneNode(true);
45
- this.sources[`${name}-${datum._id}`] = element;
46
- node.appendChild(element);
47
- this.populate(element, datum, `${name}-${datum._id}`);
48
- }
49
- } else {
50
- this.sources[name] = node;
51
- if (data !== undefined) {
52
- this.populate(node, data, name);
53
- }
54
- }
55
- }
56
-
57
- populate(node, data, key, from_update) {
58
- const regex = /^data-/u;
59
- new Node(node).children.forEach(child => {
60
- let populate = true;
61
- for (let i = 0; i < child.attributes.length; i++) {
62
- const attribute = child.attributes[i];
63
- const {name, value} = attribute;
64
- if (regex.test(name)) {
65
- if (name === "data-scope") {
66
- this.expand(child, data[value], `${key}-${value}`, from_update);
67
- populate = false;
68
- } else {
69
- Element.evaluate(attribute, child, data);
70
- }
71
- }
72
- }
73
- child.children.length !== 0 && populate &&
74
- this.populate(child, data, key, from_update);
75
- });
76
- }
77
-
78
- update(data) {
79
- Object.keys(data).forEach(key =>
80
- this.sources[key] !== undefined &&
81
- this.populate(this.sources[key], data[key], key, true)
82
- );
83
- }
84
-
85
- write(pathname, payload) {
86
- return this.action.write(pathname, payload);
87
- }
88
-
89
- }
@@ -1,6 +0,0 @@
1
- const {baseURI} = document;
2
- const {host, href, origin} = document.location;
3
- const base = baseURI === href ? "/" : baseURI.replace(origin, "");
4
- const origin_base = `${origin}${base}`;
5
-
6
- export {base, origin, origin_base, host};
@@ -1,15 +0,0 @@
1
- const timeout = (callback, delay = 0) => new Promise(resolve =>
2
- window.setTimeout(() => resolve(callback()), delay));
3
-
4
- // This comes first, as Action > View > ../Element.js for user overrideability.
5
- export {default as Element} from "./Element.js";
6
- export {default as Action} from "./Action.js";
7
- export {default as Context} from "./Context.js";
8
- export {contexts, routes} from "../exports.js";
9
- export {timeout};
10
-
11
- import App from "./App.js";
12
-
13
- const app = new App();
14
-
15
- export {app};
@@ -1,2 +0,0 @@
1
- import {Element} from "./primate/exports.js";
2
- export default class extends Element {}
@@ -1,2 +0,0 @@
1
- import {app} from "./primate/exports.js";
2
- app.run();
@@ -1,100 +0,0 @@
1
- import {resolve} from "path";
2
- import {default as EagerPromise, eager} from "./EagerPromise.js";
3
- import View from "./view/View.js";
4
- import Projector from "./Projector.js";
5
- import {InternalServerError} from "./errors.js";
6
- import {assert, defined} from "./invariants.js";
7
- import sanitize from "./sanitize.js";
8
-
9
- export default class Action {
10
- static action = "index";
11
- static namespace = "default";
12
- static layout = "default";
13
-
14
- constructor(request, session, context) {
15
- this.request = request;
16
- this.session = session;
17
- this.context = EagerPromise.resolve(context);
18
- }
19
-
20
- static async new(request, session, context) {
21
- const {namespace = this.namespace, action = this.action} = request.path;
22
- assert(!namespace.includes("."), () => {
23
- throw new InternalServerError("namespace may not include a dot");
24
- });
25
- const route = `${namespace}/${action}`;
26
- const path = resolve(`${context.directory}/${route}.js`);
27
- try {
28
- const {"default": UserAction} = await import(path);
29
- return new UserAction(request, session, context);
30
- } catch (error) {
31
- throw new Error(`route \`${route}\` missing`);
32
- }
33
- }
34
-
35
- get Class() {
36
- return this.constructor;
37
- }
38
-
39
- get layout() {
40
- return this.Class.layout;
41
- }
42
-
43
- get path() {
44
- const {path} = this.request;
45
- const namespace = path.namespace ?? this.Class.namespace;
46
- const action = path.action ?? this.Class.action;
47
- const {_id} = path;
48
- return {action, namespace, _id};
49
- }
50
-
51
- get params() {
52
- return this.request.params;
53
- }
54
-
55
- async run(type) {
56
- defined(this[type]);
57
- this.before && await this.before();
58
- await this[type](sanitize(this.request.payload));
59
- }
60
-
61
- return(payload = {}) {
62
- this.respond("return", () => ({payload}));
63
- }
64
-
65
- view(data = {}, layout = true) {
66
- this.respond("view", async () => {
67
- const {_view, payload} = await this.prepare(data);
68
- const view = layout ? _view.file(this.layout) : _view.content;
69
- return {view, payload};
70
- });
71
- }
72
-
73
- update(data) {
74
- this.respond("update", async () =>
75
- ({"payload": (await this.prepare(data)).payload})
76
- );
77
- }
78
-
79
- redirect(location) {
80
- this.respond("redirect", () => ({location}));
81
- }
82
-
83
- async prepare(data = {}) {
84
- const route = `${this.path.namespace}/${this.path.action}`;
85
- const path = await eager`${this.context.directory}/${route}`;
86
- const _view = await View.new(path, await this.context.layouts);
87
- await this.context.Class.prepare(this, data);
88
- const payload = await new Projector(data,
89
- await _view.elements(this.layout)).project(route);
90
- return {payload, _view};
91
- }
92
-
93
- respond(type, how) {
94
- this.response = async () => {
95
- const context = await this.context.name;
96
- const {url} = this.request;
97
- return {...await how(), type, context, ...this.path, url};
98
- };
99
- }
100
- }
@@ -1,62 +0,0 @@
1
- import {resolve} from "path";
2
- import Bundler from "./Bundler.js";
3
- import File from "./File.js";
4
- import Router from "./Router.js";
5
- import DynamicServer from "./servers/Dynamic.js";
6
- import StaticServer from "./servers/Static.js";
7
- import cache from "./cache.js";
8
- import log from "./log.js";
9
- import package_json from "../../package.json" assert {"type": "json"};
10
-
11
- export default class App {
12
- constructor(conf) {
13
- this.conf = conf;
14
- this.Bundler = Bundler;
15
- }
16
-
17
- get routes() {
18
- return cache(this, "routes", async () => {
19
- try {
20
- const path = `${this.conf.root}/routes.json`;
21
- return (await import(path, {"assert": {"type": "json"}})).default;
22
- } catch (error) {
23
- // local routes.json not required
24
- return [];
25
- }
26
- });
27
- }
28
-
29
- async run() {
30
- log.reset("Primate").yellow(package_json.version);
31
-
32
- this.router = new Router(await this.routes, this.conf);
33
- const {index, hashes} = await new this.Bundler(this.conf).bundle();
34
- const {router} = this;
35
-
36
- const conf = {index, hashes, router,
37
- "serve_from": this.conf.paths.public,
38
- "http": {
39
- ...this.conf.http,
40
- "key": File.read_sync(resolve(this.conf.http.ssl.key)),
41
- "cert": File.read_sync(resolve(this.conf.http.ssl.cert)),
42
- },
43
- "context": this.conf.defaults.context,
44
- };
45
- this.static_server = new StaticServer(conf);
46
- await this.static_server.run();
47
-
48
- this.dynamic_server = new DynamicServer({router,
49
- "path": this.conf.base,
50
- "server": this.static_server.server,
51
- "context": this.conf.defaults.context,
52
- });
53
- await this.dynamic_server.run();
54
-
55
- const {port, host} = this.conf.http;
56
- this.static_server.listen(port, host);
57
- }
58
-
59
- stop() {
60
- this.static_server.close();
61
- }
62
- }