primate 0.4.0 → 0.5.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.
- package/README.md +7 -2
- package/package.json +2 -2
- package/source/client/Action.js +8 -7
- package/source/client/Client.js +3 -3
- package/source/client/Context.js +2 -2
- package/source/client/Element.js +10 -11
- package/source/client/View.js +1 -2
- package/source/server/Action.js +7 -4
- package/source/server/App.js +1 -1
- package/source/server/Bundler.js +2 -2
- package/source/server/Context.js +1 -2
- package/source/server/EagerPromise.js +1 -1
- package/source/server/Projector.js +12 -12
- package/source/server/Router.js +12 -19
- package/source/server/attributes.js +1 -0
- package/source/server/domain/Field.js +21 -26
- package/source/server/domain/Predicate.js +24 -0
- package/source/server/errors/InternalServer.js +1 -1
- package/source/server/errors/Predicate.js +1 -1
- package/source/server/exports.js +1 -1
- package/source/server/invariants.js +23 -8
- package/source/server/sanitize.js +8 -3
- package/source/server/servers/Dynamic.js +4 -4
- package/source/server/servers/Server.js +1 -1
- package/source/server/servers/Static.js +1 -1
- package/source/server/types/Array.js +2 -2
- package/source/server/types/Boolean.js +2 -2
- package/source/server/types/Date.js +2 -2
- package/source/server/types/Domain.js +1 -1
- package/source/server/types/Instance.js +1 -1
- package/source/server/types/Number.js +2 -2
- package/source/server/types/Object.js +2 -2
- package/source/server/types/Primitive.js +1 -1
- package/source/server/types/Storeable.js +9 -15
- package/source/server/types/String.js +2 -2
- package/source/server/view/View.js +1 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ yarn add primate
|
|
|
27
27
|
* **Sane defaults** minimally opinionated with good, overrideable defaults
|
|
28
28
|
for most features
|
|
29
29
|
* **Data linking** modeling `1:1`, `1:n` and `n:m` relationships on domains via
|
|
30
|
-
ad-hoc getters
|
|
30
|
+
cacheable ad-hoc getters
|
|
31
31
|
|
|
32
32
|
## Getting started
|
|
33
33
|
|
|
@@ -35,7 +35,10 @@ See the [getting started][getting-started] guide.
|
|
|
35
35
|
|
|
36
36
|
## Resources
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
* [Source code][source-code]
|
|
39
|
+
* [Issues][issues]
|
|
40
|
+
|
|
41
|
+
A full guide is coming soon.
|
|
39
42
|
|
|
40
43
|
## Versioning
|
|
41
44
|
|
|
@@ -49,3 +52,5 @@ BSD-3-Clause
|
|
|
49
52
|
|
|
50
53
|
[ws]: https://github.com/websockets/ws
|
|
51
54
|
[getting-started]: https://primatejs.com/getting-started
|
|
55
|
+
[source-code]: https://adaptivecloud.dev/primate/primate
|
|
56
|
+
[issues]: https://adaptivecloud.dev/primate/primate/issues
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"author": "Primate core team <core@primatejs.com>",
|
|
5
5
|
"homepage": "https://primatejs.com",
|
|
6
6
|
"bugs": "https://adaptivecloud.dev/primate/primate/issues",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"description": "Server-client framework",
|
|
9
9
|
"license": "BSD-3-Clause",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"ws": "^8.
|
|
11
|
+
"ws": "^8.5.0"
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
14
|
"copy": "rm -rf output && mkdir output && cp source/* output -a",
|
package/source/client/Action.js
CHANGED
|
@@ -11,7 +11,7 @@ export default class Action {
|
|
|
11
11
|
|
|
12
12
|
before() {}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
enter(data) {
|
|
15
15
|
if (data.location) {
|
|
16
16
|
location.href = data.location;
|
|
17
17
|
} else {
|
|
@@ -28,13 +28,13 @@ export default class Action {
|
|
|
28
28
|
this.view = new View(data, this, root);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
create_and_return_view(data, root
|
|
31
|
+
create_and_return_view(data, root) {
|
|
32
32
|
return new View(data, this, root);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
get_form_data(target) {
|
|
36
36
|
const payload = {};
|
|
37
|
-
const elements = target
|
|
37
|
+
const {elements} = target;
|
|
38
38
|
for (let i = 0; i < elements.length; i++) {
|
|
39
39
|
const element = elements[i];
|
|
40
40
|
const type = Element.value(element, "type");
|
|
@@ -64,7 +64,7 @@ export default class Action {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
async onsubmit(event) {
|
|
67
|
-
const target = event
|
|
67
|
+
const {target} = event;
|
|
68
68
|
const stop = this.fire("submit", event);
|
|
69
69
|
if (stop) {
|
|
70
70
|
return event.preventDefault();
|
|
@@ -108,13 +108,13 @@ export default class Action {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
oninput() {
|
|
111
|
-
|
|
111
|
+
this.fire("input", event);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
fire(type, event) {
|
|
115
115
|
const listeners = this.listeners[type];
|
|
116
116
|
if (listeners !== undefined) {
|
|
117
|
-
const target = event
|
|
117
|
+
const {target} = event;
|
|
118
118
|
for (const listener of listeners) {
|
|
119
119
|
if (event.target.closest(listener.selector)) {
|
|
120
120
|
listener.listener(event, target);
|
|
@@ -131,7 +131,8 @@ export default class Action {
|
|
|
131
131
|
const index = this.listeners[event]
|
|
132
132
|
.findIndex(listener => listener.selector === selector);
|
|
133
133
|
const object = {selector, listener};
|
|
134
|
-
|
|
134
|
+
const not_found = -1;
|
|
135
|
+
if (index === not_found) {
|
|
135
136
|
this.listeners[event].push(object);
|
|
136
137
|
} else {
|
|
137
138
|
this.listeners[event].splice(index, 1, object);
|
package/source/client/Client.js
CHANGED
|
@@ -31,11 +31,11 @@ export default class Client {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
async receive(data) {
|
|
34
|
-
if (back
|
|
34
|
+
if (back === undefined) {
|
|
35
|
+
await this.session.execute(data);
|
|
36
|
+
} else {
|
|
35
37
|
back(data);
|
|
36
38
|
back = undefined;
|
|
37
|
-
} else {
|
|
38
|
-
await this.session.execute(data);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
package/source/client/Context.js
CHANGED
|
@@ -16,8 +16,8 @@ export default class Context {
|
|
|
16
16
|
async run(data) {
|
|
17
17
|
const {namespace, action} = data;
|
|
18
18
|
const route = `${namespace}/${action}`;
|
|
19
|
-
const
|
|
20
|
-
this.actions[route] = new
|
|
19
|
+
const Module = routes?.[namespace]?.[action] ?? Action;
|
|
20
|
+
this.actions[route] = new Module(action, this.session);
|
|
21
21
|
const nextaction = this.actions[route];
|
|
22
22
|
nextaction.enter(data);
|
|
23
23
|
await this.Class.before(nextaction);
|
package/source/client/Element.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const file_listener = event => reader.readAsDataURL(event.target.files[0]);
|
|
2
1
|
const reader = new FileReader();
|
|
2
|
+
const file_listener = event => reader.readAsDataURL(event.target.files[0]);
|
|
3
3
|
|
|
4
4
|
const replace = (attribute, source) => {
|
|
5
5
|
if (attribute.includes(".")) {
|
|
@@ -20,11 +20,11 @@ const fulfill = (attribute, source) => {
|
|
|
20
20
|
if (source === undefined) {
|
|
21
21
|
return undefined;
|
|
22
22
|
}
|
|
23
|
-
let value = attribute
|
|
23
|
+
let {value} = attribute;
|
|
24
24
|
const matches = [...value.matchAll(data_regex)];
|
|
25
25
|
if (matches.length > 0) {
|
|
26
26
|
for (const match of matches) {
|
|
27
|
-
const key = match
|
|
27
|
+
const [key] = match;
|
|
28
28
|
const new_value = replace(match[1], source);
|
|
29
29
|
value = value.replace(key, new_value);
|
|
30
30
|
}
|
|
@@ -34,7 +34,6 @@ const fulfill = (attribute, source) => {
|
|
|
34
34
|
return value;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
|
|
38
37
|
const $selector = Symbol("$selector");
|
|
39
38
|
const $element = Symbol("$element");
|
|
40
39
|
const $action = Symbol("$action");
|
|
@@ -47,13 +46,13 @@ const proxy_handler = {
|
|
|
47
46
|
if (property === "on" || property === "insert") {
|
|
48
47
|
return target[property].bind(target);
|
|
49
48
|
}
|
|
50
|
-
if (target.element
|
|
51
|
-
return (...args) => target[property] !== undefined
|
|
52
|
-
? target[property](...args)
|
|
53
|
-
: receiver;
|
|
54
|
-
} else {
|
|
49
|
+
if (target.element === null) {
|
|
55
50
|
return () => receiver;
|
|
56
51
|
}
|
|
52
|
+
|
|
53
|
+
return (...args) => target[property] === undefined
|
|
54
|
+
? receiver
|
|
55
|
+
: target[property](...args);
|
|
57
56
|
},
|
|
58
57
|
};
|
|
59
58
|
|
|
@@ -145,7 +144,7 @@ export default class Element {
|
|
|
145
144
|
class(classes, set) {
|
|
146
145
|
classes.split(" ").forEach(name => {
|
|
147
146
|
this.element.classList.toggle(name, set !== false);
|
|
148
|
-
})
|
|
147
|
+
});
|
|
149
148
|
return this;
|
|
150
149
|
}
|
|
151
150
|
|
|
@@ -224,7 +223,7 @@ export default class Element {
|
|
|
224
223
|
}
|
|
225
224
|
|
|
226
225
|
input_file_value(value) {
|
|
227
|
-
const element = this
|
|
226
|
+
const {element} = this;
|
|
228
227
|
if (value !== undefined) {
|
|
229
228
|
element.setAttribute("base64", value);
|
|
230
229
|
}
|
package/source/client/View.js
CHANGED
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
import Element from "../Element.js";
|
|
4
4
|
import Node from "./Node.js";
|
|
5
5
|
|
|
6
|
-
const data_regex = /\${([^}]*)}/g;
|
|
7
|
-
|
|
8
6
|
export default class View {
|
|
9
7
|
constructor(data, action, root) {
|
|
10
8
|
this.sources = {};
|
|
@@ -87,4 +85,5 @@ export default class View {
|
|
|
87
85
|
write(pathname, payload) {
|
|
88
86
|
return this.action.write(pathname, payload);
|
|
89
87
|
}
|
|
88
|
+
|
|
90
89
|
}
|
package/source/server/Action.js
CHANGED
|
@@ -3,7 +3,7 @@ import {default as EagerPromise, eager} from "./EagerPromise.js";
|
|
|
3
3
|
import View from "./view/View.js";
|
|
4
4
|
import Projector from "./Projector.js";
|
|
5
5
|
import InternalServerError from "./errors/InternalServer.js";
|
|
6
|
-
import {assert} from "./invariants.js";
|
|
6
|
+
import {assert, defined} from "./invariants.js";
|
|
7
7
|
import sanitize from "./sanitize.js";
|
|
8
8
|
|
|
9
9
|
export default class Action {
|
|
@@ -20,7 +20,8 @@ export default class Action {
|
|
|
20
20
|
static async new(request, session, context) {
|
|
21
21
|
const {namespace = this.namespace, action = this.action} = request.path;
|
|
22
22
|
assert(!namespace.includes("."), () => {
|
|
23
|
-
throw new InternalServerError("namespace may not include a dot");
|
|
23
|
+
throw new InternalServerError("namespace may not include a dot");
|
|
24
|
+
});
|
|
24
25
|
const route = `${namespace}/${action}`;
|
|
25
26
|
const path = resolve(`${context.directory}/${route}.js`);
|
|
26
27
|
try {
|
|
@@ -42,7 +43,7 @@ export default class Action {
|
|
|
42
43
|
const {path} = this.request;
|
|
43
44
|
const namespace = path.namespace ?? this.Class.namespace;
|
|
44
45
|
const action = path.action ?? this.Class.action;
|
|
45
|
-
const _id = path
|
|
46
|
+
const {_id} = path;
|
|
46
47
|
return {action, namespace, _id};
|
|
47
48
|
}
|
|
48
49
|
|
|
@@ -51,6 +52,7 @@ export default class Action {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
async run(type) {
|
|
55
|
+
defined(this[type]);
|
|
54
56
|
this.before && await this.before();
|
|
55
57
|
await this[type](sanitize(this.request.payload));
|
|
56
58
|
}
|
|
@@ -82,7 +84,8 @@ export default class Action {
|
|
|
82
84
|
const path = await eager`${this.context.directory}/${route}`;
|
|
83
85
|
const _view = await View.new(path, await this.context.layouts);
|
|
84
86
|
await this.context.Class.prepare(this, data);
|
|
85
|
-
const payload = await new Projector(data,
|
|
87
|
+
const payload = await new Projector(data,
|
|
88
|
+
await _view.elements(this.layout)).project(route);
|
|
86
89
|
return {payload, _view};
|
|
87
90
|
}
|
|
88
91
|
|
package/source/server/App.js
CHANGED
|
@@ -31,7 +31,7 @@ export default class App {
|
|
|
31
31
|
|
|
32
32
|
this.router = new Router(await this.routes, this.conf);
|
|
33
33
|
const {index, hashes} = await new this.Bundler(this.conf).bundle();
|
|
34
|
-
const router = this
|
|
34
|
+
const {router} = this;
|
|
35
35
|
|
|
36
36
|
const conf = {index, hashes, router,
|
|
37
37
|
"serve_from": this.conf.paths.public,
|
package/source/server/Bundler.js
CHANGED
|
@@ -94,7 +94,7 @@ export default class Bundler {
|
|
|
94
94
|
const index_html = await File.read(`${paths.public}/${this.index}`);
|
|
95
95
|
await File.remove(`${paths.public}/${this.index}`);
|
|
96
96
|
|
|
97
|
-
const body =
|
|
97
|
+
const body = `return \`${index_html}\``;
|
|
98
98
|
const index = new Function("conf", "client", body)(this.conf, this.client);
|
|
99
99
|
|
|
100
100
|
return {index, "hashes": this.hashes};
|
|
@@ -138,7 +138,7 @@ export default class Bundler {
|
|
|
138
138
|
client += `<script type="module" integrity="${integrity}" src="${src}">`
|
|
139
139
|
+ "</script>\n";
|
|
140
140
|
}
|
|
141
|
-
return client
|
|
141
|
+
return `${client}<first-view />`;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
async write_exports() {
|
package/source/server/Context.js
CHANGED
|
@@ -2,7 +2,7 @@ import {resolve} from "path";
|
|
|
2
2
|
import File from "./File.js";
|
|
3
3
|
import Action from "./Action.js";
|
|
4
4
|
import {InternalServerError} from "./errors.js";
|
|
5
|
-
import {assert
|
|
5
|
+
import {assert} from "./invariants.js";
|
|
6
6
|
import cache from "./cache.js";
|
|
7
7
|
import log from "./log.js";
|
|
8
8
|
|
|
@@ -87,7 +87,6 @@ export default class Context {
|
|
|
87
87
|
const {type = "read"} = request;
|
|
88
88
|
try {
|
|
89
89
|
const action = await Action.new(request, session, this);
|
|
90
|
-
defined(action[type]);
|
|
91
90
|
await this.Class.before(action);
|
|
92
91
|
await action.run(type);
|
|
93
92
|
return action.response();
|
|
@@ -3,7 +3,7 @@ import {inconstructible_function} from "./attributes.js";
|
|
|
3
3
|
const $promise = Symbol("#promise");
|
|
4
4
|
|
|
5
5
|
const handler = {
|
|
6
|
-
"get":
|
|
6
|
+
"get": (target, property) => {
|
|
7
7
|
const promise = target[$promise];
|
|
8
8
|
|
|
9
9
|
if (["then", "catch"].includes(property)) {
|
|
@@ -15,7 +15,7 @@ export default class Projector {
|
|
|
15
15
|
}
|
|
16
16
|
this.documents = documents;
|
|
17
17
|
this.projection = projection.reduce((sofar, domain) => {
|
|
18
|
-
const key = Object.keys(domain)
|
|
18
|
+
const [key] = Object.keys(domain);
|
|
19
19
|
sofar[key] = domain[key];
|
|
20
20
|
return sofar;
|
|
21
21
|
}, {});
|
|
@@ -39,9 +39,9 @@ export default class Projector {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
object(document, projection, key, fields) {
|
|
42
|
-
return fields?.[key]
|
|
43
|
-
? this.
|
|
44
|
-
: this.
|
|
42
|
+
return fields?.[key] === undefined
|
|
43
|
+
? this.by_store(key, document, projection)
|
|
44
|
+
: this.by_cache(fields[key], key, document, projection);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
async one(document, projection) {
|
|
@@ -57,7 +57,7 @@ export default class Projector {
|
|
|
57
57
|
if (typeof property === "string") {
|
|
58
58
|
resolved[property] = await scalar(document, property);
|
|
59
59
|
} else if (typeof property === "object") {
|
|
60
|
-
const key = Object.keys(property)
|
|
60
|
+
const [key] = Object.keys(property);
|
|
61
61
|
resolved[key] = await this.object(document, property, key, fields);
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -68,17 +68,17 @@ export default class Projector {
|
|
|
68
68
|
return this[Array.isArray(document) ? "many" : "one"](document, projection);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
async project(
|
|
71
|
+
async project() {
|
|
72
72
|
return (await Promise.all(Object.keys(this.documents).map(async key => {
|
|
73
|
-
const resolved = this.projection[key]
|
|
74
|
-
?
|
|
75
|
-
:
|
|
73
|
+
const resolved = this.projection[key] === undefined
|
|
74
|
+
? undefined
|
|
75
|
+
: await this.resolve(this.documents[key], this.projection[key]);
|
|
76
76
|
return {key, resolved};
|
|
77
77
|
}))).reduce((projected, {key, resolved}) => {
|
|
78
|
-
if (resolved
|
|
79
|
-
projected[key] = resolved;
|
|
80
|
-
} else {
|
|
78
|
+
if (resolved === undefined) {
|
|
81
79
|
log.yellow(` \`${key}\` not projected`).nl();
|
|
80
|
+
} else {
|
|
81
|
+
projected[key] = resolved;
|
|
82
82
|
}
|
|
83
83
|
return projected;
|
|
84
84
|
}, {});
|
package/source/server/Router.js
CHANGED
|
@@ -4,7 +4,7 @@ export default class Router {
|
|
|
4
4
|
this.routes = {};
|
|
5
5
|
routes.forEach(({from, to, contexts}) => contexts.forEach(context => {
|
|
6
6
|
this.routes[context] = this.routes[context] ?? [];
|
|
7
|
-
this.routes[context].push({"from": new RegExp(
|
|
7
|
+
this.routes[context].push({"from": new RegExp(`^${from}$`, "u"), to});
|
|
8
8
|
}));
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -24,36 +24,29 @@ export default class Router {
|
|
|
24
24
|
const [path, search = ""] = pathname.split("?");
|
|
25
25
|
|
|
26
26
|
const route = this.route_by_context(context, path);
|
|
27
|
-
if (route
|
|
28
|
-
const replace = path.replace(route.from, route.to);
|
|
29
|
-
return `${replace}${replace.includes("?") ? "" : "?"}&${search}`;
|
|
30
|
-
} else {
|
|
27
|
+
if (route === undefined) {
|
|
31
28
|
return pathname;
|
|
32
29
|
}
|
|
30
|
+
|
|
31
|
+
const replace = path.replace(route.from, route.to);
|
|
32
|
+
return `${replace}${replace.includes("?") ? "" : "?"}&${search}`;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
resolve(pathname, context) {
|
|
36
|
-
return this.context(context)
|
|
37
|
-
?
|
|
38
|
-
: pathname;
|
|
36
|
+
return this.context(context) === undefined
|
|
37
|
+
? pathname
|
|
38
|
+
: this.resolve_by_context(pathname, context);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
async route(pathname, context) {
|
|
42
42
|
const resolved = await this.resolve(this.debase(pathname), context);
|
|
43
43
|
const url = new URL(`https://primatejs.com/${resolved}`);
|
|
44
44
|
const parts = url.pathname.split("/").filter(part => part !== "");
|
|
45
|
+
const [namespace, action, _id] = parts;
|
|
45
46
|
return {
|
|
46
|
-
"pathname": url.pathname,
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"path": {
|
|
50
|
-
"namespace": parts[0],
|
|
51
|
-
"action": parts[1],
|
|
52
|
-
"_id": parts[2],
|
|
53
|
-
},
|
|
54
|
-
"params": {
|
|
55
|
-
...Object.fromEntries(url.searchParams),
|
|
56
|
-
},
|
|
47
|
+
"pathname": url.pathname, resolved, parts,
|
|
48
|
+
"path": {namespace, action, _id},
|
|
49
|
+
"params": {...Object.fromEntries(url.searchParams)},
|
|
57
50
|
};
|
|
58
51
|
}
|
|
59
52
|
}
|
|
@@ -11,3 +11,4 @@ export const inconstructible_function = value =>
|
|
|
11
11
|
typeof value === "function" && !constructible(value);
|
|
12
12
|
export const numeric = value => !isNaN(parseFloat(value)) && isFinite(value);
|
|
13
13
|
export const boolish = value => value === "true" || value === "false";
|
|
14
|
+
export const nullish = value => value === undefined || value === null;
|
|
@@ -1,28 +1,30 @@
|
|
|
1
|
-
import * as types from "../types.js";
|
|
2
1
|
import DomainType from "../types/Domain.js";
|
|
3
|
-
import
|
|
2
|
+
import Predicate from "./Predicate.js";
|
|
4
3
|
import {PredicateError} from "../errors.js";
|
|
5
|
-
import
|
|
4
|
+
import Storeable from "../types/Storeable.js";
|
|
5
|
+
import * as types from "../types.js";
|
|
6
|
+
import cache from "../cache.js";
|
|
6
7
|
import {constructible} from "../attributes.js";
|
|
8
|
+
import {defined, is, maybe} from "../invariants.js";
|
|
7
9
|
|
|
8
10
|
const builtins = Object.values(types).reduce((aggregate, Type) => {
|
|
9
11
|
aggregate[Type.instance] = Type;
|
|
10
12
|
return aggregate;
|
|
11
13
|
}, {});
|
|
12
14
|
|
|
13
|
-
const
|
|
14
|
-
? {"type": field}
|
|
15
|
-
: as_non_constructible(field);
|
|
15
|
+
const as_array = field => ({"type": field[0], "predicates": field.slice(1)});
|
|
16
16
|
|
|
17
|
-
const
|
|
18
|
-
field => typeof field === "function" ? as_function(field) : as_object(field);
|
|
17
|
+
const as_object = field => field instanceof Array ? as_array(field) : field;
|
|
19
18
|
|
|
20
19
|
const as_function = field => ({"in": field,
|
|
21
20
|
"type": field(undefined, {}).constructor});
|
|
22
21
|
|
|
23
|
-
const
|
|
22
|
+
const as_non_constructible =
|
|
23
|
+
field => typeof field === "function" ? as_function(field) : as_object(field);
|
|
24
24
|
|
|
25
|
-
const
|
|
25
|
+
const parse = field => constructible(field)
|
|
26
|
+
? {"type": field}
|
|
27
|
+
: as_non_constructible(field);
|
|
26
28
|
|
|
27
29
|
export default class Field {
|
|
28
30
|
constructor(property, definition, options) {
|
|
@@ -30,9 +32,9 @@ export default class Field {
|
|
|
30
32
|
this.property = property;
|
|
31
33
|
this.definition = parse(definition);
|
|
32
34
|
this.options = options ?? {"transient": false, "optional": false};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
is.constructible(this.Type);
|
|
36
|
+
is.subclass(this.type, Storeable);
|
|
37
|
+
maybe.array(this.definition.predicates);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
static resolve(name) {
|
|
@@ -57,12 +59,15 @@ export default class Field {
|
|
|
57
59
|
return this.Type.prototype instanceof DomainType.instance;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
get Type(){
|
|
62
|
+
get Type() {
|
|
61
63
|
return this.definition.type;
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
get predicates() {
|
|
65
|
-
return this
|
|
67
|
+
return cache(this, "predicates", () => {
|
|
68
|
+
const predicates = this.definition.predicates ?? [];
|
|
69
|
+
return predicates.map(name => new Predicate(name));
|
|
70
|
+
});
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
by_id(id) {
|
|
@@ -75,23 +80,13 @@ export default class Field {
|
|
|
75
80
|
return in_function !== undefined ? in_function(value, document) : value;
|
|
76
81
|
}
|
|
77
82
|
|
|
78
|
-
override_predicates(document) {
|
|
79
|
-
return this.predicates.map(predicate => {
|
|
80
|
-
const [name, ...params] = predicate.split(":");
|
|
81
|
-
return document[name] !== undefined
|
|
82
|
-
? {"function": document[name].bind(document), params}
|
|
83
|
-
: predicate;
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
83
|
verify_undefined() {
|
|
88
84
|
return this.options.optional ? true : "Must not be empty";
|
|
89
85
|
}
|
|
90
86
|
|
|
91
87
|
async verify_defined(property, document) {
|
|
92
88
|
try {
|
|
93
|
-
|
|
94
|
-
await this.type.verify(property, document, predicates, this.Type);
|
|
89
|
+
await this.type.verify(property, document, this.predicates, this.Type);
|
|
95
90
|
return true;
|
|
96
91
|
} catch (error) {
|
|
97
92
|
if (error instanceof PredicateError) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Domain from "./Domain.js";
|
|
2
|
+
import Storeable from "../types/Storeable.js";
|
|
3
|
+
import {is} from "../invariants.js";
|
|
4
|
+
|
|
5
|
+
export default class Predicate {
|
|
6
|
+
constructor(definition) {
|
|
7
|
+
is.string(definition);
|
|
8
|
+
const [name, ...params] = definition.split(":");
|
|
9
|
+
this.name = name;
|
|
10
|
+
this.params = params;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async check(property, document, Type) {
|
|
14
|
+
is.string(property);
|
|
15
|
+
is.instance(document, Domain);
|
|
16
|
+
const {name, params} = this;
|
|
17
|
+
if (document[name] === undefined) {
|
|
18
|
+
is.subclass(Type, Storeable);
|
|
19
|
+
await Type.has(name, document[property], params);
|
|
20
|
+
} else {
|
|
21
|
+
await document[name](property, ...params);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export default class extends Error {}
|
|
1
|
+
export default class InternalServerError extends Error {}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export default class extends Error {}
|
|
1
|
+
export default class PredicateError extends Error {}
|
package/source/server/exports.js
CHANGED
|
@@ -14,11 +14,11 @@ export {default as domains} from "./domain/domains.js";
|
|
|
14
14
|
export {default as Storeable} from "./types/Storeable.js";
|
|
15
15
|
|
|
16
16
|
export * from "./errors.js";
|
|
17
|
+
export * from "./invariants.js";
|
|
17
18
|
|
|
18
19
|
export {default as MemoryStore} from "./store/Memory.js";
|
|
19
20
|
export {default as Store} from "./store/Store.js";
|
|
20
21
|
|
|
21
|
-
export {assert, defined} from "./invariants.js";
|
|
22
22
|
export {default as log} from "./log.js";
|
|
23
23
|
export {default as extend_object} from "./extend_object.js";
|
|
24
24
|
export {default as sanitize} from "./sanitize.js";
|
|
@@ -1,17 +1,32 @@
|
|
|
1
|
-
import {constructible} from "./attributes.js";
|
|
1
|
+
import {constructible, nullish} from "./attributes.js";
|
|
2
2
|
|
|
3
3
|
const errored = error => {
|
|
4
|
-
if (typeof error === "function") {
|
|
4
|
+
if (typeof error === "function") {
|
|
5
|
+
// fallback
|
|
5
6
|
error();
|
|
6
|
-
} else {
|
|
7
|
+
} else {
|
|
8
|
+
// error
|
|
7
9
|
throw new Error(error);
|
|
8
10
|
}
|
|
9
11
|
};
|
|
10
12
|
|
|
11
13
|
const assert = (predicate, error) => Boolean(predicate) || errored(error);
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const is = {
|
|
15
|
+
"array": value => assert(Array.isArray(value), "must be array"),
|
|
16
|
+
"string": value => assert(typeof value === "string", "must be string"),
|
|
17
|
+
"defined": (value, error) => assert (value !== undefined, error),
|
|
18
|
+
"undefined": value => assert(value === undefined, "must be undefined"),
|
|
19
|
+
"constructible": (value, error) => assert(constructible(value), error),
|
|
20
|
+
"instance": (object, Class) => assert(object instanceof Class,
|
|
21
|
+
`must instance ${Class.name}`),
|
|
22
|
+
"subclass": (object, Class) => assert(object?.prototype instanceof Class,
|
|
23
|
+
`must subclass ${Class.name}`),
|
|
24
|
+
};
|
|
25
|
+
const {defined} = is;
|
|
26
|
+
|
|
27
|
+
const maybe = Object.keys(is).reduce((aggregator, property) => {
|
|
28
|
+
aggregator[property] = value => nullish(value) || is[property](value);
|
|
29
|
+
return aggregator;
|
|
30
|
+
}, {});
|
|
16
31
|
|
|
17
|
-
export {assert, defined,
|
|
32
|
+
export {assert, defined, is, maybe};
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export default (payload = {}) => Object.keys(payload)
|
|
2
2
|
.map(key => ({key, "value": payload[key].toString().trim()}))
|
|
3
|
-
.map(datum => {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
.map(datum => {
|
|
4
|
+
datum.value = datum.value === "" ? undefined : datum.value;
|
|
5
|
+
return datum;
|
|
6
|
+
})
|
|
7
|
+
.reduce((data, {key, value}) => {
|
|
8
|
+
data[key] = value;
|
|
9
|
+
return data;
|
|
10
|
+
}, {});
|
|
@@ -23,24 +23,24 @@ export default class DynamicServer extends Server {
|
|
|
23
23
|
const {session} = socket;
|
|
24
24
|
const request = JSON.parse(event);
|
|
25
25
|
try {
|
|
26
|
-
await this.
|
|
26
|
+
await this.serve(request, socket);
|
|
27
27
|
await session.log("green", `${request.type} ${request.url}`);
|
|
28
28
|
} catch(error) {
|
|
29
29
|
await session.log("red", error.message);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
async
|
|
33
|
+
async serve(request, socket) {
|
|
34
34
|
const {router} = this.conf;
|
|
35
35
|
const {session} = socket;
|
|
36
36
|
const {path, params} = await session.route(router, request.url);
|
|
37
37
|
const response = await session.run({...request, path, params});
|
|
38
38
|
const {type} = response;
|
|
39
39
|
return this[type]?.(socket, response)
|
|
40
|
-
?? DynamicServer.
|
|
40
|
+
?? DynamicServer.serve(socket, response);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
static
|
|
43
|
+
static serve(socket, response) {
|
|
44
44
|
socket.send(JSON.stringify(response));
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -84,7 +84,7 @@ export default class StaticServer extends Server {
|
|
|
84
84
|
const integrity = `${algorithm}-${hash(src)}`;
|
|
85
85
|
const view = `<script type="module" integrity="${integrity}">${src}`
|
|
86
86
|
+ "</script>";
|
|
87
|
-
const file = index.replace("<first-view />", view);
|
|
87
|
+
const file = index.replace("<first-view />", () => view);
|
|
88
88
|
const script_src = Array.from(hashes)
|
|
89
89
|
.concat([integrity])
|
|
90
90
|
.reduce((hash_string, next_hash) => hash_string + ` '${next_hash}'`, "");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import InstanceType from "./Instance.js";
|
|
2
2
|
import errors from "./errors/Array.json" assert {"type": "json"};
|
|
3
3
|
|
|
4
|
-
export default class extends
|
|
4
|
+
export default class ArrayType extends InstanceType {
|
|
5
5
|
static get instance() {
|
|
6
6
|
return Array;
|
|
7
7
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import PrimitiveType from "./Primitive.js";
|
|
2
2
|
import {boolish} from "../attributes.js";
|
|
3
3
|
import errors from "./errors/Boolean.json" assert {"type": "json"};
|
|
4
4
|
|
|
5
|
-
export default class extends
|
|
5
|
+
export default class BooleanType extends PrimitiveType {
|
|
6
6
|
static get type() {
|
|
7
7
|
return "boolean";
|
|
8
8
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import InstanceType from "./Instance.js";
|
|
2
2
|
import errors from "./errors/Date.json" assert {"type": "json"};
|
|
3
3
|
|
|
4
|
-
export default class extends
|
|
4
|
+
export default class DateType extends InstanceType {
|
|
5
5
|
static get instance() {
|
|
6
6
|
return Date;
|
|
7
7
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Storeable from "./Storeable.js";
|
|
2
2
|
|
|
3
|
-
export default class extends Storeable {
|
|
3
|
+
export default class InstanceType extends Storeable {
|
|
4
4
|
static is(value) {
|
|
5
5
|
// no subclassing allowed, as [] instanceof Object === true et al.
|
|
6
6
|
return value?.constructor === this.instance;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import PrimitiveType from "./Primitive.js";
|
|
2
2
|
import {numeric} from "../attributes.js";
|
|
3
3
|
import errors from "./errors/Number.json" assert {"type": "json"};
|
|
4
4
|
|
|
5
|
-
export default class extends
|
|
5
|
+
export default class NumberType extends PrimitiveType {
|
|
6
6
|
static get type() {
|
|
7
7
|
return "number";
|
|
8
8
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import InstanceType from "./Instance.js";
|
|
2
2
|
import errors from "./errors/Object.json" assert {"type": "json"};
|
|
3
3
|
|
|
4
|
-
export default class extends
|
|
4
|
+
export default class ObjectType extends InstanceType {
|
|
5
5
|
static get instance() {
|
|
6
6
|
return Object;
|
|
7
7
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {PredicateError} from "../errors.js";
|
|
2
2
|
|
|
3
|
-
export default class {
|
|
3
|
+
export default class Storeable {
|
|
4
4
|
static async verify(property, document, predicates, type) {
|
|
5
5
|
document[property] = this.coerce(document[property]);
|
|
6
6
|
if (!await this.is(document[property], type)) {
|
|
7
7
|
throw new PredicateError(this.type_error(type));
|
|
8
8
|
}
|
|
9
|
-
await
|
|
9
|
+
await Promise.all(predicates.map(predicate =>
|
|
10
|
+
predicate.check(property, document, this)));
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
static type_error() {
|
|
@@ -17,20 +18,13 @@ export default class {
|
|
|
17
18
|
throw new Error("must be implemented");
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
static async has(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const [name, ...params] = predicate.split(":");
|
|
26
|
-
if (!this[name](document[property], ...params)) {
|
|
27
|
-
let error = this.errors[name];
|
|
28
|
-
for (let i = 0; i < params.length; i++) {
|
|
29
|
-
error = error.replace(`$${i+1}`, params[i]);
|
|
30
|
-
}
|
|
31
|
-
throw new PredicateError(error);
|
|
32
|
-
}
|
|
21
|
+
static async has(name, value, params) {
|
|
22
|
+
if (!this[name](value, ...params)) {
|
|
23
|
+
let error = this.errors[name];
|
|
24
|
+
for (let i = 0; i < params.length; i++) {
|
|
25
|
+
error = error.replace(`$${i+1}`, () => params[i]);
|
|
33
26
|
}
|
|
27
|
+
throw new PredicateError(error);
|
|
34
28
|
}
|
|
35
29
|
}
|
|
36
30
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import PrimitiveType from "./Primitive.js";
|
|
2
2
|
import errors from "./errors/String.json" assert {"type": "json"};
|
|
3
3
|
|
|
4
|
-
export default class extends
|
|
4
|
+
export default class StringType extends PrimitiveType {
|
|
5
5
|
static get type() {
|
|
6
6
|
return "string";
|
|
7
7
|
}
|