primate 0.1.0 → 0.3.1
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 +6 -3
- package/package.json +1 -1
- package/source/client/Action.js +4 -6
- package/source/client/App.js +1 -4
- package/source/client/Client.js +4 -10
- package/source/client/Element.js +4 -2
- package/source/client/document.js +6 -0
- package/source/server/Bundler.js +2 -2
- package/source/server/File.js +5 -1
- package/source/server/domain/Domain.js +14 -19
- package/source/server/servers/Static.js +2 -2
- package/source/server/servers/content-security-policy.json +0 -2
- package/source/server/types/Date.js +2 -2
- package/source/server/constructible.js +0 -8
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
|
|
|
@@ -46,3 +46,6 @@ will move over to semantic versioning.
|
|
|
46
46
|
## License
|
|
47
47
|
|
|
48
48
|
BSD-3-Clause
|
|
49
|
+
|
|
50
|
+
[ws]: https://github.com/websockets/ws
|
|
51
|
+
[getting-started]: https://primatejs.com/getting-started
|
package/package.json
CHANGED
package/source/client/Action.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import Element from "../Element.js";
|
|
2
2
|
import View from "./View.js";
|
|
3
3
|
import Base from "./Base.js";
|
|
4
|
-
|
|
5
|
-
const base = document.baseURI.replace(document.location.origin, "");
|
|
6
|
-
const full = `${document.location.origin}${base}`;
|
|
4
|
+
import {origin_base} from "./document.js";
|
|
7
5
|
|
|
8
6
|
export default class Action extends Base {
|
|
9
7
|
constructor(name, context) {
|
|
@@ -75,7 +73,7 @@ export default class Action extends Base {
|
|
|
75
73
|
}
|
|
76
74
|
event.preventDefault();
|
|
77
75
|
const data = await this.write(target.action, this.get_form_data(target));
|
|
78
|
-
return data.pathname === document.location.href.replace(
|
|
76
|
+
return data.pathname === document.location.href.replace(origin_base, "")
|
|
79
77
|
? this.view.update(data.payload)
|
|
80
78
|
: this.context.run(data);
|
|
81
79
|
}
|
|
@@ -94,8 +92,8 @@ export default class Action extends Base {
|
|
|
94
92
|
}
|
|
95
93
|
if (target !== null) {
|
|
96
94
|
const href = Element.value(target, "href");
|
|
97
|
-
const url = new URL(href,
|
|
98
|
-
if (event.button === 0 && url.href.startsWith(
|
|
95
|
+
const url = new URL(href, origin_base);
|
|
96
|
+
if (event.button === 0 && url.href.startsWith(origin_base)) {
|
|
99
97
|
event.preventDefault();
|
|
100
98
|
if (href !== "") {
|
|
101
99
|
const data = await this.read(href);
|
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,8 +1,10 @@
|
|
|
1
1
|
import Context from "./Context.js";
|
|
2
2
|
import {contexts} from "./exports.js";
|
|
3
|
+
import {base, host, origin_base} from "./document.js";
|
|
3
4
|
|
|
4
5
|
const events = ["submit", "click", "change", "input"];
|
|
5
6
|
let back = undefined;
|
|
7
|
+
const location = `wss://${host}${base}`;
|
|
6
8
|
|
|
7
9
|
export default class Client {
|
|
8
10
|
constructor(conf) {
|
|
@@ -18,14 +20,10 @@ export default class Client {
|
|
|
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
|
-
const connection = new WebSocket(
|
|
26
|
+
const connection = new WebSocket(location);
|
|
29
27
|
connection.addEventListener("message", ({data}) => data === "open"
|
|
30
28
|
? resolve(this)
|
|
31
29
|
: this.receive(JSON.parse(data)));
|
|
@@ -56,13 +54,9 @@ export default class Client {
|
|
|
56
54
|
return this.send("write", url, payload);
|
|
57
55
|
}
|
|
58
56
|
|
|
59
|
-
get full() {
|
|
60
|
-
return `${document.location.origin}${this.conf.base}`;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
57
|
async send(type, url, payload = {}) {
|
|
64
58
|
await this.open();
|
|
65
|
-
const {pathname, search} = new URL(url,
|
|
59
|
+
const {pathname, search} = new URL(url, origin_base);
|
|
66
60
|
return new Promise(resolve => {
|
|
67
61
|
back = data => resolve(data);
|
|
68
62
|
this.connection.send(JSON.stringify({type, pathname, search, payload}));
|
package/source/client/Element.js
CHANGED
|
@@ -143,8 +143,10 @@ export default class Element {
|
|
|
143
143
|
return this;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
class(
|
|
147
|
-
|
|
146
|
+
class(classes, set) {
|
|
147
|
+
classes.split(" ").forEach(name => {
|
|
148
|
+
this.element.classList.toggle(name, set !== false);
|
|
149
|
+
})
|
|
148
150
|
return this;
|
|
149
151
|
}
|
|
150
152
|
|
package/source/server/Bundler.js
CHANGED
|
@@ -125,7 +125,7 @@ export default class Bundler {
|
|
|
125
125
|
|
|
126
126
|
register(src, source) {
|
|
127
127
|
const integrity = `${algorithm}-${hash(source)}`;
|
|
128
|
-
this.scripts.push({src
|
|
128
|
+
this.scripts.push({"src": `${this.conf.base}${src}`, integrity});
|
|
129
129
|
this.hashes.add(integrity);
|
|
130
130
|
}
|
|
131
131
|
|
|
@@ -139,7 +139,7 @@ export default class Bundler {
|
|
|
139
139
|
client += `<script type="module" integrity="${integrity}" src="${src}">`
|
|
140
140
|
+ "</script>\n";
|
|
141
141
|
}
|
|
142
|
-
return client + "
|
|
142
|
+
return client + "<first-view />";
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
async write_exports() {
|
package/source/server/File.js
CHANGED
|
@@ -31,10 +31,14 @@ export default class File {
|
|
|
31
31
|
return this.exists && !this.stats.isDirectory();
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
get
|
|
34
|
+
get read_stream() {
|
|
35
35
|
return fs.createReadStream(this.path, {"flags": "r"});
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
get write_stream() {
|
|
39
|
+
return fs.createWriteStream(this.path);
|
|
40
|
+
}
|
|
41
|
+
|
|
38
42
|
remove() {
|
|
39
43
|
return new Promise((resolve, reject) => fs.rm(this.path,
|
|
40
44
|
{"recursive": true, "force": true},
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import Base from "../Base.js";
|
|
2
2
|
import Field from "./Field.js";
|
|
3
|
-
import
|
|
4
|
-
import {PredicateError, InternalServerError} from "../errors.js";
|
|
3
|
+
import {PredicateError} from "../errors.js";
|
|
5
4
|
import {EagerPromise} from "../promises.js";
|
|
6
|
-
import {assert} from "../invariants.js";
|
|
7
5
|
import cache from "../cache.js";
|
|
8
6
|
import {random} from "../Crypto.js";
|
|
9
|
-
import fallback from "../fallback.js";
|
|
10
7
|
|
|
11
8
|
const length = 12;
|
|
12
9
|
const preset = "../../preset/data/stores";
|
|
@@ -43,16 +40,14 @@ export default class Domain extends Base {
|
|
|
43
40
|
|
|
44
41
|
static get store() {
|
|
45
42
|
return cache(this, "store", async () => {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return instance.open();
|
|
43
|
+
const create_path = path => `${path}/${this.store_file}`;
|
|
44
|
+
let store;
|
|
45
|
+
try {
|
|
46
|
+
store = await import(create_path(this.conf.paths.data.stores));
|
|
47
|
+
} catch(error) {
|
|
48
|
+
store = await import(create_path(preset));
|
|
49
|
+
}
|
|
50
|
+
return store.default.open();
|
|
56
51
|
});
|
|
57
52
|
}
|
|
58
53
|
|
|
@@ -110,10 +105,10 @@ export default class Domain extends Base {
|
|
|
110
105
|
|
|
111
106
|
// #serialize
|
|
112
107
|
// Serializing is done from the instance's point of view.
|
|
113
|
-
serialize() {
|
|
108
|
+
async serialize() {
|
|
114
109
|
const {properties, fields} = this;
|
|
115
|
-
return properties.map(property =>
|
|
116
|
-
({property, "value": fields[property].serialize(this[property])}))
|
|
110
|
+
return (await Promise.all(properties.map(async property =>
|
|
111
|
+
({property, "value": await fields[property].serialize(this[property])}))))
|
|
117
112
|
.filter(({value}) => value !== undefined)
|
|
118
113
|
.reduce((document, {property, value}) => {
|
|
119
114
|
document[property] = value;
|
|
@@ -126,7 +121,7 @@ export default class Domain extends Base {
|
|
|
126
121
|
static deserialize(serialized) {
|
|
127
122
|
const fields = this._fields;
|
|
128
123
|
return new this(Object.keys(serialized)
|
|
129
|
-
|
|
124
|
+
.filter(property => fields[property] !== undefined)
|
|
130
125
|
.map(property =>
|
|
131
126
|
({property,
|
|
132
127
|
"value": fields[property].deserialize(serialized[property])}))
|
|
@@ -177,7 +172,7 @@ export default class Domain extends Base {
|
|
|
177
172
|
const verified = await this.verify(delta);
|
|
178
173
|
if (verified) {
|
|
179
174
|
const store = await this.store;
|
|
180
|
-
const document = this.serialize();
|
|
175
|
+
const document = await this.serialize();
|
|
181
176
|
await store.save(this.collection, {"_id": document._id}, document);
|
|
182
177
|
await after();
|
|
183
178
|
}
|
|
@@ -58,7 +58,7 @@ export default class StaticServer extends Server {
|
|
|
58
58
|
response.setHeader("Content-Type", mime(filename));
|
|
59
59
|
response.setHeader("Etag", file.modified);
|
|
60
60
|
await session.log("green", url);
|
|
61
|
-
return stream(file.
|
|
61
|
+
return stream(file.read_stream, response);
|
|
62
62
|
} else {
|
|
63
63
|
return this.serve_data(url, session, request, response);
|
|
64
64
|
}
|
|
@@ -79,7 +79,7 @@ export default class StaticServer extends Server {
|
|
|
79
79
|
const integrity = `${algorithm}-${hash(src)}`;
|
|
80
80
|
const view = `<script type="module" integrity="${integrity}">${src}`
|
|
81
81
|
+ "</script>";
|
|
82
|
-
const file =
|
|
82
|
+
const file = index.replace("<first-view />", view);
|
|
83
83
|
const script_src = Array.from(hashes)
|
|
84
84
|
.concat([integrity])
|
|
85
85
|
.reduce((hash_string, next_hash) => hash_string + ` '${next_hash}'`, "");
|