primate 0.0.1 → 0.1.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 (74) hide show
  1. package/LICENSE +27 -0
  2. package/README.md +48 -0
  3. package/package.json +10 -1
  4. package/source/client/Action.js +161 -0
  5. package/source/client/App.js +19 -0
  6. package/source/client/Base.js +5 -0
  7. package/source/client/Client.js +71 -0
  8. package/source/client/Context.js +53 -0
  9. package/source/client/Element.js +243 -0
  10. package/source/client/Node.js +13 -0
  11. package/source/client/View.js +90 -0
  12. package/source/client/exports.js +15 -0
  13. package/source/preset/client/Element.js +2 -0
  14. package/source/preset/client/app.js +2 -0
  15. package/source/preset/data/stores/default.js +2 -0
  16. package/source/preset/primate.json +31 -0
  17. package/source/preset/static/index.html +10 -0
  18. package/source/server/Action.js +118 -0
  19. package/source/server/App.js +42 -0
  20. package/source/server/Base.js +35 -0
  21. package/source/server/Bundler.js +180 -0
  22. package/source/server/Context.js +90 -0
  23. package/source/server/Crypto.js +8 -0
  24. package/source/server/Directory.js +35 -0
  25. package/source/server/File.js +113 -0
  26. package/source/server/Router.js +61 -0
  27. package/source/server/Session.js +69 -0
  28. package/source/server/attributes.js +11 -0
  29. package/source/server/cache.js +17 -0
  30. package/source/server/conf.js +33 -0
  31. package/source/server/constructible.js +8 -0
  32. package/source/server/domain/Domain.js +282 -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 +9 -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/Instance.js +8 -0
  59. package/source/server/types/Number.js +45 -0
  60. package/source/server/types/Object.js +12 -0
  61. package/source/server/types/Primitive.js +7 -0
  62. package/source/server/types/Storeable.js +51 -0
  63. package/source/server/types/String.js +49 -0
  64. package/source/server/types/errors/Array.json +7 -0
  65. package/source/server/types/errors/Boolean.json +5 -0
  66. package/source/server/types/errors/Date.json +3 -0
  67. package/source/server/types/errors/Number.json +9 -0
  68. package/source/server/types/errors/Object.json +3 -0
  69. package/source/server/types/errors/String.json +11 -0
  70. package/source/server/types.js +6 -0
  71. package/source/server/utils/extend_object.js +10 -0
  72. package/source/server/view/Parser.js +122 -0
  73. package/source/server/view/TreeNode.js +195 -0
  74. package/source/server/view/View.js +30 -0
@@ -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
+ }
@@ -0,0 +1,15 @@
1
+ import conf from "../conf.js";
2
+ import File from "../File.js";
3
+ import Field from "./Field.js";
4
+
5
+ const domains = {};
6
+ const base = conf().paths.data.domains;
7
+
8
+ for (const domain of await new File(base).list(".js")) {
9
+ const name = domain.slice(0, -3);
10
+ import(`${base}/${domain}`).then(module => {
11
+ domains[name] = module.default;
12
+ });
13
+ }
14
+
15
+ export default domains;
@@ -0,0 +1 @@
1
+ export default class extends Error {}
@@ -0,0 +1 @@
1
+ export default class extends Error {}
@@ -0,0 +1 @@
1
+ export default class extends Error {}
@@ -0,0 +1,3 @@
1
+ export {default as FallbackError} from "./errors/Fallback.js";
2
+ export {default as InternalServerError} from "./errors/InternalServer.js";
3
+ export {default as PredicateError} from "./errors/Predicate.js";
@@ -0,0 +1,27 @@
1
+ export {default as Action} from "./Action.js";
2
+ export {default as Bundler} from "./Bundler.js";
3
+ export {default as Context} from "./Context.js";
4
+ export {default as Directory} from "./Directory.js";
5
+ export {default as File} from "./File.js";
6
+
7
+ export {default as Domain} from "./domain/Domain.js";
8
+ export {default as domains} from "./domain/domains.js";
9
+ export {default as Storeable} from "./types/Storeable.js";
10
+
11
+ export * from "./errors.js";
12
+ export * from "./promises.js" ;
13
+
14
+ export {default as MemoryStore} from "./store/Memory.js";
15
+ export {default as Store} from "./store/Store.js";
16
+
17
+ export {assert, defined} from "./invariants.js";
18
+ 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
+
24
+ const app = new App();
25
+ const conf = app.conf;
26
+
27
+ export {app, conf};
@@ -0,0 +1,11 @@
1
+ const fallback = async (from, to, logic_from, logic_to) => {
2
+ try {
3
+ return await logic_from(from);
4
+ } catch (error) {
5
+ return logic_to(to);
6
+ }
7
+ };
8
+
9
+ export default (from, to, logic) => logic !== undefined
10
+ ? fallback(from, to, logic, logic)
11
+ : fallback(undefined, undefined, from, to);
@@ -0,0 +1,19 @@
1
+ import {FallbackError} from "./errors.js";
2
+ import {constructible} from "./attributes.js";
3
+
4
+ const errored = error => {
5
+ if (typeof error === "function") { // fallback
6
+ error();
7
+ throw new FallbackError();
8
+ } else { // error
9
+ throw new Error(error);
10
+ }
11
+ };
12
+
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);
18
+
19
+ export {assert, defined, is_array, instances, is_constructible};
@@ -0,0 +1,22 @@
1
+ const colors = {
2
+ "red": 31,
3
+ "green": 32,
4
+ "yellow": 33,
5
+ "blue": 34,
6
+ };
7
+ const reset = 0;
8
+
9
+ const Log = {
10
+ "paint": (color, message) => {
11
+ process.stdout.write(`\x1b[${color}m${message}\x1b[0m`);
12
+ return log;
13
+ },
14
+ "nl": () => log.paint(reset, "\n"),
15
+ };
16
+
17
+ const log = new Proxy(Log, {
18
+ "get": (target, property) => target[property] ?? (message =>
19
+ log.paint(colors[property] ?? reset, message).paint(reset, " ")),
20
+ });
21
+
22
+ export default log;
@@ -0,0 +1,49 @@
1
+ const $promise = Symbol("#promise");
2
+
3
+ const handler = {
4
+ "get": function(target, property) {
5
+ const promise = target[$promise];
6
+
7
+ if (["then", "catch"].includes(property)) {
8
+ return promise[property].bind(promise);
9
+ }
10
+
11
+ return EagerPromise.resolve(promise.then(result => {
12
+ if (property === "bind") {
13
+ return result;
14
+ }
15
+ return typeof result[property] === "function"
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[strings.length+last];
48
+
49
+ export {eager};
@@ -0,0 +1,42 @@
1
+ import Domain from "../domain/Domain.js";
2
+
3
+ const identity = value => value;
4
+ const by = {
5
+ "undefined": () => undefined,
6
+ "boolean": identity,
7
+ "string": identity,
8
+ "number": identity,
9
+ "array": identity,
10
+ "true_object": value => value instanceof Domain ? {} : value,
11
+ "object": value => by[Array.isArray(value) ? "array" : "true_object"](value),
12
+ };
13
+ const resolve_type = async (document, property) => {
14
+ if (document === undefined) {
15
+ return undefined;
16
+ }
17
+ const resolved = await document[property];
18
+ return by[typeof resolved](resolved);
19
+ };
20
+
21
+ export default async function resolve(document, projection = []) {
22
+ const resolved = {};
23
+ if (Array.isArray(document)) {
24
+ return Promise.all(document.map(sub => resolve(sub, projection)));
25
+ }
26
+ for (const property of projection) {
27
+ if (typeof property === "string") {
28
+ const result = await resolve_type(document, property);
29
+ if (result !== undefined) {
30
+ resolved[property] = result;
31
+ }
32
+ } else if (typeof property === "object") {
33
+ for (const property2 in property) {
34
+ const value = await document[property2];
35
+ if (value !== undefined) {
36
+ resolved[property2] = await resolve(value, property[property2]);
37
+ }
38
+ }
39
+ }
40
+ }
41
+ return resolved;
42
+ }
@@ -0,0 +1,2 @@
1
+ export {default as EagerPromise, eager} from "./promises/Eager.js";
2
+ export {default as MetaPromise} from "./promises/Meta.js";
@@ -0,0 +1,51 @@
1
+ import {WebSocketServer} from "ws";
2
+ import Server from "./Server.js";
3
+ import Session from "../Session.js";
4
+
5
+ const options = {"perMessageDeflate": false};
6
+
7
+ export default class DynamicServer extends Server {
8
+ run() {
9
+ const {server, path} = this.conf;
10
+ return new WebSocketServer({...options, server, path})
11
+ .on("connection", this.connected.bind(this));
12
+ }
13
+
14
+ async connected(socket, request) {
15
+ socket.session = await Session.get(request.headers.cookie);
16
+ socket.on("message", event => this.try(socket, event));
17
+ // inform client that we're connected and it can start sending messages
18
+ socket.send("open");
19
+ }
20
+
21
+ async try(socket, event) {
22
+ const {session} = socket;
23
+ const parsed = JSON.parse(event);
24
+ const url = parsed.pathname + (parsed.search ?? "");
25
+ try {
26
+ await this.onmessage(socket, parsed, url);
27
+ await session.log("green", `${parsed.type} ${url}`);
28
+ } catch(error) {
29
+ await session.log("red", error.message);
30
+ }
31
+ }
32
+
33
+ async onmessage(socket, parsed, url) {
34
+ const {router} = this.conf;
35
+ const {session} = socket;
36
+ const {path, params} = await session.route(router, url);
37
+ const data = await session.run({...parsed, path, params});
38
+ return this[data.type]?.(socket, data) ?? socket.send(JSON.stringify(data));
39
+ }
40
+
41
+ redirect(socket, data) {
42
+ if (data.location.startsWith("https://")) {
43
+ // redirect externally
44
+ return undefined;
45
+ } else {
46
+ const pathname = data.location;
47
+ this.try(socket, JSON.stringify({pathname, "type": "read"}));
48
+ return true;
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,5 @@
1
+ export default class StaticServer {
2
+ constructor(conf) {
3
+ this.conf = conf;
4
+ }
5
+ }
@@ -0,0 +1,113 @@
1
+ import zlib from "zlib";
2
+ import {Readable} from "stream";
3
+ import {createServer} from "https";
4
+ import {join} from "path";
5
+ import {parse} from "url";
6
+ import Server from "./Server.js";
7
+ import Session from "../Session.js";
8
+ import File from "../File.js";
9
+ import {algorithm, hash} from "../Crypto.js";
10
+ import log from "../log.js";
11
+ import codes from "./http-codes.json" assert {"type": "json"};
12
+ import mimes from "./mimes.json" assert {"type": "json"};
13
+ import policy from "./content-security-policy.json" assert {"type": "json"};
14
+
15
+ const regex = /\.([a-z1-9]*)$/u;
16
+ const mime = filename => mimes[filename.match(regex)[1]] ?? mimes.binary;
17
+
18
+ const csp = Object.keys(policy)
19
+ .reduce((policy_string, key) => policy_string + `${key} ${policy[key]};`, "");
20
+
21
+ const stream = (from, response) => {
22
+ response.setHeader("Content-Encoding", "br");
23
+ response.writeHead(codes.OK);
24
+ return from.pipe(zlib.createBrotliCompress())
25
+ .pipe(response)
26
+ .on("close", () => response.end());
27
+ };
28
+
29
+ export default class StaticServer extends Server {
30
+ async run() {
31
+ const {http} = this.conf;
32
+
33
+ this.server = await createServer(http, async (request, response) => {
34
+ const session = await Session.get(request.headers.cookie);
35
+ if (!session.has_cookie) {
36
+ response.setHeader("Set-Cookie", session.cookie);
37
+ }
38
+ request.on("end", () =>
39
+ this.try(parse(request.url).path, session, request, response)
40
+ ).resume();
41
+ });
42
+ }
43
+
44
+ async try(url, session, request, response) {
45
+ try {
46
+ await this.serve(url, session, request, response);
47
+ } catch (error) {
48
+ await session.log("red", error.message);
49
+ response.writeHead(codes.InternalServerError);
50
+ response.end();
51
+ }
52
+ }
53
+
54
+ async serve(url, session, request, response) {
55
+ const filename = join(this.conf.serve_from, url);
56
+ const file = await new File(filename);
57
+ if (file.is_file) {
58
+ response.setHeader("Content-Type", mime(filename));
59
+ response.setHeader("Etag", file.modified);
60
+ await session.log("green", url);
61
+ return stream(file.stream, response);
62
+ } else {
63
+ return this.serve_data(url, session, request, response);
64
+ }
65
+ }
66
+
67
+ async serve_data(pathname, session, request, response) {
68
+ const {path, params} = await session.route(this.conf.router, pathname);
69
+ await session.log("green", `read ${pathname}`);
70
+ const data = await session.run({path, params, pathname});
71
+ const handler = StaticServer[data.type] ?? this.index.bind(this);
72
+ return handler(data, response);
73
+ }
74
+
75
+ index(data, response) {
76
+ const {hashes, index} = this.conf;
77
+ const src = "import {app} from './client/primate.js';"
78
+ + `app.client.execute(${JSON.stringify(data)});`;
79
+ const integrity = `${algorithm}-${hash(src)}`;
80
+ const view = `<script type="module" integrity="${integrity}">${src}`
81
+ + "</script>";
82
+ const file = new Function("view", "return `"+index+"`")(view);
83
+ const script_src = Array.from(hashes)
84
+ .concat([integrity])
85
+ .reduce((hash_string, next_hash) => hash_string + ` '${next_hash}'`, "");
86
+
87
+ response.setHeader("Content-Security-Policy",
88
+ csp + `script-src 'self'${script_src};`);
89
+ response.setHeader("Content-Type", "text/html");
90
+ response.setHeader("Referrer-Policy", "same-origin");
91
+ return stream(Readable.from([file]), response);
92
+ }
93
+
94
+ listen(port, host) {
95
+ log.reset("on").yellow(`https://${host}:${port}`).nl();
96
+ this.server.listen(port, host);
97
+ }
98
+
99
+ static redirect({location}, response) {
100
+ response.setHeader("Location", location);
101
+ response.writeHead(codes.Found);
102
+ return response.end();
103
+ }
104
+
105
+ static return({payload}, response) {
106
+ if (payload instanceof File) {
107
+ return stream(payload.stream, response);
108
+ } else {
109
+ response.setHeader("Content-Type", "application/json");
110
+ return stream(Readable.from([JSON.stringify(payload)]), response);
111
+ }
112
+ }
113
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "default-src": "'self'",
3
+ "object-src": "'none'",
4
+ "style-src": "'self'",
5
+ "img-src": "'self' data: https:",
6
+ "frame-ancestors": "'none'",
7
+ "form-action": "'self'",
8
+ "base-uri": "'self'"
9
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "OK": 200,
3
+ "Found": 302,
4
+ "InternalServerError": 500
5
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "binary": "application/octet-stream",
3
+ "css": "text/css",
4
+ "html": "text/html",
5
+ "jpg": "image/jpeg",
6
+ "js": "text/javascript",
7
+ "json": "application/json",
8
+ "png": "image/png",
9
+ "svg": "image/svg+xml",
10
+ "woff2": "font/woff2",
11
+ "webp": "image/webp"
12
+ }
@@ -0,0 +1,60 @@
1
+ import Store from "./Store.js";
2
+
3
+ const not_found = -1;
4
+
5
+ export default class Memory extends Store {
6
+ constructor(conf) {
7
+ super(conf);
8
+ this.collections = {};
9
+ }
10
+
11
+ some(collection, operation, criteria) {
12
+ const keys = Object.keys(criteria);
13
+ return this.use(collection)[operation](document =>
14
+ // ¬∃ = ∀
15
+ !keys.some(criterion => document[criterion] !== criteria[criterion])
16
+ );
17
+ }
18
+
19
+ index(collection, _id = "") {
20
+ const criteria = typeof _id === "string" ? {_id} : _id;
21
+ return this.some(collection, "findIndex", criteria);
22
+ }
23
+
24
+ use(name) {
25
+ this.collections[name] = this.collections[name] ?? [];
26
+ return this.collections[name];
27
+ }
28
+
29
+ count(collection, criteria) {
30
+ return this.find(collection, criteria).length;
31
+ }
32
+
33
+ one(collection, _id) {
34
+ const index = this.index(collection, _id);
35
+ return index !== not_found ? this.use(collection)[index] : undefined;
36
+ }
37
+
38
+ find(collection, criteria) {
39
+ return criteria === undefined
40
+ ? this.use(collection)
41
+ : this.some(collection, "filter", criteria);
42
+ }
43
+
44
+ save(collection, {_id}, document) {
45
+ const index = this.index(collection, _id);
46
+ const use = this.use(collection);
47
+ if (index !== not_found) {
48
+ use.splice(index, 1, document);
49
+ } else {
50
+ use.push(document);
51
+ }
52
+ }
53
+
54
+ delete(collection, criteria) {
55
+ const index = this.index(collection, criteria._id);
56
+ if (index !== not_found) {
57
+ this.use(collection).splice(index, 1);
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,17 @@
1
+ export default class Store {
2
+ constructor(conf = {}) {
3
+ this.conf = conf;
4
+ }
5
+
6
+ get name() {
7
+ return this.conf.name;
8
+ }
9
+
10
+ get path() {
11
+ return this.conf.path;
12
+ }
13
+
14
+ open() {
15
+ return this;
16
+ }
17
+ }
@@ -0,0 +1,33 @@
1
+ import Instance from "./Instance.js";
2
+ import errors from "./errors/Array.json" assert {"type": "json"};
3
+
4
+ export default class extends Instance {
5
+ static get instance() {
6
+ return Array;
7
+ }
8
+
9
+ static get errors() {
10
+ return errors;
11
+ }
12
+
13
+ static is(value) {
14
+ return value instanceof this.instance;
15
+ }
16
+
17
+ static length(value, length) {
18
+ return value.length === Number(length);
19
+ }
20
+
21
+ static min(value, minimum) {
22
+ return value.length >= Number(minimum);
23
+ }
24
+
25
+ static max(value, maximum) {
26
+ return value.length <= Number(maximum);
27
+ }
28
+
29
+ static between(value, minimum, maximum) {
30
+ const length = value.length;
31
+ return length >= Number(minimum) && length <= Number(maximum);
32
+ }
33
+ }
@@ -0,0 +1,29 @@
1
+ import Primitive from "./Primitive.js";
2
+ import {boolish} from "../attributes.js";
3
+ import errors from "./errors/Boolean.json" assert {"type": "json"};
4
+
5
+ export default class extends Primitive {
6
+ static get type() {
7
+ return "boolean";
8
+ }
9
+
10
+ static get instance() {
11
+ return Boolean;
12
+ }
13
+
14
+ static get errors() {
15
+ return errors;
16
+ }
17
+
18
+ static coerce(value) {
19
+ return boolish(value) ? value === "true" : value;
20
+ }
21
+
22
+ static true(value) {
23
+ return value === true;
24
+ }
25
+
26
+ static false(value) {
27
+ return value === false;
28
+ }
29
+ }