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.
Files changed (46) hide show
  1. package/README.md +4 -3
  2. package/package.json +9 -3
  3. package/source/client/Action.js +14 -19
  4. package/source/client/App.js +1 -4
  5. package/source/client/Client.js +14 -24
  6. package/source/client/Context.js +9 -15
  7. package/source/client/Element.js +12 -5
  8. package/source/client/Session.js +27 -0
  9. package/source/client/document.js +6 -0
  10. package/source/preset/primate.json +2 -6
  11. package/source/server/Action.js +53 -75
  12. package/source/server/App.js +26 -7
  13. package/source/server/Bundler.js +13 -16
  14. package/source/server/Context.js +64 -56
  15. package/source/server/{promises/Eager.js → EagerPromise.js} +3 -1
  16. package/source/server/File.js +6 -2
  17. package/source/server/Projector.js +86 -0
  18. package/source/server/Router.js +7 -9
  19. package/source/server/Session.js +9 -33
  20. package/source/server/attributes.js +2 -0
  21. package/source/server/conf.js +20 -26
  22. package/source/server/{Crypto.js → crypto.js} +0 -0
  23. package/source/server/domain/Domain.js +61 -56
  24. package/source/server/domain/Field.js +16 -13
  25. package/source/server/domain/domains.js +16 -0
  26. package/source/server/errors.js +0 -1
  27. package/source/server/exports.js +9 -8
  28. package/source/server/{utils/extend_object.js → extend_object.js} +0 -0
  29. package/source/server/invariants.js +0 -2
  30. package/source/server/sanitize.js +5 -0
  31. package/source/server/servers/Dynamic.js +19 -13
  32. package/source/server/servers/Static.js +25 -20
  33. package/source/server/servers/content-security-policy.json +0 -2
  34. package/source/server/store/Store.js +13 -0
  35. package/source/server/types/Date.js +2 -2
  36. package/source/server/types/Domain.js +0 -5
  37. package/source/server/types/Storeable.js +6 -7
  38. package/source/server/view/TreeNode.js +2 -0
  39. package/source/server/view/View.js +5 -0
  40. package/source/client/Base.js +0 -5
  41. package/source/server/Base.js +0 -35
  42. package/source/server/constructible.js +0 -8
  43. package/source/server/errors/Fallback.js +0 -1
  44. package/source/server/fallback.js +0 -11
  45. package/source/server/promises/Meta.js +0 -42
  46. package/source/server/promises.js +0 -2
@@ -1,29 +1,34 @@
1
- import Base from "../Base.js";
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 {random} from "../Crypto.js";
9
- import fallback from "../fallback.js";
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
- const get = (target, property, receiver) =>
15
- Reflect.get(target, property, receiver) ?? receiver.as_foreign(property);
12
+ export default class Domain {
13
+ static stores_directory = "data/stores";
14
+ static store_file = "default.js";
16
15
 
17
- export default class Domain extends Base {
18
- constructor(document) {
19
- super();
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
- return new Proxy(this, {get}).set({...document, errors}).define("_id", {
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
- const logic = path => import(`${path}/${this.store_file}`);
47
- const store = await fallback(this.conf.paths.data.stores, preset, logic);
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
- // #as_foreign
106
- as_foreign(name) {
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
- return field?.is_domain ? field.by_id(this[`${name}_id`]) : undefined;
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
- //.filter(property => fields[property] !== undefined)
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 store = await this.store;
180
- const document = this.serialize();
181
- await store.save(this.collection, {"_id": document._id}, document);
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
- async delete() {
206
- const store = await this.store;
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 async delete(criteria) {
211
- const store = await this.store;
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 (await this.store).one(this.collection, _id);
218
- resolve(result === undefined ? undefined : this.deserialize(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 (await this.store).one(this.collection, criteria);
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 store = await this.store;
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 async count(criteria) {
244
- const store = await this.store;
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.definition.type);
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.definition.type] ?? this.custom;
49
+ return builtins[this.Type] ?? this.custom;
50
50
  }
51
51
 
52
52
  get custom() {
53
- return this.is_domain ? DomainType : this.definition.type;
53
+ return this.is_domain ? DomainType : this.Type;
54
54
  }
55
55
 
56
56
  get is_domain() {
57
- return this.definition.type.prototype instanceof DomainType.instance;
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.definition.type.by_id(id);
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, value, document) {
91
+ async verify_defined(property, document) {
88
92
  try {
89
93
  const predicates = this.override_predicates(document);
90
- document[property] = await this.type.verify(property, value, predicates,
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
- const value = await this.in(property, document);
103
- return value === undefined
105
+ document[property] = await this.in(property, document);
106
+ return document[property] === undefined
104
107
  ? this.verify_undefined()
105
- : this.verify_defined(property, value, document);
108
+ : this.verify_defined(property, document);
106
109
  }
107
110
 
108
111
  serialize(value) {
109
- return value !== undefined ? this.type.serialize(value) : value;
112
+ return value === undefined ? undefined : this.type.serialize(value);
110
113
  }
111
114
 
112
115
  deserialize(value) {
113
- return value !== undefined ? this.type.deserialize(value) : 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;
@@ -1,3 +1,2 @@
1
- export {default as FallbackError} from "./errors/Fallback.js";
2
1
  export {default as InternalServerError} from "./errors/InternalServer.js";
3
2
  export {default as PredicateError} from "./errors/Predicate.js";
@@ -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 "./utils/extend_object.js";
20
- export {default as fallback} from "./fallback.js";
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, conf};
28
+ export {app};
@@ -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
- socket.session = await Session.get(request.headers.cookie);
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 parsed = JSON.parse(event);
24
- const url = parsed.pathname + (parsed.search ?? "");
24
+ const request = JSON.parse(event);
25
25
  try {
26
- await this.onmessage(socket, parsed, url);
27
- await session.log("green", `${parsed.type} ${url}`);
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(socket, parsed, url) {
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 data = await session.run({...parsed, path, params});
38
- return this[data.type]?.(socket, data) ?? socket.send(JSON.stringify(data));
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
- redirect(socket, data) {
42
- if (data.location.startsWith("https://")) {
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 pathname = data.location;
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 "../Crypto.js";
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, session, request, response)
41
+ this.try(parse(request.url).path, request, response)
40
42
  ).resume();
41
43
  });
42
44
  }
43
45
 
44
- async try(url, session, request, response) {
46
+ async try(url, request, response) {
45
47
  try {
46
- await this.serve(url, session, request, response);
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 serve(url, session, request, response) {
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
- if (file.is_file) {
58
- response.setHeader("Content-Type", mime(filename));
59
- response.setHeader("Etag", file.modified);
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, session, request, response) {
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.execute(${JSON.stringify(data)});`;
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 = new Function("view", "return `"+index+"`")(view);
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,8 +1,6 @@
1
1
  {
2
2
  "default-src": "'self'",
3
3
  "object-src": "'none'",
4
- "style-src": "'self'",
5
- "img-src": "'self' data: https:",
6
4
  "frame-ancestors": "'none'",
7
5
  "form-action": "'self'",
8
6
  "base-uri": "'self'"
@@ -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
  }
@@ -14,7 +14,7 @@ export default class extends Instance {
14
14
  return value instanceof this.instance;
15
15
  }
16
16
 
17
- /*static deserialize(value) {
17
+ static deserialize(value) {
18
18
  return value instanceof this.instance ? value : new this.instance(value);
19
- }*/
19
+ }
20
20
  }
@@ -1,11 +1,6 @@
1
1
  import Storeable from "./Storeable.js";
2
- import Domain from "../domain/Domain.js";
3
2
 
4
3
  export default class extends Storeable {
5
- static get instance() {
6
- return Domain;
7
- }
8
-
9
4
  static type_error({name}) {
10
5
  return `Must be a ${name}`;
11
6
  }
@@ -1,13 +1,12 @@
1
1
  import {PredicateError} from "../errors.js";
2
2
 
3
3
  export default class {
4
- static async verify(property, value, predicates, type) {
5
- const coerced = this.coerce(value);
6
- if (!await this.is(coerced, type)) {
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, coerced, predicates);
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, value, predicates) {
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](value, ...params)) {
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]);
@@ -147,7 +147,9 @@ export default class TreeNode {
147
147
  put(found_node, right);
148
148
  }
149
149
  };
150
+ //const flattened = [...new Set(flatten)];
150
151
  flatten.forEach(part => put(tree, part));
152
+ //return {tree, flattened};
151
153
  return tree;
152
154
  }
153
155
 
@@ -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
@@ -1,5 +0,0 @@
1
- export default class Base {
2
- Class() {
3
- return this.constructor;
4
- }
5
- }