primate 0.0.1 → 0.3.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 (75) hide show
  1. package/LICENSE +27 -0
  2. package/README.md +50 -0
  3. package/package.json +15 -1
  4. package/source/client/Action.js +159 -0
  5. package/source/client/App.js +16 -0
  6. package/source/client/Base.js +5 -0
  7. package/source/client/Client.js +65 -0
  8. package/source/client/Context.js +53 -0
  9. package/source/client/Element.js +245 -0
  10. package/source/client/Node.js +13 -0
  11. package/source/client/View.js +90 -0
  12. package/source/client/document.js +6 -0
  13. package/source/client/exports.js +15 -0
  14. package/source/preset/client/Element.js +2 -0
  15. package/source/preset/client/app.js +2 -0
  16. package/source/preset/data/stores/default.js +2 -0
  17. package/source/preset/primate.json +31 -0
  18. package/source/preset/static/index.html +10 -0
  19. package/source/server/Action.js +118 -0
  20. package/source/server/App.js +42 -0
  21. package/source/server/Base.js +35 -0
  22. package/source/server/Bundler.js +180 -0
  23. package/source/server/Context.js +90 -0
  24. package/source/server/Crypto.js +8 -0
  25. package/source/server/Directory.js +35 -0
  26. package/source/server/File.js +117 -0
  27. package/source/server/Router.js +61 -0
  28. package/source/server/Session.js +69 -0
  29. package/source/server/attributes.js +11 -0
  30. package/source/server/cache.js +17 -0
  31. package/source/server/conf.js +33 -0
  32. package/source/server/domain/Domain.js +277 -0
  33. package/source/server/domain/Field.js +115 -0
  34. package/source/server/domain/domains.js +15 -0
  35. package/source/server/errors/Fallback.js +1 -0
  36. package/source/server/errors/InternalServer.js +1 -0
  37. package/source/server/errors/Predicate.js +1 -0
  38. package/source/server/errors.js +3 -0
  39. package/source/server/exports.js +27 -0
  40. package/source/server/fallback.js +11 -0
  41. package/source/server/invariants.js +19 -0
  42. package/source/server/log.js +22 -0
  43. package/source/server/promises/Eager.js +49 -0
  44. package/source/server/promises/Meta.js +42 -0
  45. package/source/server/promises.js +2 -0
  46. package/source/server/servers/Dynamic.js +51 -0
  47. package/source/server/servers/Server.js +5 -0
  48. package/source/server/servers/Static.js +113 -0
  49. package/source/server/servers/content-security-policy.json +8 -0
  50. package/source/server/servers/http-codes.json +5 -0
  51. package/source/server/servers/mimes.json +12 -0
  52. package/source/server/store/Memory.js +60 -0
  53. package/source/server/store/Store.js +17 -0
  54. package/source/server/types/Array.js +33 -0
  55. package/source/server/types/Boolean.js +29 -0
  56. package/source/server/types/Date.js +20 -0
  57. package/source/server/types/Domain.js +16 -0
  58. package/source/server/types/File.js +20 -0
  59. package/source/server/types/Instance.js +8 -0
  60. package/source/server/types/Number.js +45 -0
  61. package/source/server/types/Object.js +12 -0
  62. package/source/server/types/Primitive.js +7 -0
  63. package/source/server/types/Storeable.js +51 -0
  64. package/source/server/types/String.js +49 -0
  65. package/source/server/types/errors/Array.json +7 -0
  66. package/source/server/types/errors/Boolean.json +5 -0
  67. package/source/server/types/errors/Date.json +3 -0
  68. package/source/server/types/errors/Number.json +9 -0
  69. package/source/server/types/errors/Object.json +3 -0
  70. package/source/server/types/errors/String.json +11 -0
  71. package/source/server/types.js +7 -0
  72. package/source/server/utils/extend_object.js +10 -0
  73. package/source/server/view/Parser.js +122 -0
  74. package/source/server/view/TreeNode.js +195 -0
  75. package/source/server/view/View.js +30 -0
@@ -0,0 +1,12 @@
1
+ import Instance from "./Instance.js";
2
+ import errors from "./errors/Object.json" assert {"type": "json"};
3
+
4
+ export default class extends Instance {
5
+ static get instance() {
6
+ return Object;
7
+ }
8
+
9
+ static get errors() {
10
+ return errors;
11
+ }
12
+ }
@@ -0,0 +1,7 @@
1
+ import Storeable from "./Storeable.js";
2
+
3
+ export default class extends Storeable {
4
+ static is(value) {
5
+ return typeof value === this.type;
6
+ }
7
+ }
@@ -0,0 +1,51 @@
1
+ import {PredicateError} from "../errors.js";
2
+
3
+ export default class {
4
+ static async verify(property, value, predicates, type) {
5
+ const coerced = this.coerce(value);
6
+ if (!await this.is(coerced, type)) {
7
+ throw new PredicateError(this.type_error(type));
8
+ }
9
+ await this.has(property, coerced, predicates);
10
+ return coerced;
11
+ }
12
+
13
+ static type_error() {
14
+ return this.errors.type;
15
+ }
16
+
17
+ static is() {
18
+ throw new Error("must be implemented");
19
+ }
20
+
21
+ static async has(property, value, predicates) {
22
+ for (const predicate of predicates) {
23
+ if (typeof predicate === "object") {
24
+ await predicate.function(property, ...predicate.params);
25
+ } else {
26
+ const [name, ...params] = predicate.split(":");
27
+ if (!this[name](value, ...params)) {
28
+ let error = this.errors[name];
29
+ for (let i = 0; i < params.length; i++) {
30
+ error = error.replace(`$${i+1}`, params[i]);
31
+ }
32
+ throw new PredicateError(error);
33
+ }
34
+ }
35
+ }
36
+ }
37
+
38
+ static coerce(value) {
39
+ return value;
40
+ }
41
+
42
+ // noop for builtin types
43
+ static serialize(value) {
44
+ return value;
45
+ }
46
+
47
+ // noop for builtin types
48
+ static deserialize(value) {
49
+ return value;
50
+ }
51
+ }
@@ -0,0 +1,49 @@
1
+ import Primitive from "./Primitive.js";
2
+ import errors from "./errors/String.json" assert {"type": "json"};
3
+
4
+ export default class extends Primitive {
5
+ static get type() {
6
+ return "string";
7
+ }
8
+
9
+ static get instance() {
10
+ return String;
11
+ }
12
+
13
+ static get errors() {
14
+ return errors;
15
+ }
16
+
17
+ static length(value, length) {
18
+ return value.length === Number(length);
19
+ }
20
+
21
+ static min(value, minimum) {
22
+ return value.length >= Number(minimum);
23
+ }
24
+
25
+ static max(value, maximum) {
26
+ return value.length <= Number(maximum);
27
+ }
28
+
29
+ static between(value, minimum, maximum) {
30
+ const length = value.length;
31
+ return length >= Number(minimum) && length <= Number(maximum);
32
+ }
33
+
34
+ static lowercase(value) {
35
+ return value === value.toLowerCase();
36
+ }
37
+
38
+ static uppercase(value) {
39
+ return value === value.toUpperCase();
40
+ }
41
+
42
+ static alphanumeric(value) {
43
+ return /^[a-z0-9]+$/iu.test(value);
44
+ }
45
+
46
+ static regex(value, pattern) {
47
+ return new RegExp(pattern, "u").test(value);
48
+ }
49
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "type": "Must be an array",
3
+ "length": "Must be $1 items in length",
4
+ "min": "Must be at least $1 items in length",
5
+ "max": "Must be at most $1 items in length",
6
+ "between": "Must be between $1 and $2 items in length"
7
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "type": "Must be a boolean",
3
+ "true": "Must be true",
4
+ "false": "Must be false"
5
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "Must be a date"
3
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "type": "Must be a number",
3
+ "integer": "Must be an integer",
4
+ "positive": "Must be a positive number",
5
+ "negative": "Must be a negative number",
6
+ "between": "Must be between $1 and $2",
7
+ "min": "Must be at least $1",
8
+ "max": "Must be at most $1"
9
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "Must be an object"
3
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "type": "Must be a string",
3
+ "length": "Must be $1 characters in length",
4
+ "min": "Must be at least $1 characters in length",
5
+ "max": "Must be at most $1 characters in length",
6
+ "between": "Must be between $1 and $2 characters in length",
7
+ "lowercase": "Must be lowercase",
8
+ "uppercase": "Must be uppercase",
9
+ "alphanumeric": "Must be alphanumeric",
10
+ "regex": "Must adhere to the regular expression pattern '$1'"
11
+ }
@@ -0,0 +1,7 @@
1
+ export {default as ArrayType} from "./types/Array.js";
2
+ export {default as BooleanType} from "./types/Boolean.js";
3
+ export {default as DateType} from "./types/Date.js";
4
+ export {default as NumberType} from "./types/Number.js";
5
+ export {default as ObjectType} from "./types/Object.js";
6
+ export {default as StringType} from "./types/String.js";
7
+ export {default as FileType} from "./types/File.js";
@@ -0,0 +1,10 @@
1
+ const extend_object = (base = {}, extension = {}) =>
2
+ Object.keys(extension).reduce((result, property) => {
3
+ const value = extension[property];
4
+ result[property] = value?.constructor === Object
5
+ ? extend_object(base[property], value)
6
+ : value;
7
+ return result;
8
+ }, base);
9
+
10
+ export default extend_object;
@@ -0,0 +1,122 @@
1
+ import TreeNode from "./TreeNode.js";
2
+
3
+ const OPEN_TAG = "open_tag";
4
+ const CLOSE_TAG = "close_tag";
5
+ const OPEN_AND_CLOSE_TAG = "open_and_close_tag";
6
+ const last_index = -1;
7
+
8
+ export default class Parser {
9
+ constructor(html) {
10
+ this.html = html;
11
+ this.result = [];
12
+ this.index = 0;
13
+ this.buffer = "";
14
+ this.balance = 0;
15
+ this.reading_tag = false;
16
+ this.node = new TreeNode();
17
+ this.tree = this.node;
18
+ }
19
+
20
+ at(index) {
21
+ return this.html[index];
22
+ }
23
+
24
+ get previous() {
25
+ return this.at(this.index+last_index);
26
+ }
27
+
28
+ get current() {
29
+ return this.at(this.index);
30
+ }
31
+
32
+ get next() {
33
+ return this.at(this.index+1);
34
+ }
35
+
36
+ remove_whitespace() {
37
+ this.html = this.html.replace(/[\n\t\r]/gu, "");
38
+ }
39
+
40
+ open_tag() {
41
+ this.node = new TreeNode(this.node, this.buffer);
42
+ }
43
+
44
+ close_tag() {
45
+ if (this.node.parent !== undefined) {
46
+ this.node = this.node.parent;
47
+ }
48
+ }
49
+
50
+ open_and_close_tag() {
51
+ this.open_tag();
52
+ this.close_tag();
53
+ }
54
+
55
+ // currently inside tag
56
+ process_reading_tag() {
57
+ if (this.current === ">") {
58
+ // mark as outside tag
59
+ this.reading_tag = false;
60
+ // if the previous character is '/', it's an open and close tag
61
+ if (this.previous === "/") {
62
+ this.tag = OPEN_AND_CLOSE_TAG;
63
+ this.balance--;
64
+ this.buffer = this.buffer.slice(0, last_index);
65
+ }
66
+
67
+ // execute the function associated with this kind of tag
68
+ this[this.tag]();
69
+ // empty buffer
70
+ this.buffer = "";
71
+ } else {
72
+ this.buffer += this.current;
73
+ }
74
+ }
75
+
76
+ // currently outside of a tag
77
+ process_not_reading_tag() {
78
+ // encountered '<'
79
+ if (this.current === "<") {
80
+ // mark as inside tag
81
+ this.reading_tag = true;
82
+ if (this.next === "/") {
83
+ // next character is slash, this is a close tag
84
+ this.tag = CLOSE_TAG;
85
+ this.balance--;
86
+ } else {
87
+ // this is an open tag (or open-and-close)
88
+ this.tag = OPEN_TAG;
89
+ this.balance++;
90
+ }
91
+ }
92
+ }
93
+
94
+ read() {
95
+ if (this.reading_tag) {
96
+ this.process_reading_tag();
97
+ } else {
98
+ this.process_not_reading_tag();
99
+ }
100
+ this.index++;
101
+
102
+ return this.current !== undefined;
103
+ }
104
+
105
+ return_checked() {
106
+ if (this.balance !== 0) {
107
+ throw Error(`unbalanced DOM tree: ${this.balance}`);
108
+ }
109
+ return this.tree;
110
+ }
111
+
112
+ // TODO: ignore comments in the analysed html
113
+ parse() {
114
+ this.remove_whitespace();
115
+ do {} while (this.read());
116
+ return this.return_checked();
117
+ }
118
+
119
+ static parse(html) {
120
+ return new Parser(html).parse();
121
+ }
122
+ }
@@ -0,0 +1,195 @@
1
+ const data_regex = /\${([^}]*)}/g;
2
+
3
+ export default class TreeNode {
4
+ constructor(parent, content) {
5
+ this.children = [];
6
+ this.content = content;
7
+ if (parent !== undefined) {
8
+ this.parent = parent;
9
+ this.parent.attach(this);
10
+ }
11
+ }
12
+
13
+ attach(child) {
14
+ this.children.push(child);
15
+ child.parent = this;
16
+ }
17
+
18
+ // splices the child at the position i and hoists its children to i...
19
+ splice(i) {
20
+ if (this.children[i] !== undefined) {
21
+ const children = this.children[i].children;
22
+ for (const child of children) {
23
+ child.parent = this;
24
+ }
25
+ this.children.splice(i, 1, ...children);
26
+ }
27
+ }
28
+
29
+ filter(predicate) {
30
+ for (let i = this.children.length-1; i >= 0; i--) {
31
+ this.children[i].filter(predicate);
32
+ predicate(this.children[i].content) && this.splice(i);
33
+ }
34
+ return this;
35
+ }
36
+
37
+ each(operation) {
38
+ if (this.content !== undefined) {
39
+ operation(this);
40
+ }
41
+ for (let i = 0; i < this.children.length; i++) {
42
+ this.children[i].each(operation);
43
+ }
44
+ return this;
45
+ }
46
+
47
+ transform() {
48
+ const tree = [];
49
+ let flatten = [];
50
+
51
+ this.filter(content => !content.includes("data-"))
52
+ .each(node => {
53
+ const data_tags = node.content.split("data-");
54
+ data_tags.shift();
55
+ node.content = data_tags
56
+ .filter(part => part.includes("="))
57
+ .map(part => {
58
+ const index = part.indexOf("=");
59
+ const key = part.slice(0, index);
60
+ const right = part.slice(index+1);
61
+ return {key, "value": right.slice(1, right.indexOf("\"", 1))};
62
+ });
63
+ })
64
+ // transform template strings
65
+ .each(node => {
66
+ for (let i = 0; i < node.content.length; i++) {
67
+ const part = node.content[i];
68
+ const key = part.key;
69
+ const matches = [...part.value.matchAll(data_regex)];
70
+ if (matches.length > 0) {
71
+ for (const match of matches) {
72
+ // add new entries to node.content
73
+ node.content.push({key, "value": match[1]});
74
+ }
75
+ // remove this entry
76
+ node.content.splice(i, 1);
77
+ i--;
78
+ }
79
+ }
80
+ })
81
+ // unfold scopes
82
+ .each(node => {
83
+ for (let i = 0; i < node.content.length; i++) {
84
+ const part = node.content[i];
85
+ // memove scope and rework subtree
86
+ if (part.key === "scope") {
87
+ const scope = part.value;
88
+ for (let j = 0; j < node.children.length; j++) {
89
+ let recurse = true;
90
+ node.children[j].each(scopeable => {
91
+ // skip descending into children of data-scope
92
+ if (!recurse) {
93
+ return;
94
+ }
95
+ let found_scope = false;
96
+ scopeable.content.forEach(content => {
97
+ if (content.key === "scope") {
98
+ found_scope = true;
99
+ }
100
+ content.value = `${scope}.${content.value}`;
101
+ });
102
+ recurse = !found_scope;
103
+ });
104
+ }
105
+ node.content.splice(i, 1);
106
+ }
107
+ }
108
+ })
109
+ .each(node => {
110
+ flatten = flatten.concat(node.content.map(content => content.value));
111
+ });
112
+
113
+ const error = key => { throw new Error(
114
+ `\`${key}\` appears as both value and object key`); };
115
+ const put = (tree, part) => {
116
+ if (!part.includes(".")) {
117
+ if (!tree.includes(part)) {
118
+ tree.push(part);
119
+ }
120
+ for (const node of tree) {
121
+ if (typeof node === "object") {
122
+ if (node[part] !== undefined) {
123
+ error(part);
124
+ }
125
+ }
126
+ }
127
+ } else {
128
+ const index = part.indexOf(".");
129
+ const left = part.slice(0, index);
130
+ const right = part.slice(index+1);
131
+ if (tree.includes(left)) {
132
+ error(left);
133
+ }
134
+ let found_node = undefined;
135
+ for (const node of tree) {
136
+ if (typeof node === "object") {
137
+ if (node[left] !== undefined) {
138
+ found_node = node[left];
139
+ break;
140
+ }
141
+ }
142
+ }
143
+ if (found_node === undefined) {
144
+ found_node = [];
145
+ tree.push({[left]: found_node});
146
+ }
147
+ put(found_node, right);
148
+ }
149
+ };
150
+ flatten.forEach(part => put(tree, part));
151
+ return tree;
152
+ }
153
+
154
+ flatten() {
155
+ const object = {};
156
+ if (this.children.length === 0) {
157
+ return this.content.key;
158
+ }
159
+ const key = this.content ? this.content.key : "root";
160
+ object[key] = [];
161
+ for (const child of this.children) {
162
+ if (child !== undefined) {
163
+ const interim = child.flatten();
164
+ if (interim instanceof Array) {
165
+ object[key] = object[key].concat(interim);
166
+ } else {
167
+ object[key].push(interim);
168
+ }
169
+ const strings = [];
170
+ const objects = {};
171
+ object[key] = object[key].filter(value => {
172
+ if (typeof value === "string") {
173
+ if (strings.includes(value)) {
174
+ return false;
175
+ }
176
+ strings.push(value);
177
+ return true;
178
+ } else {
179
+ const key = Object.keys(value)[0];
180
+ value = value[key];
181
+ // merge with it
182
+ if (objects[key] !== undefined) {
183
+ objects[key].push(...value);
184
+ return false;
185
+ } else {
186
+ objects[key] = value;
187
+ return true;
188
+ }
189
+ }
190
+ });
191
+ }
192
+ }
193
+ return object;
194
+ }
195
+ }
@@ -0,0 +1,30 @@
1
+ import Parser from "./Parser.js";
2
+ import {InternalServerError} from "../errors.js";
3
+
4
+ const $content = "${content}";
5
+
6
+ export default class View {
7
+ constructor(path, content, layouts) {
8
+ this.path = path;
9
+ this.content = content;
10
+ this.layouts = layouts;
11
+ }
12
+
13
+ elements(layout) {
14
+ try {
15
+ return Parser.parse(layout === undefined
16
+ ? this.content
17
+ : this.file(layout)).transform();
18
+ } catch (error) {
19
+ throw new InternalServerError(`${this.path} ${error.message}`);
20
+ }
21
+ }
22
+
23
+ layout(layout) {
24
+ return this.layouts[layout] ?? $content;
25
+ }
26
+
27
+ file(layout = "default") {
28
+ return this.layout(layout).replace($content, this.content);
29
+ }
30
+ }