primate 0.8.0 → 0.9.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 (68) hide show
  1. package/LICENSE +17 -23
  2. package/README.md +202 -210
  3. package/README.template.md +181 -0
  4. package/bin/primate.js +5 -0
  5. package/exports.js +3 -26
  6. package/html.js +13 -0
  7. package/module.json +10 -0
  8. package/package.json +14 -16
  9. package/{source → src}/Bundler.js +2 -2
  10. package/{source → src}/cache.js +0 -0
  11. package/src/conf.js +25 -0
  12. package/{source → src}/errors/InternalServer.js +0 -0
  13. package/{source → src}/errors/Predicate.js +0 -0
  14. package/src/errors/Route.js +1 -0
  15. package/{source → src}/errors.js +0 -0
  16. package/{source/extend_object.js → src/extend.js} +3 -3
  17. package/src/extend.spec.js +111 -0
  18. package/src/handlers/http.js +1 -0
  19. package/src/handlers/http404.js +7 -0
  20. package/src/handlers/json.js +7 -0
  21. package/src/handlers/stream.js +7 -0
  22. package/src/handlers/text.js +14 -0
  23. package/{source → src}/http-codes.json +0 -0
  24. package/src/log.js +22 -0
  25. package/{source → src}/mimes.json +0 -0
  26. package/{source → src}/preset/primate.json +0 -0
  27. package/{source → src}/preset/stores/default.js +0 -0
  28. package/src/route.js +61 -0
  29. package/src/run.js +26 -0
  30. package/src/serve.js +90 -0
  31. package/source/App.js +0 -34
  32. package/source/EagerPromise.js +0 -49
  33. package/source/Router.js +0 -31
  34. package/source/Server.js +0 -93
  35. package/source/Session.js +0 -26
  36. package/source/attributes.js +0 -14
  37. package/source/conf.js +0 -26
  38. package/source/domain/Domain.js +0 -285
  39. package/source/domain/Field.js +0 -113
  40. package/source/domain/Predicate.js +0 -24
  41. package/source/handlers/DOM/Node.js +0 -179
  42. package/source/handlers/DOM/Parser.js +0 -135
  43. package/source/handlers/html.js +0 -29
  44. package/source/handlers/http.js +0 -6
  45. package/source/handlers/json.js +0 -6
  46. package/source/handlers/redirect.js +0 -10
  47. package/source/invariants.js +0 -36
  48. package/source/map_entries.js +0 -6
  49. package/source/sanitize.js +0 -8
  50. package/source/store/Memory.js +0 -60
  51. package/source/store/Store.js +0 -30
  52. package/source/types/Array.js +0 -33
  53. package/source/types/Boolean.js +0 -29
  54. package/source/types/Date.js +0 -20
  55. package/source/types/Domain.js +0 -11
  56. package/source/types/Instance.js +0 -8
  57. package/source/types/Number.js +0 -45
  58. package/source/types/Object.js +0 -12
  59. package/source/types/Primitive.js +0 -7
  60. package/source/types/Storeable.js +0 -44
  61. package/source/types/String.js +0 -49
  62. package/source/types/errors/Array.json +0 -7
  63. package/source/types/errors/Boolean.json +0 -5
  64. package/source/types/errors/Date.json +0 -3
  65. package/source/types/errors/Number.json +0 -9
  66. package/source/types/errors/Object.json +0 -3
  67. package/source/types/errors/String.json +0 -11
  68. package/source/types.js +0 -6
package/src/serve.js ADDED
@@ -0,0 +1,90 @@
1
+ import {Path} from "runtime-compat/filesystem";
2
+ import {serve, Response} from "runtime-compat/http";
3
+ import codes from "./http-codes.json" assert {type: "json"};
4
+ import mimes from "./mimes.json" assert {type: "json"};
5
+ import {http404} from "./handlers/http.js";
6
+ import log from "./log.js";
7
+
8
+ const regex = /\.([a-z1-9]*)$/u;
9
+ const mime = filename => mimes[filename.match(regex)[1]] ?? mimes.binary;
10
+
11
+ const Server = class Server {
12
+ constructor(conf) {
13
+ this.conf = conf;
14
+ }
15
+
16
+ async start() {
17
+ const {http} = this.conf;
18
+ const {csp, "same-site": same_site = "Strict"} = http;
19
+ this.csp = Object.keys(csp).reduce((policy_string, key) =>
20
+ `${policy_string}${key} ${csp[key]};`, "");
21
+
22
+ const decoder = new TextDecoder();
23
+ serve(async request => {
24
+ const reader = request.body.getReader();
25
+ const chunks = [];
26
+ let result;
27
+ do {
28
+ result = await reader.read();
29
+ if (result.value !== undefined) {
30
+ chunks.push(decoder.decode(result.value));
31
+ }
32
+ } while (!result.done);
33
+ const body = chunks.join();
34
+ const payload = Object.fromEntries(decodeURI(body).replaceAll("+", " ")
35
+ .split("&")
36
+ .map(part => part.split("=")));
37
+ const {pathname, search} = new URL(`https://example.com${request.url}`);
38
+ return this.try(pathname + search, request, payload);
39
+ }, http);
40
+ const {port, host} = this.conf.http;
41
+ log.reset("on").yellow(`https://${host}:${port}`).nl();
42
+ }
43
+
44
+ async try(url, request, payload) {
45
+ try {
46
+ return await this.serve(url, request, payload);
47
+ } catch (error) {
48
+ console.log(error);
49
+ return new Response(null, {status: codes.InternalServerError});
50
+ }
51
+ }
52
+
53
+ async serve(url, request, payload) {
54
+ const path = new Path(this.conf.from, url);
55
+ return await path.isFile
56
+ ? this.resource(path.file)
57
+ : this.route(url, request, payload);
58
+ }
59
+
60
+ async resource(file) {
61
+ return new Response(file.readable, {
62
+ status: codes.OK,
63
+ headers: {
64
+ "Content-Type": mime(file.name),
65
+ Etag: await file.modified,
66
+ },
67
+ });
68
+ }
69
+
70
+ async route(pathname, request, payload) {
71
+ const req = {pathname, method: request.method.toLowerCase(), payload};
72
+ let result;
73
+ try {
74
+ result = await (await this.conf.router.process(req))(this.conf);
75
+ } catch (error) {
76
+ console.log(error);
77
+ result = http404``;
78
+ }
79
+ return new Response(result.body, {
80
+ status: result.code,
81
+ headers: {
82
+ ...result.headers,
83
+ "Content-Security-Policy": this.csp,
84
+ "Referrer-Policy": "same-origin",
85
+ },
86
+ });
87
+ }
88
+ };
89
+
90
+ export default conf => new Server(conf).start();
package/source/App.js DELETED
@@ -1,34 +0,0 @@
1
- import {Path, File, log} from "runtime-compat";
2
- import Bundler from "./Bundler.js";
3
- import Router from "./Router.js";
4
- import Server from "./Server.js";
5
- import package_json from "../package.json" assert {"type": "json"};
6
-
7
- export default class App {
8
- constructor(conf) {
9
- this.conf = conf;
10
- }
11
-
12
- async run() {
13
- log.reset("Primate").yellow(package_json.version);
14
- const routes = await File.list(this.conf.paths.routes);
15
- for (const route of routes) {
16
- await import(`file://${this.conf.paths.routes}/${route}`);
17
- }
18
- await new Bundler(this.conf).bundle();
19
-
20
- const conf = {"router": Router,
21
- "serve_from": this.conf.paths.public,
22
- "http": {
23
- ...this.conf.http,
24
- "key": File.read_sync(Path.resolve(this.conf.http.ssl.key)),
25
- "cert": File.read_sync(Path.resolve(this.conf.http.ssl.cert)),
26
- "keyFile": Path.resolve(this.conf.http.ssl.key),
27
- "certFile": Path.resolve(this.conf.http.ssl.cert),
28
- },
29
- };
30
- this.server = new Server(conf);
31
- await this.server.run();
32
- this.server.listen();
33
- }
34
- }
@@ -1,49 +0,0 @@
1
- import {inconstructible_function} from "./attributes.js";
2
-
3
- const $promise = Symbol("#promise");
4
-
5
- const handler = {
6
- "get": (target, property) => {
7
- const promise = target[$promise];
8
-
9
- if (["then", "catch"].includes(property)) {
10
- return promise[property].bind(promise);
11
- }
12
-
13
- return EagerPromise.resolve(promise.then(result => property === "bind"
14
- ? result
15
- : inconstructible_function(result[property])
16
- ? result[property].bind(result)
17
- : result[property]
18
- ));
19
- },
20
- "apply": (target, that, args) =>
21
- EagerPromise.resolve(target[$promise].then(result =>
22
- typeof result === "function" ? result.apply(that, args) : result
23
- )),
24
- };
25
-
26
- export default class EagerPromise {
27
- constructor(resolve, reject) {
28
- const promise = new Promise(resolve, reject);
29
- const callable = () => undefined;
30
- callable[$promise] = promise;
31
- return new Proxy(callable, handler);
32
- }
33
-
34
- static resolve(value) {
35
- return new EagerPromise(resolve => resolve(value));
36
- }
37
-
38
- static reject(value) {
39
- return new EagerPromise((resolve, reject) => reject(value));
40
- }
41
- }
42
-
43
- const last = -1;
44
- const eager = async (strings, ...keys) =>
45
- (await Promise.all(strings.slice(0, last).map(async (string, i) =>
46
- strings[i] + await keys[i]
47
- ))).join("") + strings.at(last);
48
-
49
- export {eager};
package/source/Router.js DELETED
@@ -1,31 +0,0 @@
1
- import {http404} from "./handlers/http.js";
2
-
3
- const aliases = [];
4
- const routes = [];
5
- const dealias = path => aliases.reduce((dealiased, {key, value}) =>
6
- dealiased.replace(key, () => value), path);
7
- const push = (type, path, handler) =>
8
- routes.push({type, "path": new RegExp(`^${dealias(path)}$`, "u"), handler});
9
- const find = (type, path, fallback = {"handler": r => r}) => routes.find(route =>
10
- route.type === type && route.path.test(path)) ?? fallback;
11
-
12
- export default {
13
- "map": (path, callback) => push("map", path, callback),
14
- "get": (path, callback) => push("get", path, callback),
15
- "post": (path, callback) => push("post", path, callback),
16
- "alias": (key, value) => aliases.push({key, value}),
17
- "process": async original_request => {
18
- const {method} = original_request;
19
- const url = new URL(`https://primatejs.com${original_request.pathname}`);
20
- const {pathname, searchParams} = url;
21
- const params = Object.fromEntries(searchParams);
22
- const verb = find(method, pathname, () => ({"handler": http404``}));
23
- const path = pathname.split("/").filter(path => path !== "");
24
- Object.entries(verb.path.exec(pathname)?.groups ?? [])
25
- .filter(([key]) => path[key] === undefined)
26
- .forEach(([key, value]) => Object.defineProperty(path, key, {value}));
27
-
28
- const request = {...original_request, pathname, params, path};
29
- return verb.handler(await find("map", pathname).handler(request));
30
- },
31
- };
package/source/Server.js DELETED
@@ -1,93 +0,0 @@
1
- import {Path, File, WebServer, log} from "runtime-compat";
2
- import Session from "./Session.js";
3
- import codes from "./http-codes.json" assert {"type": "json"};
4
- import mimes from "./mimes.json" assert {"type": "json"};
5
- import {http404} from "./handlers/http.js";
6
-
7
- const regex = /\.([a-z1-9]*)$/u;
8
- const mime = filename => mimes[filename.match(regex)[1]] ?? mimes.binary;
9
-
10
- const stream = (from, response) => {
11
- response.setStatus(codes.OK);
12
- return from.pipe(response).on("close", () => response.end());
13
- };
14
-
15
- export default class Server {
16
- constructor(conf) {
17
- this.conf = conf;
18
- }
19
-
20
- async run() {
21
- const {http} = this.conf;
22
- const {csp, "same-site": same_site = "Strict"} = http;
23
- this.csp = Object.keys(csp).reduce((policy_string, key) =>
24
- policy_string + `${key} ${csp[key]};`, "");
25
-
26
- this.server = new WebServer(http, async (request, response) => {
27
- const session = await Session.get(request.headers.cookie);
28
- if (!session.has_cookie) {
29
- const {cookie} = session;
30
- response.setHeader("Set-Cookie", `${cookie}; SameSite=${same_site}`);
31
- }
32
- response.session = session;
33
- const text = await request.text();
34
- const payload = Object.fromEntries(decodeURI(text).replaceAll("+", " ")
35
- .split("&")
36
- .map(part => part.split("="))
37
- .filter(([, value]) => value !== ""));
38
- const {pathname, search} = request.url;
39
- return this.try(pathname + search, request, response, payload);
40
- });
41
- }
42
-
43
- async try(url, request, response, payload) {
44
- try {
45
- await this.serve(url, request, response, payload);
46
- } catch (error) {
47
- console.log(error);
48
- response.setStatus(codes.InternalServerError);
49
- response.end();
50
- }
51
- }
52
-
53
- async serve_file(url, filename, file, response) {
54
- response.setHeader("Content-Type", mime(filename));
55
- response.setHeader("Etag", await file.modified);
56
- //await response.session.log("green", url);
57
- return stream(file.read_stream, response);
58
- }
59
-
60
- async serve(url, request, response, payload) {
61
- const filename = Path.join(this.conf.serve_from, url);
62
- const file = await new File(filename);
63
- return await file.is_file
64
- ? this.serve_file(url, filename, file, response, payload)
65
- : this.serve_route(url, request, response, payload);
66
- }
67
-
68
- async serve_route(pathname, request, response, payload) {
69
- const req = {pathname, "method": request.method.toLowerCase(), payload};
70
- let result;
71
- try {
72
- result = await this.conf.router.process(req);
73
- for (const [key, value] of Object.entries(result.headers)) {
74
- response.setHeader(key, value);
75
- }
76
- } catch (error) {
77
- console.log(error);
78
- result = http404``;
79
- }
80
- const {body, code} = result;
81
- response.setHeader("Content-Security-Policy", this.csp);
82
- response.setHeader("Referrer-Policy", "same-origin");
83
- response.setStatus(code);
84
- response.setBody(body);
85
- response.end();
86
- }
87
-
88
- listen() {
89
- const {port, host} = this.conf.http;
90
- log.reset("on").yellow(`https://${host}:${port}`).nl();
91
- this.server.listen(port, host);
92
- }
93
- }
package/source/Session.js DELETED
@@ -1,26 +0,0 @@
1
- import Domain from "./domain/Domain.js";
2
-
3
- const extract_id = cookie_header => cookie_header
4
- ?.split(";").filter(text => text.includes("session_id="))[0]?.split("=")[1];
5
-
6
- export default class Session extends Domain {
7
- static get fields() {
8
- return {
9
- "?data": Object,
10
- "created": value => value ?? new Date(),
11
- };
12
- }
13
-
14
- static async get(cookie_header) {
15
- const session = await Session.touch({"_id": extract_id(cookie_header)});
16
- await session.save();
17
- if (session.new) {
18
- session.has_cookie = false;
19
- }
20
- return session;
21
- }
22
-
23
- get cookie() {
24
- return `session_id=${this._id}; Path=/; Secure; HttpOnly`;
25
- }
26
- }
@@ -1,14 +0,0 @@
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 inconstructible_function = value =>
11
- typeof value === "function" && !constructible(value);
12
- export const numeric = value => !isNaN(parseFloat(value)) && isFinite(value);
13
- export const boolish = value => value === "true" || value === "false";
14
- export const nullish = value => value === undefined || value === null;
package/source/conf.js DELETED
@@ -1,26 +0,0 @@
1
- import {Path, File} from "runtime-compat";
2
- import cache from "./cache.js";
3
- import extend_object from "./extend_object.js";
4
- import primate_json from "./preset/primate.json" assert {"type": "json" };
5
-
6
- const qualify = (root, paths) =>
7
- Object.keys(paths).reduce((sofar, key) => {
8
- const value = paths[key];
9
- sofar[key] = typeof value === "string"
10
- ? Path.join(root, value)
11
- : qualify(`${root}/${key}`, value);
12
- return sofar;
13
- }, {});
14
-
15
- export default (file = "primate.json") => cache("conf", file, () => {
16
- let conf = primate_json;
17
- const root = Path.resolve();
18
- try {
19
- conf = extend_object(conf, JSON.parse(File.read_sync(Path.join(root, file))));
20
- } catch (error) {
21
- // local primate.json not required
22
- }
23
- conf.paths = qualify(root, conf.paths);
24
- conf.root = root;
25
- return conf;
26
- });
@@ -1,285 +0,0 @@
1
- import {Crypto} from "runtime-compat";
2
- import Field from "./Field.js";
3
- import {PredicateError} from "../errors.js";
4
- import EagerPromise from "../EagerPromise.js";
5
- import Store from "../store/Store.js";
6
- import cache from "../cache.js";
7
- import DomainType from "../types/Domain.js";
8
-
9
- const length = 12;
10
-
11
- export default class Domain {
12
- static stores_directory = "stores";
13
- static store_file = "default.js";
14
-
15
- static {
16
- // avoid transitive cyclic dependency between Domain and Field
17
- DomainType.instance = Domain;
18
- this.cache = {};
19
- }
20
-
21
- constructor(document) {
22
- const errors = {};
23
- this.define("_id", {
24
- "type": String,
25
- "predicates": ["unique"],
26
- "in": value => value ?? Crypto.random(length).toString("hex"),
27
- });
28
- return new Proxy(this, {"get": (target, property, receiver) =>
29
- Reflect.get(target, property, receiver) ?? target.#proxy(property),
30
- }).set({...document, errors});
31
- }
32
-
33
- get Class() {
34
- return this.constructor;
35
- }
36
-
37
- static get fields() {
38
- return {};
39
- }
40
-
41
- static get _fields() {
42
- // initialize programmatic defines
43
- cache(this, "initialized", () => {new this();});
44
- Object.keys(this.fields).map(name => this.define(name, this.fields[name]));
45
- return cache(this, "fields", () => ({}));
46
- }
47
-
48
- static get store() {
49
- return EagerPromise.resolve(cache(this, "store", () =>
50
- Store.get(this.stores_directory, this.store_file)
51
- ));
52
- }
53
-
54
- static get collection() {
55
- return this.name.toLowerCase();
56
- }
57
-
58
- static get properties() {
59
- const properties = [];
60
- const fields = this._fields;
61
- for (const key in fields) {
62
- const field = fields[key];
63
- if (!field.options.transient) {
64
- properties.push(key);
65
- }
66
- }
67
- return properties;
68
- }
69
-
70
- static define(name, definition) {
71
- const fields = cache(this, "fields", () => ({}));
72
- const {property, options} = Field.resolve(name);
73
- if (fields[property] === undefined) {
74
- fields[property] = new Field(property, definition, options);
75
- }
76
- }
77
-
78
- define(name, definition) {
79
- this.Class.define(name, definition);
80
- return this;
81
- }
82
-
83
- get() {
84
- return this.properties
85
- .filter(property => property !== "_id")
86
- .reduce((document, property) => {
87
- document[property] = this[property];
88
- return document;
89
- }, {});
90
- }
91
-
92
- set(document = {}) {
93
- return Object.assign(this, document);
94
- }
95
-
96
- #proxy(property) {
97
- return typeof property === "string" ? this.#link(property) : this[property];
98
- }
99
-
100
- #link(name) {
101
- const field = this.fields[`${name}_id`];
102
- if (field?.is_domain) {
103
- const {collection} = field.Type;
104
- const {cache} = this.Class;
105
- if (cache[collection] === undefined) {
106
- cache[collection] = {};
107
- }
108
- if (cache[collection][this[`${name}_id`]] === undefined) {
109
- cache[collection][this[`${name}_id`]] = field.by_id(this[`${name}_id`]);
110
- }
111
- return cache[collection][this[`${name}_id`]];
112
- }
113
- return undefined;
114
- }
115
-
116
- // Serializing is done from the instance's point of view.
117
- async serialize() {
118
- const {properties, fields} = this;
119
- return (await Promise.all(properties.map(async property =>
120
- ({property, "value": await fields[property].serialize(this[property])}))))
121
- .filter(({value}) => value !== undefined)
122
- .reduce((document, {property, value}) => {
123
- document[property] = value;
124
- return document;
125
- }, {});
126
- }
127
-
128
- // Deserializing is done from the class's point of view.
129
- static deserialize(serialized) {
130
- const fields = this._fields;
131
- return new this(Object.keys(serialized)
132
- .filter(property => fields[property] !== undefined)
133
- .map(property =>
134
- ({property,
135
- "value": fields[property].deserialize(serialized[property])}))
136
- .reduce((document, {property, value}) => {
137
- document[property] = value;
138
- return document;
139
- }, {}));
140
- }
141
-
142
- get fields() {
143
- return this.Class._fields;
144
- }
145
-
146
- get collection() {
147
- return this.Class.collection;
148
- }
149
-
150
- get properties() {
151
- return this.Class.properties;
152
- }
153
-
154
- get store() {
155
- return this.Class.store;
156
- }
157
-
158
- get new() {
159
- return this._id === undefined;
160
- }
161
-
162
- async verify(delta) {
163
- this.set(delta);
164
- const {fields} = this;
165
- this.errors = (await Promise.all(Object.keys(fields).map(async property =>
166
- ({property, "value": await fields[property].verify(property, this)}))))
167
- .filter(result => typeof result.value === "string")
168
- .reduce((errors, result) => {
169
- errors[result.property] = result.value;
170
- return errors;
171
- }, {});
172
-
173
- return Object.keys(this.errors).length === 0;
174
- }
175
-
176
- save(delta) {
177
- return this.new ? this.insert(delta) : this.update(delta);
178
- }
179
-
180
- async savewith(delta, after = () => undefined) {
181
- const verified = await this.verify(delta);
182
- if (verified) {
183
- const document = await this.serialize();
184
- await this.store.save(this.collection, {"_id": document._id}, document);
185
- const cache = this.Class.cache;
186
- if (cache[this.collection]?.[document._id] !== undefined) {
187
- delete cache[this.collection][document._id];
188
- }
189
- await after();
190
- }
191
- return verified;
192
- }
193
-
194
- insert(delta) {
195
- delete this._id;
196
- return this.savewith(delta, () => this.inserted());
197
- }
198
-
199
- inserted() {
200
- return this;
201
- }
202
-
203
- async update(delta) {
204
- const old = await this.Class.by_id(this._id);
205
- return this.savewith(delta, () => this.updated(old));
206
- }
207
-
208
- updated() {
209
- return this;
210
- }
211
-
212
- delete() {
213
- return this.store.delete(this.collection, {"_id": this._id});
214
- }
215
-
216
- static delete(criteria) {
217
- return this.store.delete(this.collection, criteria);
218
- }
219
-
220
- static by_id(_id) {
221
- return new EagerPromise(async resolve => {
222
- const result = await this.store.find(this.collection, {"_id": await _id});
223
- resolve(result.length > 0 ? this.deserialize(result[0]) : undefined);
224
- });
225
- }
226
-
227
- static first(criteria, options) {
228
- return new EagerPromise(async resolve => {
229
- const result = await this.store.one(this.collection, criteria, options);
230
- resolve(result === undefined ? undefined : this.deserialize(result));
231
- });
232
- }
233
-
234
- static one(criteria) {
235
- return this[typeof criteria === "object" ? "first" : "by_id"](criteria);
236
- }
237
-
238
- static async touch(criteria) {
239
- return await this.one(criteria) ?? new this();
240
- }
241
-
242
- static async find(criteria, options) {
243
- const results = await this.store.find(this.collection, criteria, options);
244
- return results.map(result => this.deserialize(result));
245
- }
246
-
247
- static count(criteria) {
248
- return this.store.count(this.collection, criteria);
249
- }
250
-
251
- static async exists(criteria) {
252
- return await this.count(criteria) > 0;
253
- }
254
-
255
- async exists(property) {
256
- if (!await this.Class.exists({[property]: this[property]})) {
257
- throw new PredicateError(`No document exists with this ${property}`);
258
- }
259
- }
260
-
261
- static async unique(criteria, _id) {
262
- const one = await this.count({...criteria, _id});
263
- const count = await this.count(criteria);
264
- return count-one === 0;
265
- }
266
-
267
- async unique(property) {
268
- if (!await this.Class.unique({[property]: this[property]}, this._id)) {
269
- throw new PredicateError("Must be unique");
270
- }
271
- }
272
-
273
- async unique_by(property, by_property) {
274
- if (!await this.Class.unique({
275
- [property]: await this[property],
276
- [by_property]: await this[by_property],
277
- }, this._id)) {
278
- throw new PredicateError(`Must be unique in respect to ${by_property}`);
279
- }
280
- }
281
-
282
- static insert(document) {
283
- return new this(document).save();
284
- }
285
- }