primate 0.3.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +10 -7
  2. package/debris.json +5 -0
  3. package/jsconfig.json +8 -0
  4. package/package.json +16 -4
  5. package/source/client/Action.js +20 -22
  6. package/source/client/Client.js +13 -17
  7. package/source/client/Context.js +10 -16
  8. package/source/client/Element.js +18 -14
  9. package/source/client/Session.js +27 -0
  10. package/source/client/View.js +1 -2
  11. package/source/client/document.js +1 -1
  12. package/source/preset/primate.json +2 -8
  13. package/source/server/Action.js +57 -75
  14. package/source/server/App.js +27 -8
  15. package/source/server/Bundler.js +13 -16
  16. package/source/server/Context.js +63 -56
  17. package/source/server/{promises/Eager.js → EagerPromise.js} +4 -2
  18. package/source/server/File.js +5 -1
  19. package/source/server/Projector.js +86 -0
  20. package/source/server/Router.js +15 -24
  21. package/source/server/Session.js +9 -33
  22. package/source/server/attributes.js +3 -0
  23. package/source/server/conf.js +20 -26
  24. package/source/server/{Crypto.js → crypto.js} +0 -0
  25. package/source/server/domain/Domain.js +56 -46
  26. package/source/server/domain/Field.js +34 -36
  27. package/source/server/domain/Predicate.js +24 -0
  28. package/source/server/domain/domains.js +17 -1
  29. package/source/server/errors/InternalServer.js +1 -1
  30. package/source/server/errors/Predicate.js +1 -1
  31. package/source/server/errors.js +0 -1
  32. package/source/server/exports.js +10 -9
  33. package/source/server/{utils/extend_object.js → extend_object.js} +0 -0
  34. package/source/server/invariants.js +23 -10
  35. package/source/server/sanitize.js +10 -0
  36. package/source/server/servers/Dynamic.js +19 -13
  37. package/source/server/servers/Server.js +1 -1
  38. package/source/server/servers/Static.js +25 -20
  39. package/source/server/store/Store.js +13 -0
  40. package/source/server/types/Array.js +2 -2
  41. package/source/server/types/Boolean.js +2 -2
  42. package/source/server/types/Date.js +2 -2
  43. package/source/server/types/Domain.js +1 -6
  44. package/source/server/types/Instance.js +1 -1
  45. package/source/server/types/Number.js +2 -2
  46. package/source/server/types/Object.js +2 -2
  47. package/source/server/types/Primitive.js +1 -1
  48. package/source/server/types/Storeable.js +12 -19
  49. package/source/server/types/String.js +2 -2
  50. package/source/server/view/TreeNode.js +2 -0
  51. package/source/server/view/View.js +6 -1
  52. package/source/client/Base.js +0 -5
  53. package/source/server/Base.js +0 -35
  54. package/source/server/errors/Fallback.js +0 -1
  55. package/source/server/fallback.js +0 -11
  56. package/source/server/promises/Meta.js +0 -42
  57. package/source/server/promises.js +0 -2
@@ -1,47 +1,37 @@
1
1
  import Context from "./Context.js";
2
2
  import Domain from "./domain/Domain.js";
3
- import {FallbackError, InternalServerError} from "./errors.js";
4
3
 
5
- const extract_id = cookie_header => cookie_header === undefined
6
- ? undefined
7
- : cookie_header
8
- .split(";")
9
- .filter(cookie => cookie.includes("session_id="))[0]
10
- ?.split("=")[1];
4
+ const extract_id = cookie_header => cookie_header
5
+ ?.split(";").filter(text => text.includes("session_id="))[0]?.split("=")[1];
11
6
 
12
7
  export default class Session extends Domain {
13
8
  static get fields() {
14
- const default_context = this.conf.defaults.context;
15
9
  return {
16
10
  "?data": Object,
17
- "context" : value => value ?? default_context,
11
+ "context" : String,
18
12
  "created": value => value ?? new Date(),
19
13
  };
20
14
  }
21
15
 
22
16
  async log(color, message) {
23
- (await this.actual_context).log(color, message);
17
+ (await Context.get(this.context)).log(color, message);
24
18
  }
25
19
 
26
20
  route(router, url) {
27
21
  return router.route(url, this.context);
28
22
  }
29
23
 
30
- get actual_context() {
31
- return Context.get(this.context);
32
- }
33
-
34
- static async get(cookie_header) {
24
+ static async get(cookie_header, default_context) {
35
25
  const session = await Session.touch({"_id": extract_id(cookie_header)});
36
26
  if (session.new) {
37
- await session.save();
27
+ await session.save({"context": default_context});
38
28
  session.has_cookie = false;
39
29
  }
40
30
  return session;
41
31
  }
42
32
 
43
33
  get cookie() {
44
- return `session_id=${this._id}; Secure; HttpOnly; Path=/`;
34
+ return `session_id=${this._id}; Secure; SameSite=Strict; HttpOnly; Path=/`;
45
35
  }
46
36
 
47
37
  async switch_context(context, data = {}) {
@@ -49,21 +39,7 @@ export default class Session extends Domain {
49
39
  return this;
50
40
  }
51
41
 
52
- async run(data) {
53
- let response, context;
54
- try {
55
- context = await this.actual_context;
56
- response = await context.run(data, this);
57
- } catch (error) {
58
- if (error instanceof FallbackError) {
59
- // do nothing
60
- } else if (error instanceof InternalServerError) {
61
- // cannot proceed, throw up
62
- throw error;
63
- } else {
64
- return context.errored(data, error, this);
65
- }
66
- }
67
- return response;
42
+ async run(request) {
43
+ return (await Context.get(this.context)).run(request, this);
68
44
  }
69
45
  }
@@ -7,5 +7,8 @@ export const constructible = value => {
7
7
  }
8
8
  };
9
9
 
10
+ export const inconstructible_function = value =>
11
+ typeof value === "function" && !constructible(value);
10
12
  export const numeric = value => !isNaN(parseFloat(value)) && isFinite(value);
11
13
  export const boolish = value => value === "true" || value === "false";
14
+ export const nullish = value => value === undefined || value === null;
@@ -1,33 +1,27 @@
1
1
  import {join, resolve} from "path";
2
- import log from "./log.js";
3
2
  import cache from "./cache.js";
4
3
  import File from "./File.js";
5
- import extend_object from "./utils/extend_object.js";
4
+ import extend_object from "./extend_object.js";
6
5
  import primate_json from "../preset/primate.json" assert {"type": "json" };
7
6
 
8
- const qualify = (root, paths) => {
9
- const object = {};
10
- for (const key in paths) {
7
+ const qualify = (root, paths) =>
8
+ Object.keys(paths).reduce((sofar, key) => {
11
9
  const value = paths[key];
12
- if (typeof value === "string") {
13
- object[key] = join(root, value);
14
- } else {
15
- object[key] = qualify(`${root}/${key}`, value);
16
- }
17
- }
18
- return object;
19
- }
10
+ sofar[key] = typeof value === "string"
11
+ ? join(root, value)
12
+ : qualify(`${root}/${key}`, value);
13
+ return sofar;
14
+ }, {});
20
15
 
21
- export default (file = "primate.json") =>
22
- cache("conf", file, () => {
23
- const root = resolve();
24
- let conf = primate_json;
25
- try {
26
- conf = extend_object(conf, JSON.parse(File.read_sync(join(root, file))));
27
- } catch (error) {
28
- // local primate.json not required
29
- }
30
- conf.paths = qualify(root, conf.paths);
31
- conf.root = root;
32
- return conf
33
- });
16
+ export default (file = "primate.json") => cache("conf", file, () => {
17
+ let conf = primate_json;
18
+ const root = resolve();
19
+ try {
20
+ conf = extend_object(conf, JSON.parse(File.read_sync(join(root, file))));
21
+ } catch (error) {
22
+ // local primate.json not required
23
+ }
24
+ conf.paths = qualify(root, conf.paths);
25
+ conf.root = root;
26
+ return conf;
27
+ });
File without changes
@@ -1,26 +1,34 @@
1
- import Base from "../Base.js";
1
+ import {resolve as path_resolve} from "path";
2
2
  import Field from "./Field.js";
3
3
  import {PredicateError} from "../errors.js";
4
- import {EagerPromise} from "../promises.js";
4
+ import EagerPromise from "../EagerPromise.js";
5
+ import Store from "../store/Store.js";
5
6
  import cache from "../cache.js";
6
- import {random} from "../Crypto.js";
7
+ import DomainType from "../types/Domain.js";
8
+ import {random} from "../crypto.js";
7
9
 
8
10
  const length = 12;
9
- const preset = "../../preset/data/stores";
10
11
 
11
- const get = (target, property, receiver) =>
12
- 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";
13
15
 
14
- export default class Domain extends Base {
15
- constructor(document) {
16
- super();
16
+ static {
17
+ // avoid transitive cyclic dependency between Domain and Field
18
+ DomainType.instance = Domain;
19
+ this.cache = {};
20
+ }
17
21
 
22
+ constructor(document) {
18
23
  const errors = {};
19
- return new Proxy(this, {get}).set({...document, errors}).define("_id", {
24
+ this.define("_id", {
20
25
  "type": String,
21
26
  "predicates": ["unique"],
22
27
  "in": value => value ?? random(length).toString("hex"),
23
28
  });
29
+ return new Proxy(this, {"get": (target, property, receiver) =>
30
+ Reflect.get(target, property, receiver) ?? target.#proxy(property)
31
+ }).set({...document, errors})
24
32
  }
25
33
 
26
34
  get Class() {
@@ -39,20 +47,9 @@ export default class Domain extends Base {
39
47
  }
40
48
 
41
49
  static get store() {
42
- return cache(this, "store", async () => {
43
- const create_path = path => `${path}/${this.store_file}`;
44
- let store;
45
- try {
46
- store = await import(create_path(this.conf.paths.data.stores));
47
- } catch(error) {
48
- store = await import(create_path(preset));
49
- }
50
- return store.default.open();
51
- });
52
- }
53
-
54
- static get store_file() {
55
- return this.conf.defaults.store;
50
+ return EagerPromise.resolve(cache(this, "store", async () =>
51
+ Store.get(this.stores_directory, this.store_file)
52
+ ));
56
53
  }
57
54
 
58
55
  static get collection() {
@@ -97,13 +94,27 @@ export default class Domain extends Base {
97
94
  return Object.assign(this, document);
98
95
  }
99
96
 
100
- // #as_foreign
101
- as_foreign(name) {
97
+ #proxy(property) {
98
+ return typeof property === "string" ? this.#link(property) : this[property];
99
+ }
100
+
101
+ #link(name) {
102
102
  const field = this.fields[`${name}_id`];
103
- 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
+ }
104
116
  }
105
117
 
106
- // #serialize
107
118
  // Serializing is done from the instance's point of view.
108
119
  async serialize() {
109
120
  const {properties, fields} = this;
@@ -116,7 +127,6 @@ export default class Domain extends Base {
116
127
  }, {});
117
128
  }
118
129
 
119
- // #deserialize
120
130
  // Deserializing is done from the class's point of view.
121
131
  static deserialize(serialized) {
122
132
  const fields = this._fields;
@@ -138,6 +148,7 @@ export default class Domain extends Base {
138
148
  get collection() {
139
149
  return this.Class.collection;
140
150
  }
151
+
141
152
  get properties() {
142
153
  return this.Class.properties;
143
154
  }
@@ -171,9 +182,12 @@ export default class Domain extends Base {
171
182
  async savewith(delta, after = () => undefined) {
172
183
  const verified = await this.verify(delta);
173
184
  if (verified) {
174
- const store = await this.store;
175
185
  const document = await this.serialize();
176
- await store.save(this.collection, {"_id": document._id}, document);
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
+ }
177
191
  await after();
178
192
  }
179
193
  return verified;
@@ -197,26 +211,24 @@ export default class Domain extends Base {
197
211
  return this;
198
212
  }
199
213
 
200
- async delete() {
201
- const store = await this.store;
202
- return store.delete(this.collection, {"_id": this._id});
214
+ delete() {
215
+ return this.store.delete(this.collection, {"_id": this._id});
203
216
  }
204
217
 
205
- static async delete(criteria) {
206
- const store = await this.store;
207
- return store.delete(this.collection, criteria);
218
+ static delete(criteria) {
219
+ return this.store.delete(this.collection, criteria);
208
220
  }
209
221
 
210
222
  static by_id(_id) {
211
223
  return new EagerPromise(async resolve => {
212
- const result = await (await this.store).one(this.collection, _id);
213
- 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);
214
226
  });
215
227
  }
216
228
 
217
- static first(criteria) {
229
+ static first(criteria, options) {
218
230
  return new EagerPromise(async resolve => {
219
- const result = await (await this.store).one(this.collection, criteria);
231
+ const result = await this.store.one(this.collection, criteria, options);
220
232
  resolve(result === undefined ? undefined : this.deserialize(result));
221
233
  });
222
234
  }
@@ -230,14 +242,12 @@ export default class Domain extends Base {
230
242
  }
231
243
 
232
244
  static async find(criteria, options) {
233
- const store = await this.store;
234
- const results = await store.find(this.collection, criteria, options);
245
+ const results = await this.store.find(this.collection, criteria, options);
235
246
  return results.map(result => this.deserialize(result));
236
247
  }
237
248
 
238
- static async count(criteria) {
239
- const store = await this.store;
240
- return store.count(this.collection, criteria);
249
+ static count(criteria) {
250
+ return this.store.count(this.collection, criteria);
241
251
  }
242
252
 
243
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 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.definition.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) {
@@ -46,23 +48,30 @@ export default class Field {
46
48
  }
47
49
 
48
50
  get type() {
49
- return builtins[this.definition.type] ?? this.custom;
51
+ return builtins[this.Type] ?? this.custom;
50
52
  }
51
53
 
52
54
  get custom() {
53
- return this.is_domain ? DomainType : this.definition.type;
55
+ return this.is_domain ? DomainType : this.Type;
54
56
  }
55
57
 
56
58
  get is_domain() {
57
- return this.definition.type.prototype instanceof DomainType.instance;
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.definition.predicates ?? [];
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.definition.type.by_id(id);
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, value, document) {
87
+ async verify_defined(property, document) {
88
88
  try {
89
- const predicates = this.override_predicates(document);
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
- const value = await this.in(property, document);
103
- return value === undefined
100
+ document[property] = await this.in(property, document);
101
+ return document[property] === undefined
104
102
  ? this.verify_undefined()
105
- : this.verify_defined(property, value, document);
103
+ : this.verify_defined(property, document);
106
104
  }
107
105
 
108
106
  serialize(value) {
109
- return value !== undefined ? this.type.serialize(value) : value;
107
+ return value === undefined ? undefined : this.type.serialize(value);
110
108
  }
111
109
 
112
110
  deserialize(value) {
113
- return value !== undefined ? this.type.deserialize(value) : 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,9 +1,10 @@
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
- const base = conf().paths.data.domains;
7
+ const base = conf().paths.domains;
7
8
 
8
9
  for (const domain of await new File(base).list(".js")) {
9
10
  const name = domain.slice(0, -3);
@@ -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 {}
@@ -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" ;
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 "./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,19 +1,32 @@
1
- import {FallbackError} from "./errors.js";
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") { // fallback
4
+ if (typeof error === "function") {
5
+ // fallback
6
6
  error();
7
- throw new FallbackError();
8
- } else { // error
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 defined = (value, error) => assert(value !== undefined, error);
15
- const is_array = value => assert(Array.isArray(value), "must be array");
16
- const instances = (sub, parent, error) => assert(sub instanceof parent, error);
17
- 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
+ }, {});
18
31
 
19
- export {assert, defined, is_array, instances, is_constructible};
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
+ }, {});