primate 0.4.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.
Files changed (36) hide show
  1. package/README.md +7 -2
  2. package/package.json +2 -2
  3. package/source/client/Action.js +8 -7
  4. package/source/client/Client.js +3 -3
  5. package/source/client/Context.js +2 -2
  6. package/source/client/Element.js +10 -11
  7. package/source/client/View.js +1 -2
  8. package/source/server/Action.js +7 -4
  9. package/source/server/App.js +1 -1
  10. package/source/server/Bundler.js +2 -2
  11. package/source/server/Context.js +1 -2
  12. package/source/server/EagerPromise.js +1 -1
  13. package/source/server/Projector.js +12 -12
  14. package/source/server/Router.js +12 -19
  15. package/source/server/attributes.js +1 -0
  16. package/source/server/domain/Field.js +21 -26
  17. package/source/server/domain/Predicate.js +24 -0
  18. package/source/server/errors/InternalServer.js +1 -1
  19. package/source/server/errors/Predicate.js +1 -1
  20. package/source/server/exports.js +1 -1
  21. package/source/server/invariants.js +23 -8
  22. package/source/server/sanitize.js +8 -3
  23. package/source/server/servers/Dynamic.js +4 -4
  24. package/source/server/servers/Server.js +1 -1
  25. package/source/server/servers/Static.js +1 -1
  26. package/source/server/types/Array.js +2 -2
  27. package/source/server/types/Boolean.js +2 -2
  28. package/source/server/types/Date.js +2 -2
  29. package/source/server/types/Domain.js +1 -1
  30. package/source/server/types/Instance.js +1 -1
  31. package/source/server/types/Number.js +2 -2
  32. package/source/server/types/Object.js +2 -2
  33. package/source/server/types/Primitive.js +1 -1
  34. package/source/server/types/Storeable.js +9 -15
  35. package/source/server/types/String.js +2 -2
  36. package/source/server/view/View.js +1 -1
package/README.md CHANGED
@@ -27,7 +27,7 @@ yarn add primate
27
27
  * **Sane defaults** minimally opinionated with good, overrideable defaults
28
28
  for most features
29
29
  * **Data linking** modeling `1:1`, `1:n` and `n:m` relationships on domains via
30
- ad-hoc getters
30
+ cacheable ad-hoc getters
31
31
 
32
32
  ## Getting started
33
33
 
@@ -35,7 +35,10 @@ See the [getting started][getting-started] guide.
35
35
 
36
36
  ## Resources
37
37
 
38
- Coming soon.
38
+ * [Source code][source-code]
39
+ * [Issues][issues]
40
+
41
+ A full guide is coming soon.
39
42
 
40
43
  ## Versioning
41
44
 
@@ -49,3 +52,5 @@ BSD-3-Clause
49
52
 
50
53
  [ws]: https://github.com/websockets/ws
51
54
  [getting-started]: https://primatejs.com/getting-started
55
+ [source-code]: https://adaptivecloud.dev/primate/primate
56
+ [issues]: https://adaptivecloud.dev/primate/primate/issues
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primate",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "author": "Primate core team <core@primatejs.com>",
5
5
  "homepage": "https://primatejs.com",
6
6
  "bugs": "https://adaptivecloud.dev/primate/primate/issues",
@@ -8,7 +8,7 @@
8
8
  "description": "Server-client framework",
9
9
  "license": "BSD-3-Clause",
10
10
  "dependencies": {
11
- "ws": "^8.4.2"
11
+ "ws": "^8.5.0"
12
12
  },
13
13
  "scripts": {
14
14
  "copy": "rm -rf output && mkdir output && cp source/* output -a",
@@ -11,7 +11,7 @@ export default class Action {
11
11
 
12
12
  before() {}
13
13
 
14
- async enter(data) {
14
+ enter(data) {
15
15
  if (data.location) {
16
16
  location.href = data.location;
17
17
  } else {
@@ -28,13 +28,13 @@ export default class Action {
28
28
  this.view = new View(data, this, root);
29
29
  }
30
30
 
31
- create_and_return_view(data, root, save_history) {
31
+ create_and_return_view(data, root) {
32
32
  return new View(data, this, root);
33
33
  }
34
34
 
35
35
  get_form_data(target) {
36
36
  const payload = {};
37
- const elements = target.elements;
37
+ const {elements} = target;
38
38
  for (let i = 0; i < elements.length; i++) {
39
39
  const element = elements[i];
40
40
  const type = Element.value(element, "type");
@@ -64,7 +64,7 @@ export default class Action {
64
64
  }
65
65
 
66
66
  async onsubmit(event) {
67
- const target = event.target;
67
+ const {target} = event;
68
68
  const stop = this.fire("submit", event);
69
69
  if (stop) {
70
70
  return event.preventDefault();
@@ -108,13 +108,13 @@ export default class Action {
108
108
  }
109
109
 
110
110
  oninput() {
111
- const stop = this.fire("input", event);
111
+ this.fire("input", event);
112
112
  }
113
113
 
114
114
  fire(type, event) {
115
115
  const listeners = this.listeners[type];
116
116
  if (listeners !== undefined) {
117
- const target = event.target;
117
+ const {target} = event;
118
118
  for (const listener of listeners) {
119
119
  if (event.target.closest(listener.selector)) {
120
120
  listener.listener(event, target);
@@ -131,7 +131,8 @@ export default class Action {
131
131
  const index = this.listeners[event]
132
132
  .findIndex(listener => listener.selector === selector);
133
133
  const object = {selector, listener};
134
- if (index === -1) {
134
+ const not_found = -1;
135
+ if (index === not_found) {
135
136
  this.listeners[event].push(object);
136
137
  } else {
137
138
  this.listeners[event].splice(index, 1, object);
@@ -31,11 +31,11 @@ export default class Client {
31
31
  }
32
32
 
33
33
  async receive(data) {
34
- if (back !== undefined) {
34
+ if (back === undefined) {
35
+ await this.session.execute(data);
36
+ } else {
35
37
  back(data);
36
38
  back = undefined;
37
- } else {
38
- await this.session.execute(data);
39
39
  }
40
40
  }
41
41
 
@@ -16,8 +16,8 @@ export default class Context {
16
16
  async run(data) {
17
17
  const {namespace, action} = data;
18
18
  const route = `${namespace}/${action}`;
19
- const module = routes?.[namespace]?.[action] ?? Action;
20
- this.actions[route] = new module(action, this.session);
19
+ const Module = routes?.[namespace]?.[action] ?? Action;
20
+ this.actions[route] = new Module(action, this.session);
21
21
  const nextaction = this.actions[route];
22
22
  nextaction.enter(data);
23
23
  await this.Class.before(nextaction);
@@ -1,5 +1,5 @@
1
- const file_listener = event => reader.readAsDataURL(event.target.files[0]);
2
1
  const reader = new FileReader();
2
+ const file_listener = event => reader.readAsDataURL(event.target.files[0]);
3
3
 
4
4
  const replace = (attribute, source) => {
5
5
  if (attribute.includes(".")) {
@@ -20,11 +20,11 @@ const fulfill = (attribute, source) => {
20
20
  if (source === undefined) {
21
21
  return undefined;
22
22
  }
23
- let value = attribute.value;
23
+ let {value} = attribute;
24
24
  const matches = [...value.matchAll(data_regex)];
25
25
  if (matches.length > 0) {
26
26
  for (const match of matches) {
27
- const key = match[0];
27
+ const [key] = match;
28
28
  const new_value = replace(match[1], source);
29
29
  value = value.replace(key, new_value);
30
30
  }
@@ -34,7 +34,6 @@ const fulfill = (attribute, source) => {
34
34
  return value;
35
35
  };
36
36
 
37
-
38
37
  const $selector = Symbol("$selector");
39
38
  const $element = Symbol("$element");
40
39
  const $action = Symbol("$action");
@@ -47,13 +46,13 @@ const proxy_handler = {
47
46
  if (property === "on" || property === "insert") {
48
47
  return target[property].bind(target);
49
48
  }
50
- if (target.element !== null) {
51
- return (...args) => target[property] !== undefined
52
- ? target[property](...args)
53
- : receiver;
54
- } else {
49
+ if (target.element === null) {
55
50
  return () => receiver;
56
51
  }
52
+
53
+ return (...args) => target[property] === undefined
54
+ ? receiver
55
+ : target[property](...args);
57
56
  },
58
57
  };
59
58
 
@@ -145,7 +144,7 @@ export default class Element {
145
144
  class(classes, set) {
146
145
  classes.split(" ").forEach(name => {
147
146
  this.element.classList.toggle(name, set !== false);
148
- })
147
+ });
149
148
  return this;
150
149
  }
151
150
 
@@ -224,7 +223,7 @@ export default class Element {
224
223
  }
225
224
 
226
225
  input_file_value(value) {
227
- const element = this.element;
226
+ const {element} = this;
228
227
  if (value !== undefined) {
229
228
  element.setAttribute("base64", value);
230
229
  }
@@ -3,8 +3,6 @@
3
3
  import Element from "../Element.js";
4
4
  import Node from "./Node.js";
5
5
 
6
- const data_regex = /\${([^}]*)}/g;
7
-
8
6
  export default class View {
9
7
  constructor(data, action, root) {
10
8
  this.sources = {};
@@ -87,4 +85,5 @@ export default class View {
87
85
  write(pathname, payload) {
88
86
  return this.action.write(pathname, payload);
89
87
  }
88
+
90
89
  }
@@ -3,7 +3,7 @@ import {default as EagerPromise, eager} from "./EagerPromise.js";
3
3
  import View from "./view/View.js";
4
4
  import Projector from "./Projector.js";
5
5
  import InternalServerError from "./errors/InternalServer.js";
6
- import {assert} from "./invariants.js";
6
+ import {assert, defined} from "./invariants.js";
7
7
  import sanitize from "./sanitize.js";
8
8
 
9
9
  export default class Action {
@@ -20,7 +20,8 @@ export default class Action {
20
20
  static async new(request, session, context) {
21
21
  const {namespace = this.namespace, action = this.action} = request.path;
22
22
  assert(!namespace.includes("."), () => {
23
- throw new InternalServerError("namespace may not include a dot"); });
23
+ throw new InternalServerError("namespace may not include a dot");
24
+ });
24
25
  const route = `${namespace}/${action}`;
25
26
  const path = resolve(`${context.directory}/${route}.js`);
26
27
  try {
@@ -42,7 +43,7 @@ export default class Action {
42
43
  const {path} = this.request;
43
44
  const namespace = path.namespace ?? this.Class.namespace;
44
45
  const action = path.action ?? this.Class.action;
45
- const _id = path._id;
46
+ const {_id} = path;
46
47
  return {action, namespace, _id};
47
48
  }
48
49
 
@@ -51,6 +52,7 @@ export default class Action {
51
52
  }
52
53
 
53
54
  async run(type) {
55
+ defined(this[type]);
54
56
  this.before && await this.before();
55
57
  await this[type](sanitize(this.request.payload));
56
58
  }
@@ -82,7 +84,8 @@ export default class Action {
82
84
  const path = await eager`${this.context.directory}/${route}`;
83
85
  const _view = await View.new(path, await this.context.layouts);
84
86
  await this.context.Class.prepare(this, data);
85
- const payload = await new Projector(data, await _view.elements(this.layout)).project(route);
87
+ const payload = await new Projector(data,
88
+ await _view.elements(this.layout)).project(route);
86
89
  return {payload, _view};
87
90
  }
88
91
 
@@ -31,7 +31,7 @@ export default class App {
31
31
 
32
32
  this.router = new Router(await this.routes, this.conf);
33
33
  const {index, hashes} = await new this.Bundler(this.conf).bundle();
34
- const router = this.router;
34
+ const {router} = this;
35
35
 
36
36
  const conf = {index, hashes, router,
37
37
  "serve_from": this.conf.paths.public,
@@ -94,7 +94,7 @@ export default class Bundler {
94
94
  const index_html = await File.read(`${paths.public}/${this.index}`);
95
95
  await File.remove(`${paths.public}/${this.index}`);
96
96
 
97
- const body = "return `"+index_html+"`";
97
+ const body = `return \`${index_html}\``;
98
98
  const index = new Function("conf", "client", body)(this.conf, this.client);
99
99
 
100
100
  return {index, "hashes": this.hashes};
@@ -138,7 +138,7 @@ export default class Bundler {
138
138
  client += `<script type="module" integrity="${integrity}" src="${src}">`
139
139
  + "</script>\n";
140
140
  }
141
- return client + "<first-view />";
141
+ return `${client}<first-view />`;
142
142
  }
143
143
 
144
144
  async write_exports() {
@@ -2,7 +2,7 @@ import {resolve} from "path";
2
2
  import File from "./File.js";
3
3
  import Action from "./Action.js";
4
4
  import {InternalServerError} from "./errors.js";
5
- import {assert, defined} from "./invariants.js";
5
+ import {assert} from "./invariants.js";
6
6
  import cache from "./cache.js";
7
7
  import log from "./log.js";
8
8
 
@@ -87,7 +87,6 @@ export default class Context {
87
87
  const {type = "read"} = request;
88
88
  try {
89
89
  const action = await Action.new(request, session, this);
90
- defined(action[type]);
91
90
  await this.Class.before(action);
92
91
  await action.run(type);
93
92
  return action.response();
@@ -3,7 +3,7 @@ import {inconstructible_function} from "./attributes.js";
3
3
  const $promise = Symbol("#promise");
4
4
 
5
5
  const handler = {
6
- "get": function(target, property) {
6
+ "get": (target, property) => {
7
7
  const promise = target[$promise];
8
8
 
9
9
  if (["then", "catch"].includes(property)) {
@@ -15,7 +15,7 @@ export default class Projector {
15
15
  }
16
16
  this.documents = documents;
17
17
  this.projection = projection.reduce((sofar, domain) => {
18
- const key = Object.keys(domain)[0];
18
+ const [key] = Object.keys(domain);
19
19
  sofar[key] = domain[key];
20
20
  return sofar;
21
21
  }, {});
@@ -39,9 +39,9 @@ export default class Projector {
39
39
  }
40
40
 
41
41
  object(document, projection, key, fields) {
42
- return fields?.[key] !== undefined
43
- ? this.by_cache(fields[key], key, document, projection)
44
- : this.by_store(key, document, projection);
42
+ return fields?.[key] === undefined
43
+ ? this.by_store(key, document, projection)
44
+ : this.by_cache(fields[key], key, document, projection);
45
45
  }
46
46
 
47
47
  async one(document, projection) {
@@ -57,7 +57,7 @@ export default class Projector {
57
57
  if (typeof property === "string") {
58
58
  resolved[property] = await scalar(document, property);
59
59
  } else if (typeof property === "object") {
60
- const key = Object.keys(property)[0];
60
+ const [key] = Object.keys(property);
61
61
  resolved[key] = await this.object(document, property, key, fields);
62
62
  }
63
63
  }
@@ -68,17 +68,17 @@ export default class Projector {
68
68
  return this[Array.isArray(document) ? "many" : "one"](document, projection);
69
69
  }
70
70
 
71
- async project(route) {
71
+ async project() {
72
72
  return (await Promise.all(Object.keys(this.documents).map(async key => {
73
- const resolved = this.projection[key] !== undefined
74
- ? await this.resolve(this.documents[key], this.projection[key])
75
- : undefined;
73
+ const resolved = this.projection[key] === undefined
74
+ ? undefined
75
+ : await this.resolve(this.documents[key], this.projection[key]);
76
76
  return {key, resolved};
77
77
  }))).reduce((projected, {key, resolved}) => {
78
- if (resolved !== undefined) {
79
- projected[key] = resolved;
80
- } else {
78
+ if (resolved === undefined) {
81
79
  log.yellow(` \`${key}\` not projected`).nl();
80
+ } else {
81
+ projected[key] = resolved;
82
82
  }
83
83
  return projected;
84
84
  }, {});
@@ -4,7 +4,7 @@ export default class Router {
4
4
  this.routes = {};
5
5
  routes.forEach(({from, to, contexts}) => contexts.forEach(context => {
6
6
  this.routes[context] = this.routes[context] ?? [];
7
- this.routes[context].push({"from": new RegExp("^"+from+"$", "u"), to});
7
+ this.routes[context].push({"from": new RegExp(`^${from}$`, "u"), to});
8
8
  }));
9
9
  }
10
10
 
@@ -24,36 +24,29 @@ export default class Router {
24
24
  const [path, search = ""] = pathname.split("?");
25
25
 
26
26
  const route = this.route_by_context(context, path);
27
- if (route !== undefined) {
28
- const replace = path.replace(route.from, route.to);
29
- return `${replace}${replace.includes("?") ? "" : "?"}&${search}`;
30
- } else {
27
+ if (route === undefined) {
31
28
  return pathname;
32
29
  }
30
+
31
+ const replace = path.replace(route.from, route.to);
32
+ return `${replace}${replace.includes("?") ? "" : "?"}&${search}`;
33
33
  }
34
34
 
35
35
  resolve(pathname, context) {
36
- return this.context(context) !== undefined
37
- ? this.resolve_by_context(pathname, context)
38
- : pathname;
36
+ return this.context(context) === undefined
37
+ ? pathname
38
+ : this.resolve_by_context(pathname, context);
39
39
  }
40
40
 
41
41
  async route(pathname, context) {
42
42
  const resolved = await this.resolve(this.debase(pathname), context);
43
43
  const url = new URL(`https://primatejs.com/${resolved}`);
44
44
  const parts = url.pathname.split("/").filter(part => part !== "");
45
+ const [namespace, action, _id] = parts;
45
46
  return {
46
- "pathname": url.pathname,
47
- "resolved": resolved,
48
- "parts": parts,
49
- "path": {
50
- "namespace": parts[0],
51
- "action": parts[1],
52
- "_id": parts[2],
53
- },
54
- "params": {
55
- ...Object.fromEntries(url.searchParams),
56
- },
47
+ "pathname": url.pathname, resolved, parts,
48
+ "path": {namespace, action, _id},
49
+ "params": {...Object.fromEntries(url.searchParams)},
57
50
  };
58
51
  }
59
52
  }
@@ -11,3 +11,4 @@ export const inconstructible_function = value =>
11
11
  typeof value === "function" && !constructible(value);
12
12
  export const numeric = value => !isNaN(parseFloat(value)) && isFinite(value);
13
13
  export const boolish = value => value === "true" || value === "false";
14
+ export const nullish = value => value === undefined || value === null;
@@ -1,28 +1,30 @@
1
- import * as types from "../types.js";
2
1
  import DomainType from "../types/Domain.js";
3
- import Storeable from "../types/Storeable.js";
2
+ import Predicate from "./Predicate.js";
4
3
  import {PredicateError} from "../errors.js";
5
- import {defined, is_array, instances, is_constructible} from "../invariants.js";
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 parse = field => constructible(field)
14
- ? {"type": field}
15
- : as_non_constructible(field);
15
+ const as_array = field => ({"type": field[0], "predicates": field.slice(1)});
16
16
 
17
- const as_non_constructible =
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 as_object = field => field instanceof Array ? as_array(field) : field;
22
+ const as_non_constructible =
23
+ field => typeof field === "function" ? as_function(field) : as_object(field);
24
24
 
25
- const as_array = field => ({"type": field[0], "predicates": field.slice(1)});
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
- is_constructible(this.Type);
34
- instances(this.type.prototype, Storeable, "type must extend Storeable");
35
- is_array(this.predicates);
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) {
@@ -57,12 +59,15 @@ export default class Field {
57
59
  return this.Type.prototype instanceof DomainType.instance;
58
60
  }
59
61
 
60
- get Type(){
62
+ get Type() {
61
63
  return this.definition.type;
62
64
  }
63
65
 
64
66
  get predicates() {
65
- return this.definition.predicates ?? [];
67
+ return cache(this, "predicates", () => {
68
+ const predicates = this.definition.predicates ?? [];
69
+ return predicates.map(name => new Predicate(name));
70
+ });
66
71
  }
67
72
 
68
73
  by_id(id) {
@@ -75,23 +80,13 @@ export default class Field {
75
80
  return in_function !== undefined ? in_function(value, document) : value;
76
81
  }
77
82
 
78
- override_predicates(document) {
79
- return this.predicates.map(predicate => {
80
- const [name, ...params] = predicate.split(":");
81
- return document[name] !== undefined
82
- ? {"function": document[name].bind(document), params}
83
- : predicate;
84
- });
85
- }
86
-
87
83
  verify_undefined() {
88
84
  return this.options.optional ? true : "Must not be empty";
89
85
  }
90
86
 
91
87
  async verify_defined(property, document) {
92
88
  try {
93
- const predicates = this.override_predicates(document);
94
- await this.type.verify(property, document, predicates, this.Type);
89
+ await this.type.verify(property, document, this.predicates, this.Type);
95
90
  return true;
96
91
  } catch (error) {
97
92
  if (error instanceof PredicateError) {
@@ -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 +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 {}
@@ -14,11 +14,11 @@ export {default as domains} from "./domain/domains.js";
14
14
  export {default as Storeable} from "./types/Storeable.js";
15
15
 
16
16
  export * from "./errors.js";
17
+ export * from "./invariants.js";
17
18
 
18
19
  export {default as MemoryStore} from "./store/Memory.js";
19
20
  export {default as Store} from "./store/Store.js";
20
21
 
21
- export {assert, defined} from "./invariants.js";
22
22
  export {default as log} from "./log.js";
23
23
  export {default as extend_object} from "./extend_object.js";
24
24
  export {default as sanitize} from "./sanitize.js";
@@ -1,17 +1,32 @@
1
- import {constructible} from "./attributes.js";
1
+ import {constructible, nullish} from "./attributes.js";
2
2
 
3
3
  const errored = error => {
4
- if (typeof error === "function") { // fallback
4
+ if (typeof error === "function") {
5
+ // fallback
5
6
  error();
6
- } else { // error
7
+ } else {
8
+ // error
7
9
  throw new Error(error);
8
10
  }
9
11
  };
10
12
 
11
13
  const assert = (predicate, error) => Boolean(predicate) || errored(error);
12
- const defined = (value, error) => assert(value !== undefined, error);
13
- const is_array = value => assert(Array.isArray(value), "must be array");
14
- const instances = (sub, parent, error) => assert(sub instanceof parent, error);
15
- const is_constructible = (value, error) => assert(constructible(value), error);
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
+ }, {});
16
31
 
17
- export {assert, defined, is_array, instances, is_constructible};
32
+ export {assert, defined, is, maybe};
@@ -1,5 +1,10 @@
1
1
  export default (payload = {}) => Object.keys(payload)
2
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; }, {});
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
+ }, {});
@@ -23,24 +23,24 @@ export default class DynamicServer extends Server {
23
23
  const {session} = socket;
24
24
  const request = JSON.parse(event);
25
25
  try {
26
- await this.onmessage(request, socket);
26
+ await this.serve(request, socket);
27
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(request, socket) {
33
+ async serve(request, socket) {
34
34
  const {router} = this.conf;
35
35
  const {session} = socket;
36
36
  const {path, params} = await session.route(router, request.url);
37
37
  const response = await session.run({...request, path, params});
38
38
  const {type} = response;
39
39
  return this[type]?.(socket, response)
40
- ?? DynamicServer.send(socket, response);
40
+ ?? DynamicServer.serve(socket, response);
41
41
  }
42
42
 
43
- static send(socket, response) {
43
+ static serve(socket, response) {
44
44
  socket.send(JSON.stringify(response));
45
45
  }
46
46
 
@@ -1,4 +1,4 @@
1
- export default class StaticServer {
1
+ export default class Server {
2
2
  constructor(conf) {
3
3
  this.conf = conf;
4
4
  }
@@ -84,7 +84,7 @@ export default class StaticServer extends Server {
84
84
  const integrity = `${algorithm}-${hash(src)}`;
85
85
  const view = `<script type="module" integrity="${integrity}">${src}`
86
86
  + "</script>";
87
- const file = index.replace("<first-view />", view);
87
+ const file = index.replace("<first-view />", () => view);
88
88
  const script_src = Array.from(hashes)
89
89
  .concat([integrity])
90
90
  .reduce((hash_string, next_hash) => hash_string + ` '${next_hash}'`, "");
@@ -1,7 +1,7 @@
1
- import Instance from "./Instance.js";
1
+ import InstanceType from "./Instance.js";
2
2
  import errors from "./errors/Array.json" assert {"type": "json"};
3
3
 
4
- export default class extends Instance {
4
+ export default class ArrayType extends InstanceType {
5
5
  static get instance() {
6
6
  return Array;
7
7
  }
@@ -1,8 +1,8 @@
1
- import Primitive from "./Primitive.js";
1
+ import PrimitiveType from "./Primitive.js";
2
2
  import {boolish} from "../attributes.js";
3
3
  import errors from "./errors/Boolean.json" assert {"type": "json"};
4
4
 
5
- export default class extends Primitive {
5
+ export default class BooleanType extends PrimitiveType {
6
6
  static get type() {
7
7
  return "boolean";
8
8
  }
@@ -1,7 +1,7 @@
1
- import Instance from "./Instance.js";
1
+ import InstanceType from "./Instance.js";
2
2
  import errors from "./errors/Date.json" assert {"type": "json"};
3
3
 
4
- export default class extends Instance {
4
+ export default class DateType extends InstanceType {
5
5
  static get instance() {
6
6
  return Date;
7
7
  }
@@ -1,6 +1,6 @@
1
1
  import Storeable from "./Storeable.js";
2
2
 
3
- export default class extends Storeable {
3
+ export default class DomainType extends Storeable {
4
4
  static type_error({name}) {
5
5
  return `Must be a ${name}`;
6
6
  }
@@ -1,6 +1,6 @@
1
1
  import Storeable from "./Storeable.js";
2
2
 
3
- export default class extends Storeable {
3
+ export default class InstanceType extends Storeable {
4
4
  static is(value) {
5
5
  // no subclassing allowed, as [] instanceof Object === true et al.
6
6
  return value?.constructor === this.instance;
@@ -1,8 +1,8 @@
1
- import Primitive from "./Primitive.js";
1
+ import PrimitiveType from "./Primitive.js";
2
2
  import {numeric} from "../attributes.js";
3
3
  import errors from "./errors/Number.json" assert {"type": "json"};
4
4
 
5
- export default class extends Primitive {
5
+ export default class NumberType extends PrimitiveType {
6
6
  static get type() {
7
7
  return "number";
8
8
  }
@@ -1,7 +1,7 @@
1
- import Instance from "./Instance.js";
1
+ import InstanceType from "./Instance.js";
2
2
  import errors from "./errors/Object.json" assert {"type": "json"};
3
3
 
4
- export default class extends Instance {
4
+ export default class ObjectType extends InstanceType {
5
5
  static get instance() {
6
6
  return Object;
7
7
  }
@@ -1,6 +1,6 @@
1
1
  import Storeable from "./Storeable.js";
2
2
 
3
- export default class extends Storeable {
3
+ export default class PrimitiveType extends Storeable {
4
4
  static is(value) {
5
5
  return typeof value === this.type;
6
6
  }
@@ -1,12 +1,13 @@
1
1
  import {PredicateError} from "../errors.js";
2
2
 
3
- export default class {
3
+ export default class Storeable {
4
4
  static async verify(property, document, predicates, type) {
5
5
  document[property] = this.coerce(document[property]);
6
6
  if (!await this.is(document[property], type)) {
7
7
  throw new PredicateError(this.type_error(type));
8
8
  }
9
- await this.has(property, document, predicates);
9
+ await Promise.all(predicates.map(predicate =>
10
+ predicate.check(property, document, this)));
10
11
  }
11
12
 
12
13
  static type_error() {
@@ -17,20 +18,13 @@ export default class {
17
18
  throw new Error("must be implemented");
18
19
  }
19
20
 
20
- static async has(property, document, predicates) {
21
- for (const predicate of predicates) {
22
- if (typeof predicate === "object") {
23
- await predicate.function(property, ...predicate.params);
24
- } else {
25
- const [name, ...params] = predicate.split(":");
26
- if (!this[name](document[property], ...params)) {
27
- let error = this.errors[name];
28
- for (let i = 0; i < params.length; i++) {
29
- error = error.replace(`$${i+1}`, params[i]);
30
- }
31
- throw new PredicateError(error);
32
- }
21
+ static async has(name, value, params) {
22
+ if (!this[name](value, ...params)) {
23
+ let error = this.errors[name];
24
+ for (let i = 0; i < params.length; i++) {
25
+ error = error.replace(`$${i+1}`, () => params[i]);
33
26
  }
27
+ throw new PredicateError(error);
34
28
  }
35
29
  }
36
30
 
@@ -1,7 +1,7 @@
1
- import Primitive from "./Primitive.js";
1
+ import PrimitiveType from "./Primitive.js";
2
2
  import errors from "./errors/String.json" assert {"type": "json"};
3
3
 
4
- export default class extends Primitive {
4
+ export default class StringType extends PrimitiveType {
5
5
  static get type() {
6
6
  return "string";
7
7
  }
@@ -30,6 +30,6 @@ export default class View {
30
30
  }
31
31
 
32
32
  file(layout = "default") {
33
- return this.layout(layout).replace($content, this.content);
33
+ return this.layout(layout).replace($content, () => this.content);
34
34
  }
35
35
  }