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,90 @@
1
+ // We do not import Element directly from "./Element.js", rather the otherwise
2
+ // preset user version of it, to allow for the user overriding attributes.
3
+ import Element from "../Element.js";
4
+ import Node from "./Node.js";
5
+
6
+ const data_regex = /\${([^}]*)}/g;
7
+
8
+ export default class View {
9
+ constructor(data, action, root) {
10
+ this.sources = {};
11
+ this.store = data.payload;
12
+ this.action = action;
13
+ this.paint_into(data, root);
14
+ }
15
+
16
+ paint_into(data, root = document.body) {
17
+ root.innerHTML = data.view;
18
+ root.classList.add(data.context);
19
+ this.unfold(root);
20
+ if (root === document.body) {
21
+ window.scrollTo(0, 0);
22
+ }
23
+ }
24
+
25
+ unfold(node) {
26
+ this.expand_attributes(new Node(node));
27
+ new Node(node).children.forEach(child => this.unfold(child));
28
+ }
29
+
30
+ expand_attributes(node) {
31
+ node.attributes.filter(attribute => attribute.name === "data-scope")
32
+ .map(attribute => attribute.value)
33
+ .forEach(scope => this.expand(node.node, this.store[scope], scope));
34
+ }
35
+
36
+ expand(node, data, name, from_update) {
37
+ if (data instanceof Array) {
38
+ if (from_update) {
39
+ return;
40
+ }
41
+ const blueprint = document.createElement("div");
42
+ while (node.children.length > 0) {
43
+ blueprint.appendChild(node.firstChild);
44
+ }
45
+ for (const datum of data) {
46
+ const element = blueprint.cloneNode(true);
47
+ this.sources[`${name}-${datum._id}`] = element;
48
+ node.appendChild(element);
49
+ this.populate(element, datum, `${name}-${datum._id}`);
50
+ }
51
+ } else {
52
+ this.sources[name] = node;
53
+ if (data !== undefined) {
54
+ this.populate(node, data, name);
55
+ }
56
+ }
57
+ }
58
+
59
+ populate(node, data, key, from_update) {
60
+ const regex = /^data-/u;
61
+ new Node(node).children.forEach(child => {
62
+ let populate = true;
63
+ for (let i = 0; i < child.attributes.length; i++) {
64
+ const attribute = child.attributes[i];
65
+ const {name, value} = attribute;
66
+ if (regex.test(name)) {
67
+ if (name === "data-scope") {
68
+ this.expand(child, data[value], `${key}-${value}`, from_update);
69
+ populate = false;
70
+ } else {
71
+ Element.evaluate(attribute, child, data);
72
+ }
73
+ }
74
+ }
75
+ child.children.length !== 0 && populate &&
76
+ this.populate(child, data, key, from_update);
77
+ });
78
+ }
79
+
80
+ update(data) {
81
+ Object.keys(data).forEach(key =>
82
+ this.sources[key] !== undefined &&
83
+ this.populate(this.sources[key], data[key], key, true)
84
+ );
85
+ }
86
+
87
+ write(pathname, payload) {
88
+ return this.action.write(pathname, payload);
89
+ }
90
+ }
@@ -0,0 +1,6 @@
1
+ const {baseURI} = document;
2
+ const {host, href, origin} = document.location;
3
+ const base = baseURI === href ? "/" : baseURI.replace(origin, "");
4
+ const origin_base = `${origin}${base}`;
5
+
6
+ export {base, origin_base, host};
@@ -0,0 +1,15 @@
1
+ const timeout = (callback, delay = 0) => new Promise(resolve =>
2
+ window.setTimeout(() => resolve(callback()), delay));
3
+
4
+ // This comes first, as Action > View > ../Element.js for user overrideability.
5
+ export {default as Element} from "./Element.js";
6
+ export {default as Action} from "./Action.js";
7
+ export {default as Context} from "./Context.js";
8
+ export {contexts, routes} from "../exports.js";
9
+ export {timeout};
10
+
11
+ import App from "./App.js";
12
+
13
+ const app = new App();
14
+
15
+ export {app};
@@ -0,0 +1,2 @@
1
+ import {Element} from "./primate/exports.js";
2
+ export default class extends Element {}
@@ -0,0 +1,2 @@
1
+ import {app} from "./primate/exports.js";
2
+ app.run();
@@ -0,0 +1,2 @@
1
+ import MemoryStore from "../../../server/store/Memory.js";
2
+ export default new MemoryStore();
@@ -0,0 +1,31 @@
1
+ {
2
+ "base": "/",
3
+ "debug": false,
4
+ "defaults": {
5
+ "action": "index",
6
+ "context": "guest",
7
+ "namespace": "default",
8
+ "store": "default.js"
9
+ },
10
+ "files": {
11
+ "index": "index.html"
12
+ },
13
+ "http": {
14
+ "host": "localhost",
15
+ "port": 9999,
16
+ "ssl": {
17
+ "key": "ssl/default.key",
18
+ "cert": "ssl/default.crt"
19
+ }
20
+ },
21
+ "paths": {
22
+ "client": "client",
23
+ "data": {
24
+ "domains": "domains",
25
+ "stores": "stores"
26
+ },
27
+ "public": "public",
28
+ "server": "server",
29
+ "static": "static"
30
+ }
31
+ }
@@ -0,0 +1,10 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>Primate app</title>
5
+ <base href="${conf.base}" />
6
+ <meta charset="utf-8" />
7
+ ${client}
8
+ </head>
9
+ <body></body>
10
+ </html>
@@ -0,0 +1,118 @@
1
+ import Base from "./Base.js";
2
+ import {MetaPromise} from "./promises.js";
3
+
4
+ const _response = Symbol("#response");
5
+
6
+ export default class Action extends Base {
7
+ constructor(data, session) {
8
+ super();
9
+ this._data = data;
10
+ this.session = session;
11
+ }
12
+
13
+ static get layout() {
14
+ return "default";
15
+ }
16
+
17
+ static async load(data, session, context) {
18
+ let module;
19
+ const server = this.conf.paths.server;
20
+ const {namespace, action} = data.path;
21
+ const route = `${namespace}/${action}`;
22
+ try {
23
+ module = (await import(`${server}/${context}/${route}.js`)).default;
24
+ } catch (error) {
25
+ throw new Error(`route '${route}' missing`);
26
+ }
27
+ return new module(data, session);
28
+ }
29
+
30
+ get layout() {
31
+ return this.Class().layout;
32
+ }
33
+
34
+ get path() {
35
+ return this._data.path;
36
+ }
37
+
38
+ get params() {
39
+ return this._data.params;
40
+ }
41
+
42
+ async _view(path) {
43
+ const {namespace, action} = this.path;
44
+ const view_path = path !== undefined ? path : `${namespace}/${action}`;
45
+ return (await this.context).view(view_path);
46
+ }
47
+
48
+ static sanitize(payload = {}) {
49
+ //assert(typeof payload === "object");
50
+ return Object.keys(payload)
51
+ .map(key => ({key, "value": payload[key].toString().trim()}))
52
+ .map(({key, value}) => ({key, "value":
53
+ value === "" ? undefined : value}))
54
+ .reduce((o, {key, value}) => {o[key] = value; return o;}, {});
55
+ }
56
+
57
+ async run(type) {
58
+ this.before && await this.before();
59
+ await this[type](this.Class().sanitize(this._data.payload));
60
+ }
61
+
62
+ get response() {
63
+ return this[_response]();
64
+ }
65
+
66
+ return(data = {}) {
67
+ this.respond("return", () => ({"payload": data}));
68
+ }
69
+
70
+ view(data = {}, layout = true) {
71
+ this.respond("view", async () => {
72
+ const _view = await this._view();
73
+ const payload = await this.prepare(data, _view);
74
+ const view = layout ? _view.file(this.layout) : _view.content;
75
+ return {view, payload};
76
+ });
77
+ }
78
+
79
+ update(data) {
80
+ this.respond("update", async () => {
81
+ const _view = await this._view();
82
+ const payload = await this.prepare(data, _view);
83
+ return {payload};
84
+ });
85
+ }
86
+
87
+ redirect(location) {
88
+ this.respond("redirect", () => ({location}));
89
+ }
90
+
91
+ get context() {
92
+ return this.session.actual_context;
93
+ }
94
+
95
+ switch_context(name, parameters) {
96
+ return this.session.switch_context(name, parameters);
97
+ }
98
+
99
+ async prepare(data = {}, view) {
100
+ (await this.context).Class().prepare(this, data);
101
+ return MetaPromise(data, await view.elements(this.layout));
102
+ }
103
+
104
+ respond(type, response) {
105
+ this[_response] = async () => {
106
+ const data = await response();
107
+ data.context = (await this.context).name;
108
+ data.namespace = this.path.namespace;
109
+ data.action = this.path.action;
110
+ if (data.pathname === undefined) {
111
+ data.pathname = this._data.pathname.replace(this.conf.base, "")
112
+ + (this._data.search ?? "");
113
+ }
114
+ data.type = type;
115
+ return data;
116
+ };
117
+ }
118
+ }
@@ -0,0 +1,42 @@
1
+ import {resolve} from "path";
2
+ import Base from "./Base.js";
3
+ import Bundler from "./Bundler.js";
4
+ import File from "./File.js";
5
+ import Router from "./Router.js";
6
+ import DynamicServer from "./servers/Dynamic.js";
7
+ import StaticServer from "./servers/Static.js";
8
+ import log from "./log.js";
9
+
10
+ export default class App extends Base {
11
+ constructor() {
12
+ super();
13
+ this.router = new Router(this.routes, this.conf);
14
+ this.Bundler = Bundler;
15
+ }
16
+
17
+ async run() {
18
+ log.reset("Primate").yellow(this.package.version);
19
+
20
+ const {index, hashes} = await new this.Bundler(this.conf).bundle();
21
+ const router = this.router;
22
+
23
+ const conf = {index, hashes, router,
24
+ "serve_from": this.conf.paths.public,
25
+ "http": {
26
+ "key": File.read_sync(resolve(this.conf.http.ssl.key)),
27
+ "cert": File.read_sync(resolve(this.conf.http.ssl.cert)),
28
+ },
29
+ };
30
+ this.static_server = new StaticServer(conf);
31
+ await this.static_server.run();
32
+
33
+ this.dynamic_server = new DynamicServer({router,
34
+ "path": this.base,
35
+ "server": this.static_server.server,
36
+ });
37
+ await this.dynamic_server.run();
38
+
39
+ const {port, host} = this.conf.http;
40
+ this.static_server.listen(port, host);
41
+ }
42
+ }
@@ -0,0 +1,35 @@
1
+ import conf from "./conf.js";
2
+ import log from "./log.js";
3
+ import package_json from "../../package.json" assert {"type": "json" };
4
+
5
+ const _conf = conf();
6
+ let routes;
7
+ try {
8
+ routes = await import(`${_conf.root}/routes.json`, {
9
+ "assert": {"type": "json"}
10
+ });
11
+ } catch (error) {
12
+ // local routes.json not required
13
+ }
14
+
15
+ export default class Base {
16
+ static get conf() {
17
+ return _conf;
18
+ }
19
+
20
+ get conf() {
21
+ return this.Class().conf;
22
+ }
23
+
24
+ get package() {
25
+ return package_json;
26
+ }
27
+
28
+ get routes() {
29
+ return routes?.default ?? [];
30
+ }
31
+
32
+ Class() {
33
+ return this.constructor;
34
+ }
35
+ }
@@ -0,0 +1,180 @@
1
+ import {dirname} from "path";
2
+ import {fileURLToPath} from "url";
3
+ import Directory from "./Directory.js";
4
+ import File from "./File.js";
5
+ import {algorithm, hash} from "./Crypto.js";
6
+
7
+ const meta_url = fileURLToPath(import.meta.url);
8
+ const directory = dirname(meta_url);
9
+ const preset = `${directory}/../preset`;
10
+
11
+ const ending = 3;
12
+
13
+ const s_declarations = () =>
14
+ "// Declarations\n"
15
+ + "const contexts = {};\n"
16
+ + "const routes = {};\n\n";
17
+
18
+ const stringify_action = (context, domain, action, i, j, k) => {
19
+ const import_name = `route{i}_${j}_${k}`;
20
+ return `import ${import_name} from "./${context}/${domain}/${action}.js";\n`
21
+ + `routes["${context}"]["${domain}"]["${action}"] = ${import_name};\n`;
22
+ };
23
+
24
+ const stringify_actions = ({context, domain, actions = []}, i, j) =>
25
+ actions.reduce((s, action, k) =>
26
+ s + stringify_action(context, domain, action, i, j, k), "");
27
+
28
+ const stringify_domain = (domain, i, j) =>
29
+ `routes["${domain.context}"]["${domain.domain}"] = {};\n`
30
+ + stringify_actions(domain, i, j);
31
+
32
+ const stringify_domains = (domains, i) =>
33
+ domains.reduce((s, domain, j) => s + stringify_domain(domain, i, j), "");
34
+
35
+ const stringify_context = (context, i) =>
36
+ `import context${i} from "./${context[0].context}/context.js";\n`
37
+ + `contexts["${context[0].context}"] = context${i};\n`
38
+ + `routes["${context[0].context}"] = {};\n`
39
+ + stringify_domains(context, i) + "\n";
40
+
41
+ const s_exports = () =>
42
+ "// Exports\n"
43
+ + "export {contexts, routes};\n";
44
+
45
+ const stringify = actions =>
46
+ actions.reduce((s, context, i) => s + stringify_context(context, i), "");
47
+
48
+ export default class Bundler {
49
+ constructor(conf) {
50
+ this.conf = conf;
51
+ this.debug = conf.debug;
52
+ this.index = conf.files.index;
53
+ this.scripts = [];
54
+ this.hashes = new Set();
55
+ }
56
+
57
+ async copy_with_preset(subdirectory, to) {
58
+ const {paths} = this.conf;
59
+
60
+ // copy files preset files first
61
+ await File.copy(`${preset}/${subdirectory}`, to);
62
+
63
+ // copy any user code over it, not recreating the folder
64
+ try {
65
+ await File.copy(paths[subdirectory], to, false);
66
+ } catch(error) {
67
+ // directory doesn't exist
68
+ }
69
+ }
70
+
71
+ async bundle() {
72
+ const {paths} = this.conf;
73
+ const client_directory = `${paths.public}/client`;
74
+
75
+ // copy static files to public
76
+ await this.copy_with_preset("static", paths.public);
77
+
78
+ // copy client files to public/client
79
+ await this.copy_with_preset("client", client_directory);
80
+
81
+ const primate_js = new File(`${client_directory}/primate.js`);
82
+ await primate_js.write("export * from \"./primate/exports.js\";");
83
+
84
+ // write dynamic exports to public/client/exports.js
85
+ await this.write_exports();
86
+
87
+ // copy framework code to public/client/primate
88
+ await File.copy(`${directory}/../client`, `${client_directory}/primate`);
89
+
90
+ await this.register_scripts();
91
+
92
+ // read index.html from public, then remove it (we serve it dynamically)
93
+ const index_html = await File.read(`${paths.public}/${this.index}`);
94
+ await File.remove(`${paths.public}/${this.index}`);
95
+
96
+ const body = "return `"+index_html+"`";
97
+ const index = new Function("conf", "client", body)(this.conf, this.client);
98
+
99
+ return {index, "hashes": this.hashes};
100
+ }
101
+
102
+ async register_scripts() {
103
+ const {paths} = this.conf;
104
+
105
+ const scripts = await this.collect("client");
106
+ Promise.all(scripts.map(async script =>
107
+ this.register(script, await File.read(paths.public, script))
108
+ ));
109
+ }
110
+
111
+ async collect(path) {
112
+ // scan public/client and collect scripts to be added as tags
113
+ let scripts = [];
114
+ const files = await Directory.list(`${this.conf.paths.public}/${path}`);
115
+ for (const file of files) {
116
+ if (file.endsWith(".js")) {
117
+ scripts.push(`${path}/${file}`);
118
+ } else {
119
+ scripts = scripts.concat(await this.collect(`${path}/${file}`));
120
+ }
121
+ }
122
+
123
+ return scripts;
124
+ }
125
+
126
+ register(src, source) {
127
+ const integrity = `${algorithm}-${hash(source)}`;
128
+ this.scripts.push({"src": `${this.conf.base}${src}`, integrity});
129
+ this.hashes.add(integrity);
130
+ }
131
+
132
+ get public_client() {
133
+ return `${this.conf.paths.public}/client`;
134
+ }
135
+
136
+ get client() {
137
+ let client = "";
138
+ for (const {integrity, src} of this.scripts) {
139
+ client += `<script type="module" integrity="${integrity}" src="${src}">`
140
+ + "</script>\n";
141
+ }
142
+ return client + "<first-view />";
143
+ }
144
+
145
+ async write_exports() {
146
+ const exports = new File(this.public_client, "exports.js");
147
+
148
+ let export_string = s_declarations();
149
+ export_string += stringify(await this.read());
150
+ export_string += s_exports();
151
+
152
+ await exports.write(export_string);
153
+ }
154
+
155
+ async read() {
156
+ const path = this.public_client;
157
+
158
+ const contexts = (await Directory.list(path))
159
+ .filter(file => !file.endsWith(".js"))
160
+ .map(context => ({context}));
161
+
162
+ const context_domains = await Promise.all(contexts.map(async ({context}) =>
163
+ ({context, "domains": (await Directory.list(path, context))
164
+ .filter(file => !file.endsWith(".js"))
165
+ .map(domain => ({domain})),
166
+ })));
167
+
168
+ const actions = await Promise.all(context_domains
169
+ .map(({context, domains}) =>
170
+ Promise.all(domains.map(async ({domain}) => ({
171
+ context, domain,
172
+ "actions": (await Directory.list(path, context, domain))
173
+ .map(action => action.slice(0, -ending)),
174
+ })))));
175
+
176
+ return actions.length === 1 && actions[0].length === 0
177
+ ? [context_domains]
178
+ : actions;
179
+ }
180
+ }
@@ -0,0 +1,90 @@
1
+ import Base from "./Base.js";
2
+ import File from "./File.js";
3
+ import View from "./view/View.js";
4
+ import Action from "./Action.js";
5
+ import {InternalServerError} from "./errors.js";
6
+ import cache from "./cache.js";
7
+ import {defined} from "./invariants.js";
8
+ import fallback from "./fallback.js";
9
+ import log from "./log.js";
10
+
11
+ export default class Context extends Base {
12
+ constructor(name) {
13
+ super();
14
+ this.name = name;
15
+ }
16
+
17
+ log(color, message) {
18
+ log[color](this.name).reset(message).nl();
19
+ }
20
+
21
+ async errored(data, error, session) {
22
+ if (this.Class().error !== undefined) {
23
+ const action = new Action(data, session);
24
+ this.Class().error(action);
25
+ const response = await action.response;
26
+ if (data.pathname === response.location) {
27
+ throw new InternalServerError("redirect loop detected");
28
+ }
29
+ this.log("red", "handling error");
30
+ return response;
31
+ } else {
32
+ const {namespace, action} = data.path;
33
+ const route = `${namespace}/${action}`;
34
+ throw new InternalServerError(`${data.type} /${route} missing`);
35
+ }
36
+ }
37
+
38
+ static before(action) {
39
+ return action;
40
+ }
41
+
42
+ static prepare(action, data) {
43
+ return {action, data};
44
+ }
45
+
46
+ static get(name) {
47
+ return cache(this, name, async () => {
48
+ const exists = await File.exists(`${this.conf.paths.server}/${name}`);
49
+ if (!exists) {
50
+ throw new InternalServerError("missing context directory");
51
+ }
52
+ const path = `${this.conf.paths.server}/${name}/context.js`;
53
+ const context = await fallback(
54
+ async () => (await import(path)).default,
55
+ () => Context);
56
+ return new context(name);
57
+ });
58
+ }
59
+
60
+ get base_path() {
61
+ return `${this.conf.paths.server}/${this.name}`;
62
+ }
63
+
64
+ async view(path) {
65
+ const file = await new File(`${this.base_path}/${path}.html`).read();
66
+ return new View(path, file, await this.layouts());
67
+ }
68
+
69
+ layouts() {
70
+ return cache(this, "layouts", async () => {
71
+ const path = `${this.base_path}/layouts`;
72
+
73
+ const layouts = {};
74
+ for (const file of await new File(path).list()) {
75
+ layouts[file.slice(0, -5)] = await File.read(`${path}/${file}`);
76
+ }
77
+
78
+ return layouts;
79
+ });
80
+ }
81
+
82
+ async run(data, session) {
83
+ const {type = "read"} = data;
84
+ const action = await Action.load(data, session, this.name);
85
+ defined(action[type]);
86
+ await this.Class().before(action);
87
+ await action.run(type);
88
+ return action.response;
89
+ }
90
+ }
@@ -0,0 +1,8 @@
1
+ import {createHash, randomBytes} from "crypto";
2
+
3
+ export const algorithm = "sha256";
4
+
5
+ export const hash = (payload, digest = "base64") =>
6
+ createHash(algorithm).update(payload, "utf8").digest(digest);
7
+
8
+ export const random = length => randomBytes(length);
@@ -0,0 +1,35 @@
1
+ import fs from "fs";
2
+ import {join} from "path";
3
+
4
+ const options = {"encoding": "utf8"};
5
+
6
+ const readdir = path => new Promise((resolve, reject) =>
7
+ fs.readdir(path, options,
8
+ (error, files) => error === null ? resolve(files) : reject(error)
9
+ ));
10
+
11
+ const rm = path => new Promise((resolve, reject) =>
12
+ fs.rm(path, {"recursive": true, "force": true},
13
+ error => error === null ? resolve(true) : reject(error)
14
+ ));
15
+
16
+ const mkdir = path => new Promise((resolve, reject) =>
17
+ fs.mkdir(path, error => error === null ? resolve(true) : reject(error)));
18
+
19
+ export default class Directory {
20
+ static list(...args) {
21
+ return readdir(join(...args));
22
+ }
23
+
24
+ static remove(...args) {
25
+ return rm(join(...args));
26
+ }
27
+
28
+ static make(...args) {
29
+ return mkdir(join(...args));
30
+ }
31
+
32
+ static async remake(...args) {
33
+ await this.remove(...args) && this.make(...args);
34
+ }
35
+ }