primate 0.1.1 → 0.4.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 +4 -3
- package/package.json +9 -3
- package/source/client/Action.js +14 -19
- package/source/client/App.js +1 -4
- package/source/client/Client.js +14 -24
- package/source/client/Context.js +9 -15
- package/source/client/Element.js +12 -5
- package/source/client/Session.js +27 -0
- package/source/client/document.js +6 -0
- package/source/preset/primate.json +2 -6
- package/source/server/Action.js +53 -75
- package/source/server/App.js +26 -7
- package/source/server/Bundler.js +13 -16
- package/source/server/Context.js +64 -56
- package/source/server/{promises/Eager.js → EagerPromise.js} +3 -1
- package/source/server/File.js +6 -2
- package/source/server/Projector.js +86 -0
- package/source/server/Router.js +7 -9
- package/source/server/Session.js +9 -33
- package/source/server/attributes.js +2 -0
- package/source/server/conf.js +20 -26
- package/source/server/{Crypto.js → crypto.js} +0 -0
- package/source/server/domain/Domain.js +61 -56
- package/source/server/domain/Field.js +16 -13
- package/source/server/domain/domains.js +16 -0
- package/source/server/errors.js +0 -1
- package/source/server/exports.js +9 -8
- package/source/server/{utils/extend_object.js → extend_object.js} +0 -0
- package/source/server/invariants.js +0 -2
- package/source/server/sanitize.js +5 -0
- package/source/server/servers/Dynamic.js +19 -13
- package/source/server/servers/Static.js +25 -20
- package/source/server/servers/content-security-policy.json +0 -2
- package/source/server/store/Store.js +13 -0
- package/source/server/types/Date.js +2 -2
- package/source/server/types/Domain.js +0 -5
- package/source/server/types/Storeable.js +6 -7
- package/source/server/view/TreeNode.js +2 -0
- package/source/server/view/View.js +5 -0
- package/source/client/Base.js +0 -5
- package/source/server/Base.js +0 -35
- package/source/server/constructible.js +0 -8
- package/source/server/errors/Fallback.js +0 -1
- package/source/server/fallback.js +0 -11
- package/source/server/promises/Meta.js +0 -42
- package/source/server/promises.js +0 -2
package/README.md
CHANGED
|
@@ -18,8 +18,8 @@ yarn add primate
|
|
|
18
18
|
* **Minimal** just one dependency ([ws][])
|
|
19
19
|
* **Simple** only native web technologies (JavaScript, HTML)
|
|
20
20
|
* **Full-stack** server and client, both in JavaScript
|
|
21
|
-
* **Layered** separation of data (domains), logic (actions) and
|
|
22
|
-
|
|
21
|
+
* **Layered** separation of data (domains), logic (actions) and presentation
|
|
22
|
+
(views)
|
|
23
23
|
* **Correct** data verification using field definitions in domains
|
|
24
24
|
* **Secure** serving hash-verified scripts on secure HTTP with a strong CSP
|
|
25
25
|
* **Roles** access control with contexts
|
|
@@ -31,7 +31,7 @@ ad-hoc getters
|
|
|
31
31
|
|
|
32
32
|
## Getting started
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
See the [getting started][getting-started] guide.
|
|
35
35
|
|
|
36
36
|
## Resources
|
|
37
37
|
|
|
@@ -48,3 +48,4 @@ will move over to semantic versioning.
|
|
|
48
48
|
BSD-3-Clause
|
|
49
49
|
|
|
50
50
|
[ws]: https://github.com/websockets/ws
|
|
51
|
+
[getting-started]: https://primatejs.com/getting-started
|
package/package.json
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"author": "Primate core team <core@primatejs.com>",
|
|
5
5
|
"homepage": "https://primatejs.com",
|
|
6
|
+
"bugs": "https://adaptivecloud.dev/primate/primate/issues",
|
|
7
|
+
"repository": "https://adaptivecloud.dev/primate/primate",
|
|
6
8
|
"description": "Server-client framework",
|
|
7
9
|
"license": "BSD-3-Clause",
|
|
8
10
|
"dependencies": {
|
|
9
|
-
"ws": "^8.4.
|
|
11
|
+
"ws": "^8.4.2"
|
|
10
12
|
},
|
|
11
13
|
"scripts": {
|
|
12
|
-
"
|
|
14
|
+
"copy": "rm -rf output && mkdir output && cp source/* output -a",
|
|
15
|
+
"instrument": "nyc instrument source ./output",
|
|
16
|
+
"stick": "node --experimental-json-modules node_modules/stick stick.json",
|
|
17
|
+
"coverage": "yarn instrument && nyc yarn run stick",
|
|
18
|
+
"test": "yarn copy && yarn run stick"
|
|
13
19
|
},
|
|
14
20
|
"type": "module",
|
|
15
21
|
"exports": "./source/server/exports.js",
|
package/source/client/Action.js
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import Element from "../Element.js";
|
|
2
2
|
import View from "./View.js";
|
|
3
|
-
import
|
|
3
|
+
import {origin_base, origin} from "./document.js";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export default class Action extends Base {
|
|
9
|
-
constructor(name, context) {
|
|
10
|
-
super();
|
|
5
|
+
export default class Action {
|
|
6
|
+
constructor(name, session) {
|
|
11
7
|
this.name = name;
|
|
12
|
-
this.
|
|
8
|
+
this.session = session;
|
|
13
9
|
this.listeners = [];
|
|
14
10
|
}
|
|
15
11
|
|
|
@@ -25,9 +21,9 @@ export default class Action extends Base {
|
|
|
25
21
|
|
|
26
22
|
create_view(data, root, save_history = true) {
|
|
27
23
|
if (save_history) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
if (data.url !== document.location.href.replace(origin, "")) {
|
|
25
|
+
history.pushState({}, "", data.url);
|
|
26
|
+
}
|
|
31
27
|
}
|
|
32
28
|
this.view = new View(data, this, root);
|
|
33
29
|
}
|
|
@@ -75,9 +71,9 @@ export default class Action extends Base {
|
|
|
75
71
|
}
|
|
76
72
|
event.preventDefault();
|
|
77
73
|
const data = await this.write(target.action, this.get_form_data(target));
|
|
78
|
-
return data.
|
|
74
|
+
return data.type === "update"
|
|
79
75
|
? this.view.update(data.payload)
|
|
80
|
-
: this.
|
|
76
|
+
: this.session.run(data);
|
|
81
77
|
}
|
|
82
78
|
|
|
83
79
|
async onclick(event) {
|
|
@@ -94,12 +90,11 @@ export default class Action extends Base {
|
|
|
94
90
|
}
|
|
95
91
|
if (target !== null) {
|
|
96
92
|
const href = Element.value(target, "href");
|
|
97
|
-
const url = new URL(href,
|
|
98
|
-
if (event.button === 0 && url.href.startsWith(
|
|
93
|
+
const url = new URL(href, origin_base);
|
|
94
|
+
if (event.button === 0 && url.href.startsWith(origin_base)) {
|
|
99
95
|
event.preventDefault();
|
|
100
96
|
if (href !== "") {
|
|
101
|
-
|
|
102
|
-
await this.context.run(data);
|
|
97
|
+
await this.session.run(await this.read(href));
|
|
103
98
|
}
|
|
104
99
|
}
|
|
105
100
|
}
|
|
@@ -152,10 +147,10 @@ export default class Action extends Base {
|
|
|
152
147
|
}
|
|
153
148
|
|
|
154
149
|
read(pathname) {
|
|
155
|
-
return this.
|
|
150
|
+
return this.session.read(pathname);
|
|
156
151
|
}
|
|
157
152
|
|
|
158
153
|
write(pathname, payload) {
|
|
159
|
-
return this.
|
|
154
|
+
return this.session.write(pathname, payload);
|
|
160
155
|
}
|
|
161
156
|
}
|
package/source/client/App.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import Client from "./Client.js";
|
|
2
2
|
|
|
3
|
-
const host = document.location.host;
|
|
4
|
-
const base = document.baseURI.replace(document.location.origin, "");
|
|
5
|
-
|
|
6
3
|
export default class App {
|
|
7
4
|
constructor() {
|
|
8
5
|
this.definitions = {};
|
|
9
6
|
}
|
|
10
7
|
|
|
11
8
|
run() {
|
|
12
|
-
this.client = new Client(
|
|
9
|
+
this.client = new Client();
|
|
13
10
|
this.client.open();
|
|
14
11
|
}
|
|
15
12
|
|
package/source/client/Client.js
CHANGED
|
@@ -1,35 +1,32 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import Session from "./Session.js";
|
|
2
|
+
import {base, host, origin_base} from "./document.js";
|
|
3
3
|
|
|
4
4
|
const events = ["submit", "click", "change", "input"];
|
|
5
5
|
let back = undefined;
|
|
6
|
+
const location = `wss://${host}${base}`;
|
|
6
7
|
|
|
7
8
|
export default class Client {
|
|
8
9
|
constructor(conf) {
|
|
9
10
|
this.conf = conf;
|
|
11
|
+
this.session = new Session(this);
|
|
10
12
|
|
|
11
13
|
window.onpopstate = async event => {
|
|
12
14
|
const {pathname, search} = event.target.location;
|
|
13
|
-
return this.
|
|
15
|
+
return this.session.run(await this.read(pathname + search));
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
for (const key of events) {
|
|
17
|
-
window.addEventListener(key, event => this.
|
|
19
|
+
window.addEventListener(key, event => this.session.on(key, event));
|
|
18
20
|
}
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
get location() {
|
|
22
|
-
return `wss://${this.conf.host}${this.conf.base}`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
23
|
open() {
|
|
26
24
|
return this.connection?.readyState === 1 ? this
|
|
27
25
|
: new Promise(resolve => {
|
|
28
|
-
|
|
29
|
-
connection.addEventListener("message", ({data}) => data === "open"
|
|
26
|
+
this.connection = new WebSocket(location);
|
|
27
|
+
this.connection.addEventListener("message", ({data}) => data === "open"
|
|
30
28
|
? resolve(this)
|
|
31
29
|
: this.receive(JSON.parse(data)));
|
|
32
|
-
this.connection = connection;
|
|
33
30
|
});
|
|
34
31
|
}
|
|
35
32
|
|
|
@@ -38,16 +35,10 @@ export default class Client {
|
|
|
38
35
|
back(data);
|
|
39
36
|
back = undefined;
|
|
40
37
|
} else {
|
|
41
|
-
await this.execute(data);
|
|
38
|
+
await this.session.execute(data);
|
|
42
39
|
}
|
|
43
40
|
}
|
|
44
41
|
|
|
45
|
-
async execute(data) {
|
|
46
|
-
const {context} = data;
|
|
47
|
-
this.context = new (contexts[context] ?? Context)(context, this);
|
|
48
|
-
await this.context.run(data);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
42
|
read(url) {
|
|
52
43
|
return this.send("read", url);
|
|
53
44
|
}
|
|
@@ -56,16 +47,15 @@ export default class Client {
|
|
|
56
47
|
return this.send("write", url, payload);
|
|
57
48
|
}
|
|
58
49
|
|
|
59
|
-
get full() {
|
|
60
|
-
return `${document.location.origin}${this.conf.base}`;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
50
|
async send(type, url, payload = {}) {
|
|
64
51
|
await this.open();
|
|
65
|
-
const {pathname, search} = new URL(url,
|
|
52
|
+
const {pathname, search} = new URL(url, origin_base);
|
|
53
|
+
const _url = pathname + search;
|
|
66
54
|
return new Promise(resolve => {
|
|
67
55
|
back = data => resolve(data);
|
|
68
|
-
this.connection.send(JSON.stringify({
|
|
56
|
+
this.connection.send(JSON.stringify({
|
|
57
|
+
type, pathname, search, payload, "url": _url,
|
|
58
|
+
}));
|
|
69
59
|
});
|
|
70
60
|
}
|
|
71
61
|
}
|
package/source/client/Context.js
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
import Base from "./Base.js";
|
|
2
1
|
import Action from "./Action.js";
|
|
3
2
|
import {routes} from "./exports.js";
|
|
4
3
|
|
|
5
|
-
export default class Context
|
|
6
|
-
constructor(name,
|
|
7
|
-
super();
|
|
4
|
+
export default class Context {
|
|
5
|
+
constructor(name, session) {
|
|
8
6
|
this.name = name;
|
|
9
7
|
this.actions = {};
|
|
10
8
|
this.used_actions = [];
|
|
11
|
-
this.
|
|
9
|
+
this.session = session;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get Class() {
|
|
13
|
+
return this.constructor;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
async run(data) {
|
|
15
17
|
const {namespace, action} = data;
|
|
16
18
|
const route = `${namespace}/${action}`;
|
|
17
19
|
const module = routes?.[namespace]?.[action] ?? Action;
|
|
18
|
-
this.actions[route] = new module(action, this);
|
|
20
|
+
this.actions[route] = new module(action, this.session);
|
|
19
21
|
const nextaction = this.actions[route];
|
|
20
22
|
nextaction.enter(data);
|
|
21
|
-
await this.Class
|
|
23
|
+
await this.Class.before(nextaction);
|
|
22
24
|
nextaction.before();
|
|
23
25
|
this.action = nextaction;
|
|
24
26
|
}
|
|
@@ -31,14 +33,6 @@ export default class Context extends Base {
|
|
|
31
33
|
return data;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
read(pathname) {
|
|
35
|
-
return this.client.read(pathname);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
write(pathname, payload) {
|
|
39
|
-
return this.client.write(pathname, payload);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
36
|
on(key, event) {
|
|
43
37
|
this.action[`on${key}`](event);
|
|
44
38
|
}
|
package/source/client/Element.js
CHANGED
|
@@ -77,8 +77,7 @@ export default class Element {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
static value(element, name) {
|
|
80
|
-
|
|
81
|
-
return attribute === null ? null : attribute.value;
|
|
80
|
+
return element?.attributes.getNamedItem(name)?.value;
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
static evaluate(attribute, source, data) {
|
|
@@ -143,8 +142,10 @@ export default class Element {
|
|
|
143
142
|
return this;
|
|
144
143
|
}
|
|
145
144
|
|
|
146
|
-
class(
|
|
147
|
-
|
|
145
|
+
class(classes, set) {
|
|
146
|
+
classes.split(" ").forEach(name => {
|
|
147
|
+
this.element.classList.toggle(name, set !== false);
|
|
148
|
+
})
|
|
148
149
|
return this;
|
|
149
150
|
}
|
|
150
151
|
|
|
@@ -181,7 +182,8 @@ export default class Element {
|
|
|
181
182
|
|
|
182
183
|
options(value) {
|
|
183
184
|
value instanceof Array && value.forEach(option => {
|
|
184
|
-
|
|
185
|
+
const children = [...this.element.children];
|
|
186
|
+
if (!children.find(child => child.value === option._id)) {
|
|
185
187
|
const newElement = document.createElement("option");
|
|
186
188
|
newElement.setAttribute("value", option._id);
|
|
187
189
|
newElement.innerHTML = option.name;
|
|
@@ -201,11 +203,13 @@ export default class Element {
|
|
|
201
203
|
|
|
202
204
|
select_value(value) {
|
|
203
205
|
this.element.value = value === undefined ? "" : value;
|
|
206
|
+
return this;
|
|
204
207
|
}
|
|
205
208
|
|
|
206
209
|
input_value(value) {
|
|
207
210
|
const type = Element.value(this.element, "type");
|
|
208
211
|
this[`input_${type}_value`]?.(value) ?? this.input_input_value(value);
|
|
212
|
+
return this;
|
|
209
213
|
}
|
|
210
214
|
|
|
211
215
|
textarea_value(value) {
|
|
@@ -216,6 +220,7 @@ export default class Element {
|
|
|
216
220
|
if (value === true) {
|
|
217
221
|
this.element.checked = true;
|
|
218
222
|
}
|
|
223
|
+
return this;
|
|
219
224
|
}
|
|
220
225
|
|
|
221
226
|
input_file_value(value) {
|
|
@@ -225,12 +230,14 @@ export default class Element {
|
|
|
225
230
|
}
|
|
226
231
|
reader.onload = ({target}) => element.setAttribute("base64", target.result);
|
|
227
232
|
element.addEventListener("change", file_listener);
|
|
233
|
+
return this;
|
|
228
234
|
}
|
|
229
235
|
|
|
230
236
|
input_input_value(value) {
|
|
231
237
|
if (value !== undefined) {
|
|
232
238
|
this.element.value = value;
|
|
233
239
|
}
|
|
240
|
+
return this;
|
|
234
241
|
}
|
|
235
242
|
|
|
236
243
|
default_value(value) {
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
}
|
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
"base": "/",
|
|
3
3
|
"debug": false,
|
|
4
4
|
"defaults": {
|
|
5
|
-
"
|
|
6
|
-
"context": "guest",
|
|
7
|
-
"namespace": "default",
|
|
8
|
-
"store": "default.js"
|
|
5
|
+
"context": "guest"
|
|
9
6
|
},
|
|
10
7
|
"files": {
|
|
11
8
|
"index": "index.html"
|
|
@@ -21,8 +18,7 @@
|
|
|
21
18
|
"paths": {
|
|
22
19
|
"client": "client",
|
|
23
20
|
"data": {
|
|
24
|
-
"domains": "domains"
|
|
25
|
-
"stores": "stores"
|
|
21
|
+
"domains": "domains"
|
|
26
22
|
},
|
|
27
23
|
"public": "public",
|
|
28
24
|
"server": "server",
|
package/source/server/Action.js
CHANGED
|
@@ -1,118 +1,96 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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} 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;
|
|
10
16
|
this.session = session;
|
|
17
|
+
this.context = EagerPromise.resolve(context);
|
|
11
18
|
}
|
|
12
19
|
|
|
13
|
-
static
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
static async load(data, session, context) {
|
|
18
|
-
let module;
|
|
19
|
-
const server = this.conf.paths.server;
|
|
20
|
-
const {namespace, action} = data.path;
|
|
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"); });
|
|
21
24
|
const route = `${namespace}/${action}`;
|
|
25
|
+
const path = resolve(`${context.directory}/${route}.js`);
|
|
22
26
|
try {
|
|
23
|
-
|
|
27
|
+
return new (await import(path)).default(request, session, context);
|
|
24
28
|
} catch (error) {
|
|
25
|
-
throw new Error(`route
|
|
29
|
+
throw new Error(`route \`${route}\` missing`);
|
|
26
30
|
}
|
|
27
|
-
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get Class() {
|
|
34
|
+
return this.constructor;
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
get layout() {
|
|
31
|
-
return this.Class
|
|
38
|
+
return this.Class.layout;
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
get path() {
|
|
35
|
-
|
|
42
|
+
const {path} = this.request;
|
|
43
|
+
const namespace = path.namespace ?? this.Class.namespace;
|
|
44
|
+
const action = path.action ?? this.Class.action;
|
|
45
|
+
const _id = path._id;
|
|
46
|
+
return {action, namespace, _id};
|
|
36
47
|
}
|
|
37
48
|
|
|
38
49
|
get params() {
|
|
39
|
-
return this.
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async _view(path) {
|
|
43
|
-
const {namespace, action} = this.path;
|
|
44
|
-
const view_path = path !== undefined ? path : `${namespace}/${action}`;
|
|
45
|
-
return (await this.context).view(view_path);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
static sanitize(payload = {}) {
|
|
49
|
-
//assert(typeof payload === "object");
|
|
50
|
-
return Object.keys(payload)
|
|
51
|
-
.map(key => ({key, "value": payload[key].toString().trim()}))
|
|
52
|
-
.map(({key, value}) => ({key, "value":
|
|
53
|
-
value === "" ? undefined : value}))
|
|
54
|
-
.reduce((o, {key, value}) => {o[key] = value; return o;}, {});
|
|
50
|
+
return this.request.params;
|
|
55
51
|
}
|
|
56
52
|
|
|
57
53
|
async run(type) {
|
|
58
54
|
this.before && await this.before();
|
|
59
|
-
await this[type](
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
get response() {
|
|
63
|
-
return this[_response]();
|
|
55
|
+
await this[type](sanitize(this.request.payload));
|
|
64
56
|
}
|
|
65
57
|
|
|
66
|
-
return(
|
|
67
|
-
this.respond("return", () => ({
|
|
58
|
+
return(payload = {}) {
|
|
59
|
+
this.respond("return", () => ({payload}));
|
|
68
60
|
}
|
|
69
61
|
|
|
70
62
|
view(data = {}, layout = true) {
|
|
71
63
|
this.respond("view", async () => {
|
|
72
|
-
const _view = await this.
|
|
73
|
-
const payload = await this.prepare(data, _view);
|
|
64
|
+
const {_view, payload} = await this.prepare(data);
|
|
74
65
|
const view = layout ? _view.file(this.layout) : _view.content;
|
|
75
66
|
return {view, payload};
|
|
76
67
|
});
|
|
77
68
|
}
|
|
78
69
|
|
|
79
70
|
update(data) {
|
|
80
|
-
this.respond("update", async () =>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return {payload};
|
|
84
|
-
});
|
|
71
|
+
this.respond("update", async () =>
|
|
72
|
+
({"payload": (await this.prepare(data)).payload})
|
|
73
|
+
);
|
|
85
74
|
}
|
|
86
75
|
|
|
87
76
|
redirect(location) {
|
|
88
77
|
this.respond("redirect", () => ({location}));
|
|
89
78
|
}
|
|
90
79
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
async prepare(data = {}, view) {
|
|
100
|
-
(await this.context).Class().prepare(this, data);
|
|
101
|
-
return MetaPromise(data, await view.elements(this.layout));
|
|
80
|
+
async prepare(data = {}) {
|
|
81
|
+
const route = `${this.path.namespace}/${this.path.action}`;
|
|
82
|
+
const path = await eager`${this.context.directory}/${route}`;
|
|
83
|
+
const _view = await View.new(path, await this.context.layouts);
|
|
84
|
+
await this.context.Class.prepare(this, data);
|
|
85
|
+
const payload = await new Projector(data, await _view.elements(this.layout)).project(route);
|
|
86
|
+
return {payload, _view};
|
|
102
87
|
}
|
|
103
88
|
|
|
104
|
-
respond(type,
|
|
105
|
-
this
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
data.action = this.path.action;
|
|
110
|
-
if (data.pathname === undefined) {
|
|
111
|
-
data.pathname = this._data.pathname.replace(this.conf.base, "")
|
|
112
|
-
+ (this._data.search ?? "");
|
|
113
|
-
}
|
|
114
|
-
data.type = type;
|
|
115
|
-
return data;
|
|
89
|
+
respond(type, how) {
|
|
90
|
+
this.response = async () => {
|
|
91
|
+
const context = await this.context.name;
|
|
92
|
+
const {url} = this.request;
|
|
93
|
+
return {...await how(), type, context, ...this.path, url};
|
|
116
94
|
};
|
|
117
95
|
}
|
|
118
96
|
}
|
package/source/server/App.js
CHANGED
|
@@ -1,22 +1,35 @@
|
|
|
1
1
|
import {resolve} from "path";
|
|
2
|
-
import Base from "./Base.js";
|
|
3
2
|
import Bundler from "./Bundler.js";
|
|
4
3
|
import File from "./File.js";
|
|
5
4
|
import Router from "./Router.js";
|
|
6
5
|
import DynamicServer from "./servers/Dynamic.js";
|
|
7
6
|
import StaticServer from "./servers/Static.js";
|
|
7
|
+
import cache from "./cache.js";
|
|
8
8
|
import log from "./log.js";
|
|
9
|
+
import package_json from "../../package.json" assert {"type": "json" };
|
|
9
10
|
|
|
10
|
-
export default class App
|
|
11
|
-
constructor() {
|
|
12
|
-
|
|
13
|
-
this.router = new Router(this.routes, this.conf);
|
|
11
|
+
export default class App {
|
|
12
|
+
constructor(conf) {
|
|
13
|
+
this.conf = conf;
|
|
14
14
|
this.Bundler = Bundler;
|
|
15
15
|
}
|
|
16
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
|
+
|
|
17
29
|
async run() {
|
|
18
|
-
log.reset("Primate").yellow(
|
|
30
|
+
log.reset("Primate").yellow(package_json.version);
|
|
19
31
|
|
|
32
|
+
this.router = new Router(await this.routes, this.conf);
|
|
20
33
|
const {index, hashes} = await new this.Bundler(this.conf).bundle();
|
|
21
34
|
const router = this.router;
|
|
22
35
|
|
|
@@ -26,17 +39,23 @@ export default class App extends Base {
|
|
|
26
39
|
"key": File.read_sync(resolve(this.conf.http.ssl.key)),
|
|
27
40
|
"cert": File.read_sync(resolve(this.conf.http.ssl.cert)),
|
|
28
41
|
},
|
|
42
|
+
"context": this.conf.defaults.context,
|
|
29
43
|
};
|
|
30
44
|
this.static_server = new StaticServer(conf);
|
|
31
45
|
await this.static_server.run();
|
|
32
46
|
|
|
33
47
|
this.dynamic_server = new DynamicServer({router,
|
|
34
|
-
"path": this.base,
|
|
48
|
+
"path": this.conf.base,
|
|
35
49
|
"server": this.static_server.server,
|
|
50
|
+
"context": this.conf.defaults.context,
|
|
36
51
|
});
|
|
37
52
|
await this.dynamic_server.run();
|
|
38
53
|
|
|
39
54
|
const {port, host} = this.conf.http;
|
|
40
55
|
this.static_server.listen(port, host);
|
|
41
56
|
}
|
|
57
|
+
|
|
58
|
+
stop() {
|
|
59
|
+
this.static_server.close();
|
|
60
|
+
}
|
|
42
61
|
}
|