primate 0.5.0 → 0.6.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 (82) hide show
  1. package/README.md +18 -34
  2. package/debris.json +4 -0
  3. package/jsconfig.json +8 -0
  4. package/package.json +14 -11
  5. package/source/{server/App.js → App.js} +14 -19
  6. package/source/Bundler.js +43 -0
  7. package/source/{server/Directory.js → Directory.js} +0 -0
  8. package/source/{server/EagerPromise.js → EagerPromise.js} +0 -0
  9. package/source/{server/File.js → File.js} +4 -0
  10. package/source/Router.js +23 -0
  11. package/source/Server.js +103 -0
  12. package/source/Session.js +26 -0
  13. package/source/{server/attributes.js → attributes.js} +0 -0
  14. package/source/{server/cache.js → cache.js} +0 -0
  15. package/source/{server/conf.js → conf.js} +1 -1
  16. package/source/{server/crypto.js → crypto.js} +0 -0
  17. package/source/{server/domain → domain}/Domain.js +8 -10
  18. package/source/{server/domain → domain}/Field.js +0 -0
  19. package/source/{server/domain → domain}/Predicate.js +0 -0
  20. package/source/{server/domain → domain}/domains.js +1 -1
  21. package/source/{server/errors → errors}/InternalServer.js +0 -0
  22. package/source/{server/errors → errors}/Predicate.js +0 -0
  23. package/source/{server/errors.js → errors.js} +0 -0
  24. package/source/{server/exports.js → exports.js} +4 -2
  25. package/source/{server/extend_object.js → extend_object.js} +0 -0
  26. package/source/handlers/DOM/Node.js +188 -0
  27. package/source/{server/view → handlers/DOM}/Parser.js +22 -18
  28. package/source/handlers/html.js +27 -0
  29. package/source/handlers/http.js +6 -0
  30. package/source/handlers/redirect.js +10 -0
  31. package/source/{server/servers/http-codes.json → http-codes.json} +0 -0
  32. package/source/{server/invariants.js → invariants.js} +1 -1
  33. package/source/{server/log.js → log.js} +0 -0
  34. package/source/{server/servers/mimes.json → mimes.json} +0 -0
  35. package/source/preset/primate.json +13 -7
  36. package/source/preset/static/index.html +0 -2
  37. package/source/preset/stores/default.js +2 -0
  38. package/source/{server/sanitize.js → sanitize.js} +0 -0
  39. package/source/{server/store → store}/Memory.js +0 -0
  40. package/source/{server/store → store}/Store.js +1 -1
  41. package/source/{server/types → types}/Array.js +0 -0
  42. package/source/{server/types → types}/Boolean.js +0 -0
  43. package/source/{server/types → types}/Date.js +0 -0
  44. package/source/{server/types → types}/Domain.js +0 -0
  45. package/source/{server/types → types}/Instance.js +0 -0
  46. package/source/{server/types → types}/Number.js +0 -0
  47. package/source/{server/types → types}/Object.js +0 -0
  48. package/source/{server/types → types}/Primitive.js +0 -0
  49. package/source/{server/types → types}/Storeable.js +0 -0
  50. package/source/{server/types → types}/String.js +0 -0
  51. package/source/{server/types → types}/errors/Array.json +0 -0
  52. package/source/{server/types → types}/errors/Boolean.json +0 -0
  53. package/source/{server/types → types}/errors/Date.json +0 -0
  54. package/source/{server/types → types}/errors/Number.json +0 -0
  55. package/source/{server/types → types}/errors/Object.json +0 -0
  56. package/source/{server/types → types}/errors/String.json +0 -0
  57. package/source/{server/types.js → types.js} +0 -0
  58. package/source/client/Action.js +0 -157
  59. package/source/client/App.js +0 -16
  60. package/source/client/Client.js +0 -61
  61. package/source/client/Context.js +0 -47
  62. package/source/client/Element.js +0 -249
  63. package/source/client/Node.js +0 -13
  64. package/source/client/Session.js +0 -27
  65. package/source/client/View.js +0 -89
  66. package/source/client/document.js +0 -6
  67. package/source/client/exports.js +0 -15
  68. package/source/preset/client/Element.js +0 -2
  69. package/source/preset/client/app.js +0 -2
  70. package/source/preset/data/stores/default.js +0 -2
  71. package/source/server/Action.js +0 -99
  72. package/source/server/Bundler.js +0 -177
  73. package/source/server/Context.js +0 -97
  74. package/source/server/Projector.js +0 -86
  75. package/source/server/Router.js +0 -52
  76. package/source/server/Session.js +0 -45
  77. package/source/server/servers/Dynamic.js +0 -57
  78. package/source/server/servers/Server.js +0 -5
  79. package/source/server/servers/Static.js +0 -118
  80. package/source/server/servers/content-security-policy.json +0 -7
  81. package/source/server/view/TreeNode.js +0 -197
  82. package/source/server/view/View.js +0 -35
@@ -1,61 +0,0 @@
1
- import Session from "./Session.js";
2
- import {base, host, origin_base} from "./document.js";
3
-
4
- const events = ["submit", "click", "change", "input"];
5
- let back = undefined;
6
- const location = `wss://${host}${base}`;
7
-
8
- export default class Client {
9
- constructor(conf) {
10
- this.conf = conf;
11
- this.session = new Session(this);
12
-
13
- window.onpopstate = async event => {
14
- const {pathname, search} = event.target.location;
15
- return this.session.run(await this.read(pathname + search));
16
- };
17
-
18
- for (const key of events) {
19
- window.addEventListener(key, event => this.session.on(key, event));
20
- }
21
- }
22
-
23
- open() {
24
- return this.connection?.readyState === 1 ? this
25
- : new Promise(resolve => {
26
- this.connection = new WebSocket(location);
27
- this.connection.addEventListener("message", ({data}) => data === "open"
28
- ? resolve(this)
29
- : this.receive(JSON.parse(data)));
30
- });
31
- }
32
-
33
- async receive(data) {
34
- if (back === undefined) {
35
- await this.session.execute(data);
36
- } else {
37
- back(data);
38
- back = undefined;
39
- }
40
- }
41
-
42
- read(url) {
43
- return this.send("read", url);
44
- }
45
-
46
- write(url, payload) {
47
- return this.send("write", url, payload);
48
- }
49
-
50
- async send(type, url, payload = {}) {
51
- await this.open();
52
- const {pathname, search} = new URL(url, origin_base);
53
- const _url = pathname + search;
54
- return new Promise(resolve => {
55
- back = data => resolve(data);
56
- this.connection.send(JSON.stringify({
57
- type, pathname, search, payload, "url": _url,
58
- }));
59
- });
60
- }
61
- }
@@ -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,2 +0,0 @@
1
- import MemoryStore from "../../../server/store/Memory.js";
2
- export default new MemoryStore();
@@ -1,99 +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/InternalServer.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
- return new (await import(path)).default(request, session, context);
29
- } catch (error) {
30
- throw new Error(`route \`${route}\` missing`);
31
- }
32
- }
33
-
34
- get Class() {
35
- return this.constructor;
36
- }
37
-
38
- get layout() {
39
- return this.Class.layout;
40
- }
41
-
42
- get path() {
43
- const {path} = this.request;
44
- const namespace = path.namespace ?? this.Class.namespace;
45
- const action = path.action ?? this.Class.action;
46
- const {_id} = path;
47
- return {action, namespace, _id};
48
- }
49
-
50
- get params() {
51
- return this.request.params;
52
- }
53
-
54
- async run(type) {
55
- defined(this[type]);
56
- this.before && await this.before();
57
- await this[type](sanitize(this.request.payload));
58
- }
59
-
60
- return(payload = {}) {
61
- this.respond("return", () => ({payload}));
62
- }
63
-
64
- view(data = {}, layout = true) {
65
- this.respond("view", async () => {
66
- const {_view, payload} = await this.prepare(data);
67
- const view = layout ? _view.file(this.layout) : _view.content;
68
- return {view, payload};
69
- });
70
- }
71
-
72
- update(data) {
73
- this.respond("update", async () =>
74
- ({"payload": (await this.prepare(data)).payload})
75
- );
76
- }
77
-
78
- redirect(location) {
79
- this.respond("redirect", () => ({location}));
80
- }
81
-
82
- async prepare(data = {}) {
83
- const route = `${this.path.namespace}/${this.path.action}`;
84
- const path = await eager`${this.context.directory}/${route}`;
85
- const _view = await View.new(path, await this.context.layouts);
86
- await this.context.Class.prepare(this, data);
87
- const payload = await new Projector(data,
88
- await _view.elements(this.layout)).project(route);
89
- return {payload, _view};
90
- }
91
-
92
- respond(type, how) {
93
- this.response = async () => {
94
- const context = await this.context.name;
95
- const {url} = this.request;
96
- return {...await how(), type, context, ...this.path, url};
97
- };
98
- }
99
- }