primate 0.2.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 +11 -5
- package/package.json +9 -3
- package/source/client/Action.js +20 -22
- package/source/client/Client.js +13 -17
- package/source/client/Context.js +10 -16
- package/source/client/Element.js +21 -15
- package/source/client/Session.js +27 -0
- package/source/client/View.js +1 -2
- package/source/client/document.js +1 -1
- package/source/preset/primate.json +2 -6
- package/source/server/Action.js +56 -75
- package/source/server/App.js +27 -8
- package/source/server/Bundler.js +13 -16
- package/source/server/Context.js +63 -56
- package/source/server/{promises/Eager.js → EagerPromise.js} +4 -2
- package/source/server/File.js +6 -2
- package/source/server/Projector.js +86 -0
- package/source/server/Router.js +15 -24
- package/source/server/Session.js +9 -33
- package/source/server/attributes.js +3 -0
- package/source/server/conf.js +20 -26
- package/source/server/{Crypto.js → crypto.js} +0 -0
- package/source/server/domain/Domain.js +60 -48
- package/source/server/domain/Field.js +34 -36
- package/source/server/domain/Predicate.js +24 -0
- package/source/server/domain/domains.js +16 -0
- package/source/server/errors/InternalServer.js +1 -1
- package/source/server/errors/Predicate.js +1 -1
- package/source/server/errors.js +0 -1
- package/source/server/exports.js +10 -9
- package/source/server/{utils/extend_object.js → extend_object.js} +0 -0
- package/source/server/invariants.js +23 -10
- package/source/server/sanitize.js +10 -0
- package/source/server/servers/Dynamic.js +19 -13
- package/source/server/servers/Server.js +1 -1
- package/source/server/servers/Static.js +25 -20
- package/source/server/servers/content-security-policy.json +0 -1
- package/source/server/store/Store.js +13 -0
- 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 -6
- 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 +12 -19
- package/source/server/types/String.js +2 -2
- package/source/server/view/TreeNode.js +2 -0
- package/source/server/view/View.js +6 -1
- package/source/client/Base.js +0 -5
- package/source/server/Base.js +0 -35
- 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,15 +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
|
-
return store.default.open();
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
static get store_file() {
|
|
53
|
-
return this.conf.defaults.store;
|
|
50
|
+
return EagerPromise.resolve(cache(this, "store", async () =>
|
|
51
|
+
Store.get(this.stores_directory, this.store_file)
|
|
52
|
+
));
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
static get collection() {
|
|
@@ -95,18 +94,32 @@ export default class Domain extends Base {
|
|
|
95
94
|
return Object.assign(this, document);
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
#proxy(property) {
|
|
98
|
+
return typeof property === "string" ? this.#link(property) : this[property];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#link(name) {
|
|
100
102
|
const field = this.fields[`${name}_id`];
|
|
101
|
-
|
|
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
|
+
}
|
|
102
116
|
}
|
|
103
117
|
|
|
104
|
-
// #serialize
|
|
105
118
|
// Serializing is done from the instance's point of view.
|
|
106
|
-
serialize() {
|
|
119
|
+
async serialize() {
|
|
107
120
|
const {properties, fields} = this;
|
|
108
|
-
return properties.map(property =>
|
|
109
|
-
({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])}))))
|
|
110
123
|
.filter(({value}) => value !== undefined)
|
|
111
124
|
.reduce((document, {property, value}) => {
|
|
112
125
|
document[property] = value;
|
|
@@ -114,7 +127,6 @@ export default class Domain extends Base {
|
|
|
114
127
|
}, {});
|
|
115
128
|
}
|
|
116
129
|
|
|
117
|
-
// #deserialize
|
|
118
130
|
// Deserializing is done from the class's point of view.
|
|
119
131
|
static deserialize(serialized) {
|
|
120
132
|
const fields = this._fields;
|
|
@@ -136,6 +148,7 @@ export default class Domain extends Base {
|
|
|
136
148
|
get collection() {
|
|
137
149
|
return this.Class.collection;
|
|
138
150
|
}
|
|
151
|
+
|
|
139
152
|
get properties() {
|
|
140
153
|
return this.Class.properties;
|
|
141
154
|
}
|
|
@@ -169,9 +182,12 @@ export default class Domain extends Base {
|
|
|
169
182
|
async savewith(delta, after = () => undefined) {
|
|
170
183
|
const verified = await this.verify(delta);
|
|
171
184
|
if (verified) {
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
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
|
+
}
|
|
175
191
|
await after();
|
|
176
192
|
}
|
|
177
193
|
return verified;
|
|
@@ -195,26 +211,24 @@ export default class Domain extends Base {
|
|
|
195
211
|
return this;
|
|
196
212
|
}
|
|
197
213
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
return store.delete(this.collection, {"_id": this._id});
|
|
214
|
+
delete() {
|
|
215
|
+
return this.store.delete(this.collection, {"_id": this._id});
|
|
201
216
|
}
|
|
202
217
|
|
|
203
|
-
static
|
|
204
|
-
|
|
205
|
-
return store.delete(this.collection, criteria);
|
|
218
|
+
static delete(criteria) {
|
|
219
|
+
return this.store.delete(this.collection, criteria);
|
|
206
220
|
}
|
|
207
221
|
|
|
208
222
|
static by_id(_id) {
|
|
209
223
|
return new EagerPromise(async resolve => {
|
|
210
|
-
const result = await
|
|
211
|
-
resolve(result
|
|
224
|
+
const result = await this.store.find(this.collection, {"_id": await _id});
|
|
225
|
+
resolve(result.length > 0 ? this.deserialize(result[0]) : undefined);
|
|
212
226
|
});
|
|
213
227
|
}
|
|
214
228
|
|
|
215
|
-
static first(criteria) {
|
|
229
|
+
static first(criteria, options) {
|
|
216
230
|
return new EagerPromise(async resolve => {
|
|
217
|
-
const result = await
|
|
231
|
+
const result = await this.store.one(this.collection, criteria, options);
|
|
218
232
|
resolve(result === undefined ? undefined : this.deserialize(result));
|
|
219
233
|
});
|
|
220
234
|
}
|
|
@@ -228,14 +242,12 @@ export default class Domain extends Base {
|
|
|
228
242
|
}
|
|
229
243
|
|
|
230
244
|
static async find(criteria, options) {
|
|
231
|
-
const
|
|
232
|
-
const results = await store.find(this.collection, criteria, options);
|
|
245
|
+
const results = await this.store.find(this.collection, criteria, options);
|
|
233
246
|
return results.map(result => this.deserialize(result));
|
|
234
247
|
}
|
|
235
248
|
|
|
236
|
-
static
|
|
237
|
-
|
|
238
|
-
return store.count(this.collection, criteria);
|
|
249
|
+
static count(criteria) {
|
|
250
|
+
return this.store.count(this.collection, criteria);
|
|
239
251
|
}
|
|
240
252
|
|
|
241
253
|
static async exists(criteria) {
|
|
@@ -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) {
|
|
@@ -46,23 +48,30 @@ export default class Field {
|
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
get type() {
|
|
49
|
-
return builtins[this.
|
|
51
|
+
return builtins[this.Type] ?? this.custom;
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
get custom() {
|
|
53
|
-
return this.is_domain ? DomainType : this.
|
|
55
|
+
return this.is_domain ? DomainType : this.Type;
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
get is_domain() {
|
|
57
|
-
return this.
|
|
59
|
+
return this.Type.prototype instanceof DomainType.instance;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get Type() {
|
|
63
|
+
return this.definition.type;
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
get predicates() {
|
|
61
|
-
return this
|
|
67
|
+
return cache(this, "predicates", () => {
|
|
68
|
+
const predicates = this.definition.predicates ?? [];
|
|
69
|
+
return predicates.map(name => new Predicate(name));
|
|
70
|
+
});
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
by_id(id) {
|
|
65
|
-
return this.
|
|
74
|
+
return this.Type.by_id(id);
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
in(property, document) {
|
|
@@ -71,24 +80,13 @@ export default class Field {
|
|
|
71
80
|
return in_function !== undefined ? in_function(value, document) : value;
|
|
72
81
|
}
|
|
73
82
|
|
|
74
|
-
override_predicates(document) {
|
|
75
|
-
return this.predicates.map(predicate => {
|
|
76
|
-
const [name, ...params] = predicate.split(":");
|
|
77
|
-
return document[name] !== undefined
|
|
78
|
-
? {"function": document[name].bind(document), params}
|
|
79
|
-
: predicate;
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
83
|
verify_undefined() {
|
|
84
84
|
return this.options.optional ? true : "Must not be empty";
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
async verify_defined(property,
|
|
87
|
+
async verify_defined(property, document) {
|
|
88
88
|
try {
|
|
89
|
-
|
|
90
|
-
document[property] = await this.type.verify(property, value, predicates,
|
|
91
|
-
this.definition.type);
|
|
89
|
+
await this.type.verify(property, document, this.predicates, this.Type);
|
|
92
90
|
return true;
|
|
93
91
|
} catch (error) {
|
|
94
92
|
if (error instanceof PredicateError) {
|
|
@@ -99,17 +97,17 @@ export default class Field {
|
|
|
99
97
|
}
|
|
100
98
|
|
|
101
99
|
async verify(property, document) {
|
|
102
|
-
|
|
103
|
-
return
|
|
100
|
+
document[property] = await this.in(property, document);
|
|
101
|
+
return document[property] === undefined
|
|
104
102
|
? this.verify_undefined()
|
|
105
|
-
: this.verify_defined(property,
|
|
103
|
+
: this.verify_defined(property, document);
|
|
106
104
|
}
|
|
107
105
|
|
|
108
106
|
serialize(value) {
|
|
109
|
-
return value
|
|
107
|
+
return value === undefined ? undefined : this.type.serialize(value);
|
|
110
108
|
}
|
|
111
109
|
|
|
112
110
|
deserialize(value) {
|
|
113
|
-
return value
|
|
111
|
+
return value === undefined ? undefined : this.type.deserialize(value);
|
|
114
112
|
}
|
|
115
113
|
}
|
|
@@ -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,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;
|
|
@@ -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/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 "./
|
|
17
|
+
export * from "./invariants.js";
|
|
13
18
|
|
|
14
19
|
export {default as MemoryStore} from "./store/Memory.js";
|
|
15
20
|
export {default as Store} from "./store/Store.js";
|
|
16
21
|
|
|
17
|
-
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,19 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {constructible} from "./attributes.js";
|
|
1
|
+
import {constructible, nullish} from "./attributes.js";
|
|
3
2
|
|
|
4
3
|
const errored = error => {
|
|
5
|
-
if (typeof error === "function") {
|
|
4
|
+
if (typeof error === "function") {
|
|
5
|
+
// fallback
|
|
6
6
|
error();
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
} else {
|
|
8
|
+
// error
|
|
9
9
|
throw new Error(error);
|
|
10
10
|
}
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
const assert = (predicate, error) => Boolean(predicate) || errored(error);
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
+
}, {});
|
|
18
31
|
|
|
19
|
-
export {assert, defined,
|
|
32
|
+
export {assert, defined, is, maybe};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default (payload = {}) => Object.keys(payload)
|
|
2
|
+
.map(key => ({key, "value": payload[key].toString().trim()}))
|
|
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
|
+
}, {});
|
|
@@ -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.
|
|
27
|
-
await session.log("green", `${
|
|
26
|
+
await this.serve(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
|
|
33
|
+
async serve(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.serve(socket, response);
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
static serve(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}'`, "");
|