primate 0.0.1 → 0.3.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 (75) hide show
  1. package/LICENSE +27 -0
  2. package/README.md +50 -0
  3. package/package.json +15 -1
  4. package/source/client/Action.js +159 -0
  5. package/source/client/App.js +16 -0
  6. package/source/client/Base.js +5 -0
  7. package/source/client/Client.js +65 -0
  8. package/source/client/Context.js +53 -0
  9. package/source/client/Element.js +245 -0
  10. package/source/client/Node.js +13 -0
  11. package/source/client/View.js +90 -0
  12. package/source/client/document.js +6 -0
  13. package/source/client/exports.js +15 -0
  14. package/source/preset/client/Element.js +2 -0
  15. package/source/preset/client/app.js +2 -0
  16. package/source/preset/data/stores/default.js +2 -0
  17. package/source/preset/primate.json +31 -0
  18. package/source/preset/static/index.html +10 -0
  19. package/source/server/Action.js +118 -0
  20. package/source/server/App.js +42 -0
  21. package/source/server/Base.js +35 -0
  22. package/source/server/Bundler.js +180 -0
  23. package/source/server/Context.js +90 -0
  24. package/source/server/Crypto.js +8 -0
  25. package/source/server/Directory.js +35 -0
  26. package/source/server/File.js +117 -0
  27. package/source/server/Router.js +61 -0
  28. package/source/server/Session.js +69 -0
  29. package/source/server/attributes.js +11 -0
  30. package/source/server/cache.js +17 -0
  31. package/source/server/conf.js +33 -0
  32. package/source/server/domain/Domain.js +277 -0
  33. package/source/server/domain/Field.js +115 -0
  34. package/source/server/domain/domains.js +15 -0
  35. package/source/server/errors/Fallback.js +1 -0
  36. package/source/server/errors/InternalServer.js +1 -0
  37. package/source/server/errors/Predicate.js +1 -0
  38. package/source/server/errors.js +3 -0
  39. package/source/server/exports.js +27 -0
  40. package/source/server/fallback.js +11 -0
  41. package/source/server/invariants.js +19 -0
  42. package/source/server/log.js +22 -0
  43. package/source/server/promises/Eager.js +49 -0
  44. package/source/server/promises/Meta.js +42 -0
  45. package/source/server/promises.js +2 -0
  46. package/source/server/servers/Dynamic.js +51 -0
  47. package/source/server/servers/Server.js +5 -0
  48. package/source/server/servers/Static.js +113 -0
  49. package/source/server/servers/content-security-policy.json +8 -0
  50. package/source/server/servers/http-codes.json +5 -0
  51. package/source/server/servers/mimes.json +12 -0
  52. package/source/server/store/Memory.js +60 -0
  53. package/source/server/store/Store.js +17 -0
  54. package/source/server/types/Array.js +33 -0
  55. package/source/server/types/Boolean.js +29 -0
  56. package/source/server/types/Date.js +20 -0
  57. package/source/server/types/Domain.js +16 -0
  58. package/source/server/types/File.js +20 -0
  59. package/source/server/types/Instance.js +8 -0
  60. package/source/server/types/Number.js +45 -0
  61. package/source/server/types/Object.js +12 -0
  62. package/source/server/types/Primitive.js +7 -0
  63. package/source/server/types/Storeable.js +51 -0
  64. package/source/server/types/String.js +49 -0
  65. package/source/server/types/errors/Array.json +7 -0
  66. package/source/server/types/errors/Boolean.json +5 -0
  67. package/source/server/types/errors/Date.json +3 -0
  68. package/source/server/types/errors/Number.json +9 -0
  69. package/source/server/types/errors/Object.json +3 -0
  70. package/source/server/types/errors/String.json +11 -0
  71. package/source/server/types.js +7 -0
  72. package/source/server/utils/extend_object.js +10 -0
  73. package/source/server/view/Parser.js +122 -0
  74. package/source/server/view/TreeNode.js +195 -0
  75. package/source/server/view/View.js +30 -0
@@ -0,0 +1,117 @@
1
+ import fs from "fs";
2
+ import {join} from "path";
3
+ import Directory from "./Directory.js";
4
+ import EagerPromise from "./promises/Eager.js";
5
+
6
+ const array = maybe => Array.isArray(maybe) ? maybe : [maybe];
7
+
8
+ const filter_files = (files, filter) =>
9
+ files.filter(file => array(filter).some(ending => file.endsWith(ending)));
10
+
11
+ export default class File {
12
+ constructor(...args) {
13
+ this.path = join(...args);
14
+ return EagerPromise.resolve(new Promise(resolve => {
15
+ fs.lstat(this.path, (error, stats) => {
16
+ this.stats = stats;
17
+ resolve(this);
18
+ });
19
+ }));
20
+ }
21
+
22
+ get modified() {
23
+ return Math.round(this.stats.mtimeMs);
24
+ }
25
+
26
+ get exists() {
27
+ return this.stats !== undefined;
28
+ }
29
+
30
+ get is_file() {
31
+ return this.exists && !this.stats.isDirectory();
32
+ }
33
+
34
+ get read_stream() {
35
+ return fs.createReadStream(this.path, {"flags": "r"});
36
+ }
37
+
38
+ get write_stream() {
39
+ return fs.createWriteStream(this.path);
40
+ }
41
+
42
+ remove() {
43
+ return new Promise((resolve, reject) => fs.rm(this.path,
44
+ {"recursive": true, "force": true},
45
+ error => error === null ? resolve(this) : reject(error)
46
+ ));
47
+ }
48
+
49
+ create() {
50
+ return new Promise((resolve, reject) => fs.mkdir(this.path, error =>
51
+ error === null ? resolve(this) : reject(error)
52
+ ));
53
+ }
54
+
55
+ async copy(to, recreate = true) {
56
+ if (this.stats.isDirectory()) {
57
+ if (recreate) {
58
+ await new File(`${to}`).recreate();
59
+ }
60
+ // copy all files
61
+ return Promise.all((await this.list()).map(file =>
62
+ new File(`${this.path}/${file}`).copy(`${to}/${file}`)
63
+ ));
64
+ } else {
65
+ return new Promise((resolve, reject) => fs.copyFile(this.path, to,
66
+ error => error === null ? resolve(this) : reject(error)));
67
+ }
68
+ }
69
+
70
+ async list(filter) {
71
+ if (!this.exists) {
72
+ return [];
73
+ }
74
+ const files = await Directory.list(this.path);
75
+ return filter !== undefined ? filter_files(files, filter) : files;
76
+ }
77
+
78
+ async recreate() {
79
+ return (await this.remove()).create();
80
+ }
81
+
82
+ read(options = {"encoding": "utf8"}) {
83
+ return new Promise((resolve, reject) =>
84
+ fs.readFile(this.path, options, (error, nonerror) =>
85
+ error === null ? resolve(nonerror) : reject(error)));
86
+ }
87
+
88
+ write(data, options = {"encoding": "utf8"}) {
89
+ return new Promise((resolve, reject) => fs.writeFile(this.path, data,
90
+ options,
91
+ error => error === null ? resolve(this) : reject(error)));
92
+ }
93
+
94
+ static read_sync(path, options = {"encoding": "utf8"}) {
95
+ return fs.readFileSync(path, options);
96
+ }
97
+
98
+ static exists(...args) {
99
+ return new File(...args).exists;
100
+ }
101
+
102
+ static read(...args) {
103
+ return new File(...args).read();
104
+ }
105
+
106
+ static write(path, data, options) {
107
+ return new File(path).write(data, options);
108
+ }
109
+
110
+ static remove(...args) {
111
+ return new File(...args).remove();
112
+ }
113
+
114
+ static copy(from, to, recreate) {
115
+ return new File(from).copy(to, recreate);
116
+ }
117
+ }
@@ -0,0 +1,61 @@
1
+ export default class Router {
2
+ constructor(routes = [], conf) {
3
+ this.conf = conf;
4
+ this.routes = {};
5
+ routes.forEach(({from, to, contexts}) =>
6
+ contexts.forEach(context => {
7
+ this.routes[context] = this.routes[context] ?? [];
8
+ this.routes[context].push({"from": new RegExp("^"+from+"$", "u"), to});
9
+ }));
10
+ }
11
+
12
+ context(context) {
13
+ return context ?? this.conf.defaults.context;
14
+ }
15
+
16
+ debase(pathname) {
17
+ return pathname.replace(this.conf.base, "/");
18
+ }
19
+
20
+ route_by_context(context, path) {
21
+ return this.routes[context]?.find(({from}) => from.test(path)) ?? [];
22
+ }
23
+
24
+ resolve_by_context(pathname, context) {
25
+ const [path, search = ""] = pathname.split("?");
26
+
27
+ const route = this.route_by_context(context, path);
28
+ if (route !== undefined) {
29
+ const replace = path.replace(route.from, route.to);
30
+ return `${replace}${replace.includes("?") ? "&" : "?&"}${search}`;
31
+ } else {
32
+ return pathname;
33
+ }
34
+ }
35
+
36
+ resolve(pathname, context) {
37
+ return this.context(context) !== undefined
38
+ ? this.resolve_by_context(pathname, context)
39
+ : pathname;
40
+ }
41
+
42
+ async route(pathname, context) {
43
+ const resolved = await this.resolve(this.debase(pathname), context);
44
+ const url = new URL(`https://primatejs.com/${resolved}`);
45
+ const parts = url.pathname.split("/").filter(part => part !== "");
46
+ const {namespace, action} = this.conf.defaults;
47
+ return {
48
+ "pathname": url.pathname,
49
+ "resolved": resolved,
50
+ "parts": parts,
51
+ "path": {
52
+ "namespace": parts[0] ?? namespace,
53
+ "action": parts[1] ?? action,
54
+ "_id": parts[2],
55
+ },
56
+ "params": {
57
+ ...Object.fromEntries(url.searchParams),
58
+ },
59
+ };
60
+ }
61
+ }
@@ -0,0 +1,69 @@
1
+ import Context from "./Context.js";
2
+ import Domain from "./domain/Domain.js";
3
+ import {FallbackError, InternalServerError} from "./errors.js";
4
+
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];
11
+
12
+ export default class Session extends Domain {
13
+ static get fields() {
14
+ const default_context = this.conf.defaults.context;
15
+ return {
16
+ "?data": Object,
17
+ "context" : value => value ?? default_context,
18
+ "created": value => value ?? new Date(),
19
+ };
20
+ }
21
+
22
+ async log(color, message) {
23
+ (await this.actual_context).log(color, message);
24
+ }
25
+
26
+ route(router, url) {
27
+ return router.route(url, this.context);
28
+ }
29
+
30
+ get actual_context() {
31
+ return Context.get(this.context);
32
+ }
33
+
34
+ static async get(cookie_header) {
35
+ const session = await Session.touch({"_id": extract_id(cookie_header)});
36
+ if (session.new) {
37
+ await session.save();
38
+ session.has_cookie = false;
39
+ }
40
+ return session;
41
+ }
42
+
43
+ get cookie() {
44
+ return `session_id=${this._id}; Secure; HttpOnly; Path=/`;
45
+ }
46
+
47
+ async switch_context(context, data = {}) {
48
+ await this.save({context, data});
49
+ return this;
50
+ }
51
+
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;
68
+ }
69
+ }
@@ -0,0 +1,11 @@
1
+ export const constructible = value => {
2
+ try {
3
+ Reflect.construct(String, [], value);
4
+ return true;
5
+ } catch (error) {
6
+ return false;
7
+ }
8
+ };
9
+
10
+ export const numeric = value => !isNaN(parseFloat(value)) && isFinite(value);
11
+ export const boolish = value => value === "true" || value === "false";
@@ -0,0 +1,17 @@
1
+ const Cache = class Cache {
2
+ static #caches = [];
3
+
4
+ static get(object, property) {
5
+ return Cache.#caches.find(entry =>
6
+ entry.object === object && entry.property === property)?.value;
7
+ }
8
+
9
+ static put(object, property, cacher) {
10
+ const value = cacher();
11
+ Cache.#caches.push({object, property, value});
12
+ return value;
13
+ }
14
+ };
15
+
16
+ export default (object, property, cacher) =>
17
+ Cache.get(object, property) ?? Cache.put(object, property, cacher);
@@ -0,0 +1,33 @@
1
+ import {join, resolve} from "path";
2
+ import log from "./log.js";
3
+ import cache from "./cache.js";
4
+ import File from "./File.js";
5
+ import extend_object from "./utils/extend_object.js";
6
+ import primate_json from "../preset/primate.json" assert {"type": "json" };
7
+
8
+ const qualify = (root, paths) => {
9
+ const object = {};
10
+ for (const key in paths) {
11
+ 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
+ }
20
+
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
+ });
@@ -0,0 +1,277 @@
1
+ import Base from "../Base.js";
2
+ import Field from "./Field.js";
3
+ import {PredicateError} from "../errors.js";
4
+ import {EagerPromise} from "../promises.js";
5
+ import cache from "../cache.js";
6
+ import {random} from "../Crypto.js";
7
+
8
+ const length = 12;
9
+ const preset = "../../preset/data/stores";
10
+
11
+ const get = (target, property, receiver) =>
12
+ Reflect.get(target, property, receiver) ?? receiver.as_foreign(property);
13
+
14
+ export default class Domain extends Base {
15
+ constructor(document) {
16
+ super();
17
+
18
+ const errors = {};
19
+ return new Proxy(this, {get}).set({...document, errors}).define("_id", {
20
+ "type": String,
21
+ "predicates": ["unique"],
22
+ "in": value => value ?? random(length).toString("hex"),
23
+ });
24
+ }
25
+
26
+ get Class() {
27
+ return this.constructor;
28
+ }
29
+
30
+ static get fields() {
31
+ return {};
32
+ }
33
+
34
+ static get _fields() {
35
+ // initialize programmatic defines
36
+ cache(this, "initialized", () => {new this();});
37
+ Object.keys(this.fields).map(name => this.define(name, this.fields[name]));
38
+ return cache(this, "fields", () => ({}));
39
+ }
40
+
41
+ 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;
56
+ }
57
+
58
+ static get collection() {
59
+ return this.name.toLowerCase();
60
+ }
61
+
62
+ static get properties() {
63
+ const properties = [];
64
+ const fields = this._fields;
65
+ for (const key in fields) {
66
+ const field = fields[key];
67
+ if (!field.options.transient) {
68
+ properties.push(key);
69
+ }
70
+ }
71
+ return properties;
72
+ }
73
+
74
+ static define(name, definition) {
75
+ const fields = cache(this, "fields", () => ({}));
76
+ const {property, options} = Field.resolve(name);
77
+ if (fields[property] === undefined) {
78
+ fields[property] = new Field(property, definition, options);
79
+ }
80
+ }
81
+
82
+ define(name, definition) {
83
+ this.Class.define(name, definition);
84
+ return this;
85
+ }
86
+
87
+ get() {
88
+ return this.properties
89
+ .filter(property => property !== "_id")
90
+ .reduce((document, property) => {
91
+ document[property] = this[property];
92
+ return document;
93
+ }, {});
94
+ }
95
+
96
+ set(document = {}) {
97
+ return Object.assign(this, document);
98
+ }
99
+
100
+ // #as_foreign
101
+ as_foreign(name) {
102
+ const field = this.fields[`${name}_id`];
103
+ return field?.is_domain ? field.by_id(this[`${name}_id`]) : undefined;
104
+ }
105
+
106
+ // #serialize
107
+ // Serializing is done from the instance's point of view.
108
+ async serialize() {
109
+ const {properties, fields} = this;
110
+ return (await Promise.all(properties.map(async property =>
111
+ ({property, "value": await fields[property].serialize(this[property])}))))
112
+ .filter(({value}) => value !== undefined)
113
+ .reduce((document, {property, value}) => {
114
+ document[property] = value;
115
+ return document;
116
+ }, {});
117
+ }
118
+
119
+ // #deserialize
120
+ // Deserializing is done from the class's point of view.
121
+ static deserialize(serialized) {
122
+ const fields = this._fields;
123
+ return new this(Object.keys(serialized)
124
+ .filter(property => fields[property] !== undefined)
125
+ .map(property =>
126
+ ({property,
127
+ "value": fields[property].deserialize(serialized[property])}))
128
+ .reduce((document, {property, value}) => {
129
+ document[property] = value;
130
+ return document;
131
+ }, {}));
132
+ }
133
+
134
+ get fields() {
135
+ return this.Class._fields;
136
+ }
137
+
138
+ get collection() {
139
+ return this.Class.collection;
140
+ }
141
+ get properties() {
142
+ return this.Class.properties;
143
+ }
144
+
145
+ get store() {
146
+ return this.Class.store;
147
+ }
148
+
149
+ get new() {
150
+ return this._id === undefined;
151
+ }
152
+
153
+ async verify(delta) {
154
+ this.set(delta);
155
+ const fields = this.fields;
156
+ this.errors = (await Promise.all(Object.keys(fields).map(async property =>
157
+ ({property, "value": await fields[property].verify(property, this)}))))
158
+ .filter(result => typeof result.value === "string")
159
+ .reduce((errors, result) => {
160
+ errors[result.property] = result.value;
161
+ return errors;
162
+ }, {});
163
+
164
+ return Object.keys(this.errors).length === 0;
165
+ }
166
+
167
+ save(delta) {
168
+ return this.new ? this.insert(delta) : this.update(delta);
169
+ }
170
+
171
+ async savewith(delta, after = () => undefined) {
172
+ const verified = await this.verify(delta);
173
+ if (verified) {
174
+ const store = await this.store;
175
+ const document = await this.serialize();
176
+ await store.save(this.collection, {"_id": document._id}, document);
177
+ await after();
178
+ }
179
+ return verified;
180
+ }
181
+
182
+ insert(delta) {
183
+ delete this._id;
184
+ return this.savewith(delta, () => this.inserted());
185
+ }
186
+
187
+ inserted() {
188
+ return this;
189
+ }
190
+
191
+ async update(delta) {
192
+ const old = await this.Class.by_id(this._id);
193
+ return this.savewith(delta, () => this.updated(old));
194
+ }
195
+
196
+ updated() {
197
+ return this;
198
+ }
199
+
200
+ async delete() {
201
+ const store = await this.store;
202
+ return store.delete(this.collection, {"_id": this._id});
203
+ }
204
+
205
+ static async delete(criteria) {
206
+ const store = await this.store;
207
+ return store.delete(this.collection, criteria);
208
+ }
209
+
210
+ static by_id(_id) {
211
+ return new EagerPromise(async resolve => {
212
+ const result = await (await this.store).one(this.collection, _id);
213
+ resolve(result === undefined ? undefined : this.deserialize(result));
214
+ });
215
+ }
216
+
217
+ static first(criteria) {
218
+ return new EagerPromise(async resolve => {
219
+ const result = await (await this.store).one(this.collection, criteria);
220
+ resolve(result === undefined ? undefined : this.deserialize(result));
221
+ });
222
+ }
223
+
224
+ static one(criteria) {
225
+ return this[typeof criteria === "object" ? "first" : "by_id"](criteria);
226
+ }
227
+
228
+ static async touch(criteria) {
229
+ return await this.one(criteria) ?? new this();
230
+ }
231
+
232
+ static async find(criteria, options) {
233
+ const store = await this.store;
234
+ const results = await store.find(this.collection, criteria, options);
235
+ return results.map(result => this.deserialize(result));
236
+ }
237
+
238
+ static async count(criteria) {
239
+ const store = await this.store;
240
+ return store.count(this.collection, criteria);
241
+ }
242
+
243
+ static async exists(criteria) {
244
+ return await this.count(criteria) > 0;
245
+ }
246
+
247
+ async exists(property) {
248
+ if (!await this.Class.exists({[property]: this[property]})) {
249
+ throw new PredicateError(`No document exists with this ${property}`);
250
+ }
251
+ }
252
+
253
+ static async unique(criteria, _id) {
254
+ const one = await this.count({...criteria, _id});
255
+ const count = await this.count(criteria);
256
+ return count-one === 0;
257
+ }
258
+
259
+ async unique(property) {
260
+ if (!await this.Class.unique({[property]: this[property]}, this._id)) {
261
+ throw new PredicateError("Must be unique");
262
+ }
263
+ }
264
+
265
+ async unique_by(property, by_property) {
266
+ if (!await this.Class.unique({
267
+ [property]: await this[property],
268
+ [by_property]: await this[by_property],
269
+ }, this._id)) {
270
+ throw new PredicateError(`Must be unique in respect to ${by_property}`);
271
+ }
272
+ }
273
+
274
+ static insert(document) {
275
+ return new this(document).save();
276
+ }
277
+ }
@@ -0,0 +1,115 @@
1
+ import * as types from "../types.js";
2
+ import DomainType from "../types/Domain.js";
3
+ import Storeable from "../types/Storeable.js";
4
+ import {PredicateError} from "../errors.js";
5
+ import {defined, is_array, instances, is_constructible} from "../invariants.js";
6
+ import {constructible} from "../attributes.js";
7
+
8
+ const builtins = Object.values(types).reduce((aggregate, Type) => {
9
+ aggregate[Type.instance] = Type;
10
+ return aggregate;
11
+ }, {});
12
+
13
+ const parse = field => constructible(field)
14
+ ? {"type": field}
15
+ : as_non_constructible(field);
16
+
17
+ const as_non_constructible =
18
+ field => typeof field === "function" ? as_function(field) : as_object(field);
19
+
20
+ const as_function = field => ({"in": field,
21
+ "type": field(undefined, {}).constructor});
22
+
23
+ const as_object = field => field instanceof Array ? as_array(field) : field;
24
+
25
+ const as_array = field => ({"type": field[0], "predicates": field.slice(1)});
26
+
27
+ export default class Field {
28
+ constructor(property, definition, options) {
29
+ defined(property, "`property` required");
30
+ this.property = property;
31
+ this.definition = parse(definition);
32
+ 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);
36
+ }
37
+
38
+ static resolve(name) {
39
+ defined(name, "`name` required");
40
+ const options = {
41
+ "optional": name.includes("?"),
42
+ "transient": name.includes("~"),
43
+ };
44
+ const property = name.replaceAll("~", "").replaceAll("?", "");
45
+ return {options, property};
46
+ }
47
+
48
+ get type() {
49
+ return builtins[this.definition.type] ?? this.custom;
50
+ }
51
+
52
+ get custom() {
53
+ return this.is_domain ? DomainType : this.definition.type;
54
+ }
55
+
56
+ get is_domain() {
57
+ return this.definition.type.prototype instanceof DomainType.instance;
58
+ }
59
+
60
+ get predicates() {
61
+ return this.definition.predicates ?? [];
62
+ }
63
+
64
+ by_id(id) {
65
+ return this.definition.type.by_id(id);
66
+ }
67
+
68
+ in(property, document) {
69
+ const value = document[property];
70
+ const in_function = this.definition.in;
71
+ return in_function !== undefined ? in_function(value, document) : value;
72
+ }
73
+
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
+ verify_undefined() {
84
+ return this.options.optional ? true : "Must not be empty";
85
+ }
86
+
87
+ async verify_defined(property, value, document) {
88
+ try {
89
+ const predicates = this.override_predicates(document);
90
+ document[property] = await this.type.verify(property, value, predicates,
91
+ this.definition.type);
92
+ return true;
93
+ } catch (error) {
94
+ if (error instanceof PredicateError) {
95
+ return error.message;
96
+ }
97
+ throw error;
98
+ }
99
+ }
100
+
101
+ async verify(property, document) {
102
+ const value = await this.in(property, document);
103
+ return value === undefined
104
+ ? this.verify_undefined()
105
+ : this.verify_defined(property, value, document);
106
+ }
107
+
108
+ serialize(value) {
109
+ return value !== undefined ? this.type.serialize(value) : value;
110
+ }
111
+
112
+ deserialize(value) {
113
+ return value !== undefined ? this.type.deserialize(value) : value;
114
+ }
115
+ }