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
|
@@ -1,29 +1,34 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {resolve as path_resolve} from "path";
|
|
2
2
|
import Field from "./Field.js";
|
|
3
|
+
import {PredicateError} from "../errors.js";
|
|
4
|
+
import EagerPromise from "../EagerPromise.js";
|
|
3
5
|
import Store from "../store/Store.js";
|
|
4
|
-
import {PredicateError, InternalServerError} from "../errors.js";
|
|
5
|
-
import {EagerPromise} from "../promises.js";
|
|
6
|
-
import {assert} from "../invariants.js";
|
|
7
6
|
import cache from "../cache.js";
|
|
8
|
-
import
|
|
9
|
-
import
|
|
7
|
+
import DomainType from "../types/Domain.js";
|
|
8
|
+
import {random} from "../crypto.js";
|
|
10
9
|
|
|
11
10
|
const length = 12;
|
|
12
|
-
const preset = "../../preset/data/stores";
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
export default class Domain {
|
|
13
|
+
static stores_directory = "data/stores";
|
|
14
|
+
static store_file = "default.js";
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
static {
|
|
17
|
+
// avoid transitive cyclic dependency between Domain and Field
|
|
18
|
+
DomainType.instance = Domain;
|
|
19
|
+
this.cache = {};
|
|
20
|
+
}
|
|
20
21
|
|
|
22
|
+
constructor(document) {
|
|
21
23
|
const errors = {};
|
|
22
|
-
|
|
24
|
+
this.define("_id", {
|
|
23
25
|
"type": String,
|
|
24
26
|
"predicates": ["unique"],
|
|
25
27
|
"in": value => value ?? random(length).toString("hex"),
|
|
26
28
|
});
|
|
29
|
+
return new Proxy(this, {"get": (target, property, receiver) =>
|
|
30
|
+
Reflect.get(target, property, receiver) ?? target.#proxy(property)
|
|
31
|
+
}).set({...document, errors})
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
get Class() {
|
|
@@ -42,22 +47,9 @@ export default class Domain extends Base {
|
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
static get store() {
|
|
45
|
-
return cache(this, "store", async () =>
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const instance = store.default;
|
|
50
|
-
|
|
51
|
-
const message = `${instance.constructor.name} must instance Store`;
|
|
52
|
-
const error = () => { throw new InternalServerError(message); };
|
|
53
|
-
assert(instance instanceof Store, error);
|
|
54
|
-
|
|
55
|
-
return instance.open();
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
static get store_file() {
|
|
60
|
-
return this.conf.defaults.store;
|
|
50
|
+
return EagerPromise.resolve(cache(this, "store", async () =>
|
|
51
|
+
Store.get(this.stores_directory, this.store_file)
|
|
52
|
+
));
|
|
61
53
|
}
|
|
62
54
|
|
|
63
55
|
static get collection() {
|
|
@@ -102,18 +94,32 @@ export default class Domain extends Base {
|
|
|
102
94
|
return Object.assign(this, document);
|
|
103
95
|
}
|
|
104
96
|
|
|
105
|
-
|
|
106
|
-
|
|
97
|
+
#proxy(property) {
|
|
98
|
+
return typeof property === "string" ? this.#link(property) : this[property];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#link(name) {
|
|
107
102
|
const field = this.fields[`${name}_id`];
|
|
108
|
-
|
|
103
|
+
if (field?.is_domain) {
|
|
104
|
+
const collection = field.Type.collection;
|
|
105
|
+
const cache = this.Class.cache;
|
|
106
|
+
if (cache[collection] === undefined) {
|
|
107
|
+
cache[collection] = {};
|
|
108
|
+
}
|
|
109
|
+
if (cache[collection][this[`${name}_id`]] === undefined) {
|
|
110
|
+
cache[collection][this[`${name}_id`]] = field.by_id(this[`${name}_id`]);
|
|
111
|
+
}
|
|
112
|
+
return cache[collection][this[`${name}_id`]];
|
|
113
|
+
} else {
|
|
114
|
+
return undefined
|
|
115
|
+
}
|
|
109
116
|
}
|
|
110
117
|
|
|
111
|
-
// #serialize
|
|
112
118
|
// Serializing is done from the instance's point of view.
|
|
113
|
-
serialize() {
|
|
119
|
+
async serialize() {
|
|
114
120
|
const {properties, fields} = this;
|
|
115
|
-
return properties.map(property =>
|
|
116
|
-
({property, "value": fields[property].serialize(this[property])}))
|
|
121
|
+
return (await Promise.all(properties.map(async property =>
|
|
122
|
+
({property, "value": await fields[property].serialize(this[property])}))))
|
|
117
123
|
.filter(({value}) => value !== undefined)
|
|
118
124
|
.reduce((document, {property, value}) => {
|
|
119
125
|
document[property] = value;
|
|
@@ -121,12 +127,11 @@ export default class Domain extends Base {
|
|
|
121
127
|
}, {});
|
|
122
128
|
}
|
|
123
129
|
|
|
124
|
-
// #deserialize
|
|
125
130
|
// Deserializing is done from the class's point of view.
|
|
126
131
|
static deserialize(serialized) {
|
|
127
132
|
const fields = this._fields;
|
|
128
133
|
return new this(Object.keys(serialized)
|
|
129
|
-
|
|
134
|
+
.filter(property => fields[property] !== undefined)
|
|
130
135
|
.map(property =>
|
|
131
136
|
({property,
|
|
132
137
|
"value": fields[property].deserialize(serialized[property])}))
|
|
@@ -143,6 +148,7 @@ export default class Domain extends Base {
|
|
|
143
148
|
get collection() {
|
|
144
149
|
return this.Class.collection;
|
|
145
150
|
}
|
|
151
|
+
|
|
146
152
|
get properties() {
|
|
147
153
|
return this.Class.properties;
|
|
148
154
|
}
|
|
@@ -176,9 +182,12 @@ export default class Domain extends Base {
|
|
|
176
182
|
async savewith(delta, after = () => undefined) {
|
|
177
183
|
const verified = await this.verify(delta);
|
|
178
184
|
if (verified) {
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
185
|
+
const document = await this.serialize();
|
|
186
|
+
await this.store.save(this.collection, {"_id": document._id}, document);
|
|
187
|
+
const cache = this.Class.cache;
|
|
188
|
+
if (cache[this.collection]?.[document._id] !== undefined) {
|
|
189
|
+
delete cache[this.collection][document._id];
|
|
190
|
+
}
|
|
182
191
|
await after();
|
|
183
192
|
}
|
|
184
193
|
return verified;
|
|
@@ -202,26 +211,24 @@ export default class Domain extends Base {
|
|
|
202
211
|
return this;
|
|
203
212
|
}
|
|
204
213
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
return store.delete(this.collection, {"_id": this._id});
|
|
214
|
+
delete() {
|
|
215
|
+
return this.store.delete(this.collection, {"_id": this._id});
|
|
208
216
|
}
|
|
209
217
|
|
|
210
|
-
static
|
|
211
|
-
|
|
212
|
-
return store.delete(this.collection, criteria);
|
|
218
|
+
static delete(criteria) {
|
|
219
|
+
return this.store.delete(this.collection, criteria);
|
|
213
220
|
}
|
|
214
221
|
|
|
215
222
|
static by_id(_id) {
|
|
216
223
|
return new EagerPromise(async resolve => {
|
|
217
|
-
const result = await
|
|
218
|
-
resolve(result
|
|
224
|
+
const result = await this.store.find(this.collection, {"_id": await _id});
|
|
225
|
+
resolve(result.length > 0 ? this.deserialize(result[0]) : undefined);
|
|
219
226
|
});
|
|
220
227
|
}
|
|
221
228
|
|
|
222
|
-
static first(criteria) {
|
|
229
|
+
static first(criteria, options) {
|
|
223
230
|
return new EagerPromise(async resolve => {
|
|
224
|
-
const result = await
|
|
231
|
+
const result = await this.store.one(this.collection, criteria, options);
|
|
225
232
|
resolve(result === undefined ? undefined : this.deserialize(result));
|
|
226
233
|
});
|
|
227
234
|
}
|
|
@@ -235,14 +242,12 @@ export default class Domain extends Base {
|
|
|
235
242
|
}
|
|
236
243
|
|
|
237
244
|
static async find(criteria, options) {
|
|
238
|
-
const
|
|
239
|
-
const results = await store.find(this.collection, criteria, options);
|
|
245
|
+
const results = await this.store.find(this.collection, criteria, options);
|
|
240
246
|
return results.map(result => this.deserialize(result));
|
|
241
247
|
}
|
|
242
248
|
|
|
243
|
-
static
|
|
244
|
-
|
|
245
|
-
return store.count(this.collection, criteria);
|
|
249
|
+
static count(criteria) {
|
|
250
|
+
return this.store.count(this.collection, criteria);
|
|
246
251
|
}
|
|
247
252
|
|
|
248
253
|
static async exists(criteria) {
|
|
@@ -30,7 +30,7 @@ export default class Field {
|
|
|
30
30
|
this.property = property;
|
|
31
31
|
this.definition = parse(definition);
|
|
32
32
|
this.options = options ?? {"transient": false, "optional": false};
|
|
33
|
-
is_constructible(this.
|
|
33
|
+
is_constructible(this.Type);
|
|
34
34
|
instances(this.type.prototype, Storeable, "type must extend Storeable");
|
|
35
35
|
is_array(this.predicates);
|
|
36
36
|
}
|
|
@@ -46,15 +46,19 @@ export default class Field {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
get type() {
|
|
49
|
-
return builtins[this.
|
|
49
|
+
return builtins[this.Type] ?? this.custom;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
get custom() {
|
|
53
|
-
return this.is_domain ? DomainType : this.
|
|
53
|
+
return this.is_domain ? DomainType : this.Type;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
get is_domain() {
|
|
57
|
-
return this.
|
|
57
|
+
return this.Type.prototype instanceof DomainType.instance;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get Type(){
|
|
61
|
+
return this.definition.type;
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
get predicates() {
|
|
@@ -62,7 +66,7 @@ export default class Field {
|
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
by_id(id) {
|
|
65
|
-
return this.
|
|
69
|
+
return this.Type.by_id(id);
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
in(property, document) {
|
|
@@ -84,11 +88,10 @@ export default class Field {
|
|
|
84
88
|
return this.options.optional ? true : "Must not be empty";
|
|
85
89
|
}
|
|
86
90
|
|
|
87
|
-
async verify_defined(property,
|
|
91
|
+
async verify_defined(property, document) {
|
|
88
92
|
try {
|
|
89
93
|
const predicates = this.override_predicates(document);
|
|
90
|
-
|
|
91
|
-
this.definition.type);
|
|
94
|
+
await this.type.verify(property, document, predicates, this.Type);
|
|
92
95
|
return true;
|
|
93
96
|
} catch (error) {
|
|
94
97
|
if (error instanceof PredicateError) {
|
|
@@ -99,17 +102,17 @@ export default class Field {
|
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
async verify(property, document) {
|
|
102
|
-
|
|
103
|
-
return
|
|
105
|
+
document[property] = await this.in(property, document);
|
|
106
|
+
return document[property] === undefined
|
|
104
107
|
? this.verify_undefined()
|
|
105
|
-
: this.verify_defined(property,
|
|
108
|
+
: this.verify_defined(property, document);
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
serialize(value) {
|
|
109
|
-
return value
|
|
112
|
+
return value === undefined ? undefined : this.type.serialize(value);
|
|
110
113
|
}
|
|
111
114
|
|
|
112
115
|
deserialize(value) {
|
|
113
|
-
return value
|
|
116
|
+
return value === undefined ? undefined : this.type.deserialize(value);
|
|
114
117
|
}
|
|
115
118
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import conf from "../conf.js";
|
|
2
2
|
import File from "../File.js";
|
|
3
3
|
import Field from "./Field.js";
|
|
4
|
+
import Domain from "./Domain.js";
|
|
4
5
|
|
|
5
6
|
const domains = {};
|
|
6
7
|
const base = conf().paths.data.domains;
|
|
@@ -12,4 +13,19 @@ for (const domain of await new File(base).list(".js")) {
|
|
|
12
13
|
});
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
export const actuals = {};
|
|
17
|
+
for (const domain in domains) {
|
|
18
|
+
if (domains[domain].prototype instanceof Domain) {
|
|
19
|
+
const fields = {};
|
|
20
|
+
for (const field in domains[domain]._fields) {
|
|
21
|
+
const Type = domains[domain]._fields[field].Type;
|
|
22
|
+
if(Type.prototype instanceof Domain) {
|
|
23
|
+
fields[field.slice(0, -3)] = Type.name;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
actuals[domain] = fields;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
15
31
|
export default domains;
|
package/source/server/errors.js
CHANGED
package/source/server/exports.js
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
|
+
import conf from "./conf.js";
|
|
2
|
+
import App from "./App.js";
|
|
3
|
+
|
|
4
|
+
export {App};
|
|
1
5
|
export {default as Action} from "./Action.js";
|
|
2
6
|
export {default as Bundler} from "./Bundler.js";
|
|
3
7
|
export {default as Context} from "./Context.js";
|
|
4
8
|
export {default as Directory} from "./Directory.js";
|
|
5
9
|
export {default as File} from "./File.js";
|
|
10
|
+
export {default as EagerPromise, eager} from "./EagerPromise.js" ;
|
|
6
11
|
|
|
7
12
|
export {default as Domain} from "./domain/Domain.js";
|
|
8
13
|
export {default as domains} from "./domain/domains.js";
|
|
9
14
|
export {default as Storeable} from "./types/Storeable.js";
|
|
10
15
|
|
|
11
16
|
export * from "./errors.js";
|
|
12
|
-
export * from "./promises.js" ;
|
|
13
17
|
|
|
14
18
|
export {default as MemoryStore} from "./store/Memory.js";
|
|
15
19
|
export {default as Store} from "./store/Store.js";
|
|
16
20
|
|
|
17
21
|
export {assert, defined} from "./invariants.js";
|
|
18
22
|
export {default as log} from "./log.js";
|
|
19
|
-
export {default as extend_object} from "./
|
|
20
|
-
export {default as
|
|
21
|
-
|
|
22
|
-
import App from "./App.js";
|
|
23
|
+
export {default as extend_object} from "./extend_object.js";
|
|
24
|
+
export {default as sanitize} from "./sanitize.js";
|
|
23
25
|
|
|
24
|
-
const app = new App();
|
|
25
|
-
const conf = app.conf;
|
|
26
|
+
const app = new App(conf());
|
|
26
27
|
|
|
27
|
-
export {app
|
|
28
|
+
export {app};
|
|
File without changes
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import {FallbackError} from "./errors.js";
|
|
2
1
|
import {constructible} from "./attributes.js";
|
|
3
2
|
|
|
4
3
|
const errored = error => {
|
|
5
4
|
if (typeof error === "function") { // fallback
|
|
6
5
|
error();
|
|
7
|
-
throw new FallbackError();
|
|
8
6
|
} else { // error
|
|
9
7
|
throw new Error(error);
|
|
10
8
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export default (payload = {}) => Object.keys(payload)
|
|
2
|
+
.map(key => ({key, "value": payload[key].toString().trim()}))
|
|
3
|
+
.map(datum => { datum.value = datum.value !== "" ? datum.value : undefined;
|
|
4
|
+
return datum; })
|
|
5
|
+
.reduce((data, {key, value}) => { data[key] = value; return data; }, {});
|
|
@@ -12,7 +12,8 @@ export default class DynamicServer extends Server {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
async connected(socket, request) {
|
|
15
|
-
|
|
15
|
+
const {context} = this.conf;
|
|
16
|
+
socket.session = await Session.get(request.headers.cookie, context);
|
|
16
17
|
socket.on("message", event => this.try(socket, event));
|
|
17
18
|
// inform client that we're connected and it can start sending messages
|
|
18
19
|
socket.send("open");
|
|
@@ -20,31 +21,36 @@ export default class DynamicServer extends Server {
|
|
|
20
21
|
|
|
21
22
|
async try(socket, event) {
|
|
22
23
|
const {session} = socket;
|
|
23
|
-
const
|
|
24
|
-
const url = parsed.pathname + (parsed.search ?? "");
|
|
24
|
+
const request = JSON.parse(event);
|
|
25
25
|
try {
|
|
26
|
-
await this.onmessage(
|
|
27
|
-
await session.log("green", `${
|
|
26
|
+
await this.onmessage(request, socket);
|
|
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 onmessage(
|
|
33
|
+
async onmessage(request, socket) {
|
|
34
34
|
const {router} = this.conf;
|
|
35
35
|
const {session} = socket;
|
|
36
|
-
const {path, params} = await session.route(router, url);
|
|
37
|
-
const
|
|
38
|
-
|
|
36
|
+
const {path, params} = await session.route(router, request.url);
|
|
37
|
+
const response = await session.run({...request, path, params});
|
|
38
|
+
const {type} = response;
|
|
39
|
+
return this[type]?.(socket, response)
|
|
40
|
+
?? DynamicServer.send(socket, response);
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
static send(socket, response) {
|
|
44
|
+
socket.send(JSON.stringify(response));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
redirect(socket, {location}) {
|
|
48
|
+
if (location.startsWith("https://")) {
|
|
43
49
|
// redirect externally
|
|
44
50
|
return undefined;
|
|
45
51
|
} else {
|
|
46
|
-
const
|
|
47
|
-
this.try(socket, JSON.stringify({pathname, "type": "read"}));
|
|
52
|
+
const url = location;
|
|
53
|
+
this.try(socket, JSON.stringify({"pathname": url, url, "type": "read"}));
|
|
48
54
|
return true;
|
|
49
55
|
}
|
|
50
56
|
}
|
|
@@ -6,7 +6,8 @@ import {parse} from "url";
|
|
|
6
6
|
import Server from "./Server.js";
|
|
7
7
|
import Session from "../Session.js";
|
|
8
8
|
import File from "../File.js";
|
|
9
|
-
import {algorithm, hash} from "../
|
|
9
|
+
import {algorithm, hash} from "../crypto.js";
|
|
10
|
+
import {assert} from "../invariants.js";
|
|
10
11
|
import log from "../log.js";
|
|
11
12
|
import codes from "./http-codes.json" assert {"type": "json"};
|
|
12
13
|
import mimes from "./mimes.json" assert {"type": "json"};
|
|
@@ -28,46 +29,50 @@ const stream = (from, response) => {
|
|
|
28
29
|
|
|
29
30
|
export default class StaticServer extends Server {
|
|
30
31
|
async run() {
|
|
31
|
-
const {http} = this.conf;
|
|
32
|
+
const {http, context} = this.conf;
|
|
32
33
|
|
|
33
34
|
this.server = await createServer(http, async (request, response) => {
|
|
34
|
-
const session = await Session.get(request.headers.cookie);
|
|
35
|
+
const session = await Session.get(request.headers.cookie, context);
|
|
35
36
|
if (!session.has_cookie) {
|
|
36
37
|
response.setHeader("Set-Cookie", session.cookie);
|
|
37
38
|
}
|
|
39
|
+
response.session = session;
|
|
38
40
|
request.on("end", () =>
|
|
39
|
-
this.try(parse(request.url).path,
|
|
41
|
+
this.try(parse(request.url).path, request, response)
|
|
40
42
|
).resume();
|
|
41
43
|
});
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
async try(url,
|
|
46
|
+
async try(url, request, response) {
|
|
45
47
|
try {
|
|
46
|
-
await this.serve(url,
|
|
48
|
+
await this.serve(url, request, response);
|
|
47
49
|
} catch (error) {
|
|
48
|
-
await session.log("red", error.message);
|
|
50
|
+
await response.session.log("red", error.message);
|
|
49
51
|
response.writeHead(codes.InternalServerError);
|
|
50
52
|
response.end();
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
async
|
|
56
|
+
async serve_file(url, filename, file, response) {
|
|
57
|
+
response.setHeader("Content-Type", mime(filename));
|
|
58
|
+
response.setHeader("Etag", file.modified);
|
|
59
|
+
await response.session.log("green", url);
|
|
60
|
+
return stream(file.read_stream, response);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async serve(url, request, response) {
|
|
55
64
|
const filename = join(this.conf.serve_from, url);
|
|
56
65
|
const file = await new File(filename);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
await session.log("green", url);
|
|
61
|
-
return stream(file.stream, response);
|
|
62
|
-
} else {
|
|
63
|
-
return this.serve_data(url, session, request, response);
|
|
64
|
-
}
|
|
66
|
+
return await file.is_file
|
|
67
|
+
? this.serve_file(url, filename, file, response)
|
|
68
|
+
: this.serve_data(url, request, response);
|
|
65
69
|
}
|
|
66
70
|
|
|
67
|
-
async serve_data(pathname,
|
|
71
|
+
async serve_data(pathname, request, response) {
|
|
72
|
+
const {session} = response;
|
|
68
73
|
const {path, params} = await session.route(this.conf.router, pathname);
|
|
69
74
|
await session.log("green", `read ${pathname}`);
|
|
70
|
-
const data = await session.run({path, params, pathname});
|
|
75
|
+
const data = await session.run({path, params, pathname, "url": pathname});
|
|
71
76
|
const handler = StaticServer[data.type] ?? this.index.bind(this);
|
|
72
77
|
return handler(data, response);
|
|
73
78
|
}
|
|
@@ -75,11 +80,11 @@ export default class StaticServer extends Server {
|
|
|
75
80
|
index(data, response) {
|
|
76
81
|
const {hashes, index} = this.conf;
|
|
77
82
|
const src = "import {app} from './client/primate.js';"
|
|
78
|
-
+ `app.client.
|
|
83
|
+
+ `app.client.session.run(${JSON.stringify(data)});`;
|
|
79
84
|
const integrity = `${algorithm}-${hash(src)}`;
|
|
80
85
|
const view = `<script type="module" integrity="${integrity}">${src}`
|
|
81
86
|
+ "</script>";
|
|
82
|
-
const file =
|
|
87
|
+
const file = index.replace("<first-view />", view);
|
|
83
88
|
const script_src = Array.from(hashes)
|
|
84
89
|
.concat([integrity])
|
|
85
90
|
.reduce((hash_string, next_hash) => hash_string + ` '${next_hash}'`, "");
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {resolve} from "path";
|
|
2
|
+
const preset = "../../preset/data/stores";
|
|
3
|
+
|
|
1
4
|
export default class Store {
|
|
2
5
|
constructor(conf = {}) {
|
|
3
6
|
this.conf = conf;
|
|
@@ -14,4 +17,14 @@ export default class Store {
|
|
|
14
17
|
open() {
|
|
15
18
|
return this;
|
|
16
19
|
}
|
|
20
|
+
|
|
21
|
+
static async get(directory, file) {
|
|
22
|
+
let store;
|
|
23
|
+
try {
|
|
24
|
+
store = await import(resolve(`${directory}/${file}`));
|
|
25
|
+
} catch(error) {
|
|
26
|
+
store = await import(`${preset}/${file}`);
|
|
27
|
+
}
|
|
28
|
+
return store.default.open();
|
|
29
|
+
}
|
|
17
30
|
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import {PredicateError} from "../errors.js";
|
|
2
2
|
|
|
3
3
|
export default class {
|
|
4
|
-
static async verify(property,
|
|
5
|
-
|
|
6
|
-
if (!await this.is(
|
|
4
|
+
static async verify(property, document, predicates, type) {
|
|
5
|
+
document[property] = this.coerce(document[property]);
|
|
6
|
+
if (!await this.is(document[property], type)) {
|
|
7
7
|
throw new PredicateError(this.type_error(type));
|
|
8
8
|
}
|
|
9
|
-
await this.has(property,
|
|
10
|
-
return coerced;
|
|
9
|
+
await this.has(property, document, predicates);
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
static type_error() {
|
|
@@ -18,13 +17,13 @@ export default class {
|
|
|
18
17
|
throw new Error("must be implemented");
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
static async has(property,
|
|
20
|
+
static async has(property, document, predicates) {
|
|
22
21
|
for (const predicate of predicates) {
|
|
23
22
|
if (typeof predicate === "object") {
|
|
24
23
|
await predicate.function(property, ...predicate.params);
|
|
25
24
|
} else {
|
|
26
25
|
const [name, ...params] = predicate.split(":");
|
|
27
|
-
if (!this[name](
|
|
26
|
+
if (!this[name](document[property], ...params)) {
|
|
28
27
|
let error = this.errors[name];
|
|
29
28
|
for (let i = 0; i < params.length; i++) {
|
|
30
29
|
error = error.replace(`$${i+1}`, params[i]);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Parser from "./Parser.js";
|
|
2
|
+
import File from "../File.js";
|
|
2
3
|
import {InternalServerError} from "../errors.js";
|
|
3
4
|
|
|
4
5
|
const $content = "${content}";
|
|
@@ -10,6 +11,10 @@ export default class View {
|
|
|
10
11
|
this.layouts = layouts;
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
static async new(path, layouts) {
|
|
15
|
+
return new View(path, await File.read(`${path}.html`), layouts);
|
|
16
|
+
}
|
|
17
|
+
|
|
13
18
|
elements(layout) {
|
|
14
19
|
try {
|
|
15
20
|
return Parser.parse(layout === undefined
|