primate 0.5.2 → 0.6.2

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 (80) hide show
  1. package/README.md +21 -36
  2. package/debris.json +1 -2
  3. package/package.json +7 -10
  4. package/source/App.js +36 -0
  5. package/source/Bundler.js +51 -0
  6. package/source/{server/Directory.js → Directory.js} +0 -0
  7. package/source/{server/EagerPromise.js → EagerPromise.js} +5 -7
  8. package/source/{server/File.js → File.js} +0 -0
  9. package/source/Router.js +28 -0
  10. package/source/Server.js +105 -0
  11. package/source/{server/Session.js → Session.js} +2 -21
  12. package/source/{server/attributes.js → attributes.js} +0 -0
  13. package/source/{server/cache.js → cache.js} +0 -0
  14. package/source/{server/conf.js → conf.js} +1 -1
  15. package/source/{server/crypto.js → crypto.js} +0 -0
  16. package/source/{server/domain → domain}/Domain.js +7 -9
  17. package/source/{server/domain → domain}/Field.js +0 -0
  18. package/source/{server/domain → domain}/Predicate.js +0 -0
  19. package/source/{server/errors → errors}/InternalServer.js +0 -0
  20. package/source/{server/errors → errors}/Predicate.js +0 -0
  21. package/source/{server/errors.js → errors.js} +0 -0
  22. package/source/{server/exports.js → exports.js} +6 -3
  23. package/source/{server/extend_object.js → extend_object.js} +0 -0
  24. package/source/handlers/DOM/Node.js +184 -0
  25. package/source/{server/view → handlers/DOM}/Parser.js +22 -18
  26. package/source/handlers/html.js +30 -0
  27. package/source/handlers/http.js +6 -0
  28. package/source/handlers/json.js +6 -0
  29. package/source/handlers/redirect.js +10 -0
  30. package/source/{server/servers/http-codes.json → http-codes.json} +0 -0
  31. package/source/{server/invariants.js → invariants.js} +0 -0
  32. package/source/{server/log.js → log.js} +0 -0
  33. package/source/{server/servers/mimes.json → mimes.json} +0 -0
  34. package/source/preset/primate.json +3 -7
  35. package/source/preset/static/index.html +0 -2
  36. package/source/preset/stores/default.js +1 -1
  37. package/source/{server/sanitize.js → sanitize.js} +0 -0
  38. package/source/{server/store → store}/Memory.js +0 -0
  39. package/source/{server/store → store}/Store.js +1 -1
  40. package/source/{server/types → types}/Array.js +0 -0
  41. package/source/{server/types → types}/Boolean.js +0 -0
  42. package/source/{server/types → types}/Date.js +0 -0
  43. package/source/{server/types → types}/Domain.js +0 -0
  44. package/source/{server/types → types}/Instance.js +0 -0
  45. package/source/{server/types → types}/Number.js +0 -0
  46. package/source/{server/types → types}/Object.js +0 -0
  47. package/source/{server/types → types}/Primitive.js +0 -0
  48. package/source/{server/types → types}/Storeable.js +0 -0
  49. package/source/{server/types → types}/String.js +0 -0
  50. package/source/{server/types → types}/errors/Array.json +0 -0
  51. package/source/{server/types → types}/errors/Boolean.json +0 -0
  52. package/source/{server/types → types}/errors/Date.json +0 -0
  53. package/source/{server/types → types}/errors/Number.json +0 -0
  54. package/source/{server/types → types}/errors/Object.json +0 -0
  55. package/source/{server/types → types}/errors/String.json +0 -0
  56. package/source/{server/types.js → types.js} +0 -0
  57. package/source/client/Action.js +0 -157
  58. package/source/client/App.js +0 -16
  59. package/source/client/Client.js +0 -61
  60. package/source/client/Context.js +0 -47
  61. package/source/client/Element.js +0 -249
  62. package/source/client/Node.js +0 -13
  63. package/source/client/Session.js +0 -27
  64. package/source/client/View.js +0 -89
  65. package/source/client/document.js +0 -6
  66. package/source/client/exports.js +0 -15
  67. package/source/preset/client/Element.js +0 -2
  68. package/source/preset/client/app.js +0 -2
  69. package/source/server/Action.js +0 -100
  70. package/source/server/App.js +0 -62
  71. package/source/server/Bundler.js +0 -177
  72. package/source/server/Context.js +0 -97
  73. package/source/server/Projector.js +0 -86
  74. package/source/server/Router.js +0 -52
  75. package/source/server/domain/domains.js +0 -31
  76. package/source/server/servers/Dynamic.js +0 -57
  77. package/source/server/servers/Server.js +0 -5
  78. package/source/server/servers/Static.js +0 -117
  79. package/source/server/view/TreeNode.js +0 -197
  80. package/source/server/view/View.js +0 -35
@@ -1,177 +0,0 @@
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 = (context, domain, i, j) =>
29
- `routes["${context}"]["${domain.domain}"] = {};\n`
30
- + stringify_actions(domain, i, j);
31
-
32
- const stringify_domains = ({context, domains}, i) =>
33
- domains.reduce((s, domain, j) =>
34
- s + stringify_domain(context, domain, i, j), "");
35
-
36
- const stringify_context = (context, i) =>
37
- `import context${i} from "./${context.context}/context.js";\n`
38
- + `contexts["${context.context}"] = context${i};\n`
39
- + `routes["${context.context}"] = {};\n`
40
- + stringify_domains(context, i) + "\n";
41
-
42
- const s_exports = () =>
43
- "// Exports\n"
44
- + "export {contexts, routes};\n";
45
-
46
- const stringify = actions =>
47
- actions.reduce((s, context, i) => s + stringify_context(context, i), "");
48
-
49
- export default class Bundler {
50
- constructor(conf) {
51
- this.conf = conf;
52
- this.debug = conf.debug;
53
- this.index = conf.files.index;
54
- this.scripts = [];
55
- this.hashes = new Set();
56
- }
57
-
58
- async copy_with_preset(subdirectory, to) {
59
- const {paths} = this.conf;
60
-
61
- // copy files preset files first
62
- await File.copy(`${preset}/${subdirectory}`, to);
63
-
64
- // copy any user code over it, not recreating the folder
65
- try {
66
- await File.copy(paths[subdirectory], to, false);
67
- } catch(error) {
68
- // directory doesn't exist
69
- }
70
- }
71
-
72
- async bundle() {
73
- const {paths} = this.conf;
74
- const client_directory = `${paths.public}/client`;
75
-
76
- // copy static files to public
77
- await this.copy_with_preset("static", paths.public);
78
-
79
- // copy client files to public/client
80
- await this.copy_with_preset("client", client_directory);
81
-
82
- const primate_js = new File(`${client_directory}/primate.js`);
83
- await primate_js.write("export * from \"./primate/exports.js\";");
84
-
85
- // write dynamic exports to public/client/exports.js
86
- await this.write_exports();
87
-
88
- // copy framework code to public/client/primate
89
- await File.copy(`${directory}/../client`, `${client_directory}/primate`);
90
-
91
- await this.register_scripts();
92
-
93
- // read index.html from public, then remove it (we serve it dynamically)
94
- const index_html = await File.read(`${paths.public}/${this.index}`);
95
- await File.remove(`${paths.public}/${this.index}`);
96
-
97
- const body = `return \`${index_html}\``;
98
- const index = new Function("conf", "client", body)(this.conf, this.client);
99
-
100
- return {index, "hashes": this.hashes};
101
- }
102
-
103
- async register_scripts() {
104
- const scripts = await this.collect("client");
105
- Promise.all(scripts.map(async script =>
106
- this.register(script, await File.read(this.conf.paths.public, script))
107
- ));
108
- }
109
-
110
- async collect(path) {
111
- // scan public/client and collect scripts to be added as tags
112
- let scripts = [];
113
- const files = await Directory.list(`${this.conf.paths.public}/${path}`);
114
- for (const file of files) {
115
- if (file.endsWith(".js")) {
116
- scripts.push(`${path}/${file}`);
117
- } else {
118
- scripts = scripts.concat(await this.collect(`${path}/${file}`));
119
- }
120
- }
121
-
122
- return scripts;
123
- }
124
-
125
- register(src, source) {
126
- const integrity = `${algorithm}-${hash(source)}`;
127
- this.scripts.push({"src": `${this.conf.base}${src}`, integrity});
128
- this.hashes.add(integrity);
129
- }
130
-
131
- get public_client() {
132
- return `${this.conf.paths.public}/client`;
133
- }
134
-
135
- get client() {
136
- let client = "";
137
- for (const {integrity, src} of this.scripts) {
138
- client += `<script type="module" integrity="${integrity}" src="${src}">`
139
- + "</script>\n";
140
- }
141
- return `${client}<first-view />`;
142
- }
143
-
144
- async write_exports() {
145
- const exports = new File(this.public_client, "exports.js");
146
-
147
- let export_string = s_declarations();
148
- export_string += stringify(await this.read());
149
- export_string += s_exports();
150
-
151
- await exports.write(export_string);
152
- }
153
-
154
- async read() {
155
- const path = this.public_client;
156
-
157
- const contexts = (await Directory.list(path))
158
- .filter(file => !file.endsWith(".js"))
159
- .map(context => ({context}));
160
-
161
- const context_domains = await Promise.all(contexts.map(async ({context}) =>
162
- ({context, "domains": (await Directory.list(path, context))
163
- .filter(file => !file.endsWith(".js"))
164
- .map(domain => ({domain})),
165
- })));
166
-
167
- const actions = await Promise.all(context_domains
168
- .map(({context, domains}) =>
169
- Promise.all(domains.map(async ({domain}) => ({
170
- context, domain,
171
- "actions": (await Directory.list(path, context, domain))
172
- .map(action => action.slice(0, -ending)),
173
- })))));
174
-
175
- return context_domains;
176
- }
177
- }
@@ -1,97 +0,0 @@
1
- import {resolve} from "path";
2
- import File from "./File.js";
3
- import Action from "./Action.js";
4
- import {InternalServerError} from "./errors.js";
5
- import {assert} from "./invariants.js";
6
- import cache from "./cache.js";
7
- import log from "./log.js";
8
-
9
- export default class Context {
10
- static directory = "server";
11
-
12
- constructor(name) {
13
- this.name = name;
14
- }
15
-
16
- static before(action) {
17
- return action;
18
- }
19
-
20
- static prepare(action, data) {
21
- return {action, data};
22
- }
23
-
24
- static get(name) {
25
- return cache(this, name, async () => {
26
- const directory = resolve(`${this.directory}/${name}`);
27
- assert(await File.exists(directory), () => {
28
- throw new InternalServerError(`missing context directory \`${name}\``);
29
- });
30
- try {
31
- return new (await import(`${directory}/context.js`)).default(name);
32
- } catch (error) {
33
- return new this(name);
34
- }
35
- });
36
- }
37
-
38
- get Class() {
39
- return this.constructor;
40
- }
41
-
42
- get directory() {
43
- return `${this.Class.directory}/${this.name}`;
44
- }
45
-
46
- get layouts() {
47
- return cache(this, "layouts", () => {
48
- const ending = -5;
49
- const path = `${this.directory}/layouts`;
50
-
51
- return new File(path).list().reduce(async (sofar, file) => {
52
- const layouts = await sofar;
53
- layouts[file.slice(0, ending)] = await File.read(`${path}/${file}`);
54
- return layouts;
55
- }, {});
56
- });
57
- }
58
-
59
- log(color, message) {
60
- log[color](this.name).reset(message).nl();
61
- }
62
-
63
- async handle(error, action) {
64
- if (this.Class.error !== undefined) {
65
- this.Class.error(action);
66
- const response = await action.response();
67
- assert(action.request.pathname !== response.location, () => {
68
- throw new InternalServerError("redirect loop detected");
69
- });
70
- this.log("red", "handling error with context error handler");
71
- return response;
72
- } else {
73
- throw new InternalServerError(error.message);
74
- }
75
- }
76
-
77
- catch(error, action) {
78
- if (error instanceof InternalServerError) {
79
- // cannot proceed, throw up
80
- throw error;
81
- } else {
82
- return this.handle(error, action);
83
- }
84
- }
85
-
86
- async run(request, session) {
87
- const {type = "read"} = request;
88
- try {
89
- const action = await Action.new(request, session, this);
90
- await this.Class.before(action);
91
- await action.run(type);
92
- return action.response();
93
- } catch(error) {
94
- return this.catch(error, new Action(request, session, this));
95
- }
96
- }
97
- }
@@ -1,86 +0,0 @@
1
- import Domain from "./domain/Domain.js";
2
- import {actuals} from "./domain/domains.js";
3
- import log from "./log.js";
4
-
5
- const scalar = async (document, property) => {
6
- const value = await document[property];
7
- return value instanceof Domain ? {} : value;
8
- };
9
-
10
- export default class Projector {
11
- constructor(documents, projection = []) {
12
- this.cache = {};
13
- for (const actual in actuals) {
14
- this.cache[actual] = {};
15
- }
16
- this.documents = documents;
17
- this.projection = projection.reduce((sofar, domain) => {
18
- const [key] = Object.keys(domain);
19
- sofar[key] = domain[key];
20
- return sofar;
21
- }, {});
22
- }
23
-
24
- many(documents, projection) {
25
- return Promise.all(documents.map(d => this.resolve(d, projection)));
26
- }
27
-
28
- async by_store(key, document, projection) {
29
- return this.resolve(await document[key], projection[key]);
30
- }
31
-
32
- async by_cache(space, key, document, projection) {
33
- const cache = this.cache[space];
34
- const key_id = document[`${key}_id`];
35
- if (cache[key_id] === undefined) {
36
- cache[key_id] = await this.by_store(key, document, projection);
37
- }
38
- return cache[key_id];
39
- }
40
-
41
- object(document, projection, key, fields) {
42
- return fields?.[key] === undefined
43
- ? this.by_store(key, document, projection)
44
- : this.by_cache(fields[key], key, document, projection);
45
- }
46
-
47
- async one(document, projection) {
48
- if (document === undefined) {
49
- return undefined;
50
- }
51
- const resolved = {};
52
- let fields = undefined;
53
- if (document instanceof Domain && document.Class.name !== "default") {
54
- fields = actuals[document.Class.name];
55
- }
56
- for (const property of projection) {
57
- if (typeof property === "string") {
58
- resolved[property] = await scalar(document, property);
59
- } else if (typeof property === "object") {
60
- const [key] = Object.keys(property);
61
- resolved[key] = await this.object(document, property, key, fields);
62
- }
63
- }
64
- return resolved;
65
- }
66
-
67
- resolve(document, projection) {
68
- return this[Array.isArray(document) ? "many" : "one"](document, projection);
69
- }
70
-
71
- async project() {
72
- return (await Promise.all(Object.keys(this.documents).map(async key => {
73
- const resolved = this.projection[key] === undefined
74
- ? undefined
75
- : await this.resolve(await this.documents[key], this.projection[key]);
76
- return {key, resolved};
77
- }))).reduce((projected, {key, resolved}) => {
78
- if (resolved === undefined) {
79
- log.yellow(` \`${key}\` not projected`).nl();
80
- } else {
81
- projected[key] = resolved;
82
- }
83
- return projected;
84
- }, {});
85
- }
86
- }
@@ -1,52 +0,0 @@
1
- export default class Router {
2
- constructor(routes = [], conf) {
3
- this.conf = conf;
4
- this.routes = {};
5
- routes.forEach(({from, to, contexts}) => contexts.forEach(context => {
6
- this.routes[context] = this.routes[context] ?? [];
7
- this.routes[context].push({"from": new RegExp(`^${from}$`, "u"), to});
8
- }));
9
- }
10
-
11
- context(context) {
12
- return context ?? this.conf.defaults.context;
13
- }
14
-
15
- debase(pathname) {
16
- return pathname.replace(this.conf.base, "/");
17
- }
18
-
19
- route_by_context(context, path) {
20
- return this.routes[context]?.find(({from}) => from.test(path)) ?? [];
21
- }
22
-
23
- resolve_by_context(pathname, context) {
24
- const [path, search = ""] = pathname.split("?");
25
-
26
- const route = this.route_by_context(context, path);
27
- if (route === undefined) {
28
- return pathname;
29
- }
30
-
31
- const replace = path.replace(route.from, route.to);
32
- return `${replace}${replace.includes("?") ? "" : "?"}&${search}`;
33
- }
34
-
35
- resolve(pathname, context) {
36
- return this.context(context) === undefined
37
- ? pathname
38
- : this.resolve_by_context(pathname, context);
39
- }
40
-
41
- async route(pathname, context) {
42
- const resolved = await this.resolve(this.debase(pathname), context);
43
- const url = new URL(`https://primatejs.com/${resolved}`);
44
- const parts = url.pathname.split("/").filter(part => part !== "");
45
- const [namespace, action, _id] = parts;
46
- return {
47
- "pathname": url.pathname, resolved, parts,
48
- "path": {namespace, action, _id},
49
- "params": {...Object.fromEntries(url.searchParams)},
50
- };
51
- }
52
- }
@@ -1,31 +0,0 @@
1
- import conf from "../conf.js";
2
- import File from "../File.js";
3
- import Field from "./Field.js";
4
- import Domain from "./Domain.js";
5
-
6
- const domains = {};
7
- const base = conf().paths.domains;
8
-
9
- for (const domain of await new File(base).list(".js")) {
10
- const name = domain.slice(0, -3);
11
- import(`${base}/${domain}`).then(module => {
12
- domains[name] = module.default;
13
- });
14
- }
15
-
16
- export const actuals = {};
17
- for (const domain in domains) {
18
- if (domains[domain].prototype instanceof Domain) {
19
- const fields = {};
20
- for (const field in domains[domain]._fields) {
21
- const Type = domains[domain]._fields[field].Type;
22
- if(Type.prototype instanceof Domain) {
23
- fields[field.slice(0, -3)] = Type.name;
24
- }
25
- }
26
- actuals[domain] = fields;
27
- }
28
- }
29
-
30
-
31
- export default domains;
@@ -1,57 +0,0 @@
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
- const {context} = this.conf;
16
- socket.session = await Session.get(request.headers.cookie, context);
17
- socket.on("message", event => this.try(socket, event));
18
- // inform client that we're connected and it can start sending messages
19
- socket.send("open");
20
- }
21
-
22
- async try(socket, event) {
23
- const {session} = socket;
24
- const request = JSON.parse(event);
25
- try {
26
- await this.serve(request, socket);
27
- await session.log("green", `${request.type} ${request.url}`);
28
- } catch(error) {
29
- await session.log("red", error.message);
30
- }
31
- }
32
-
33
- async serve(request, socket) {
34
- const {router} = this.conf;
35
- const {session} = socket;
36
- const {path, params} = await session.route(router, request.url);
37
- const response = await session.run({...request, path, params});
38
- const {type} = response;
39
- return this[type]?.(socket, response)
40
- ?? DynamicServer.serve(socket, response);
41
- }
42
-
43
- static serve(socket, response) {
44
- socket.send(JSON.stringify(response));
45
- }
46
-
47
- redirect(socket, {location}) {
48
- if (location.startsWith("https://")) {
49
- // redirect externally
50
- return undefined;
51
- } else {
52
- const url = location;
53
- this.try(socket, JSON.stringify({"pathname": url, url, "type": "read"}));
54
- return true;
55
- }
56
- }
57
- }
@@ -1,5 +0,0 @@
1
- export default class Server {
2
- constructor(conf) {
3
- this.conf = conf;
4
- }
5
- }
@@ -1,117 +0,0 @@
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
-
14
- const regex = /\.([a-z1-9]*)$/u;
15
- const mime = filename => mimes[filename.match(regex)[1]] ?? mimes.binary;
16
-
17
- const stream = (from, response) => {
18
- response.setHeader("Content-Encoding", "br");
19
- response.writeHead(codes.OK);
20
- return from.pipe(zlib.createBrotliCompress())
21
- .pipe(response)
22
- .on("close", () => response.end());
23
- };
24
-
25
- export default class StaticServer extends Server {
26
- async run() {
27
- const {http, context} = this.conf;
28
- const {csp, "same-site": same_site = "Strict"} = http;
29
- this.csp = Object.keys(csp).reduce((policy_string, key) =>
30
- policy_string + `${key} ${csp[key]};`, "");
31
-
32
- this.server = await createServer(http, async (request, response) => {
33
- const session = await Session.get(request.headers.cookie, context);
34
- if (!session.has_cookie) {
35
- const {cookie} = session;
36
- response.setHeader("Set-Cookie", `${cookie}; SameSite=${same_site}`);
37
- }
38
- response.session = session;
39
- request.on("end", () =>
40
- this.try(parse(request.url).path, request, response)
41
- ).resume();
42
- });
43
- }
44
-
45
- async try(url, request, response) {
46
- try {
47
- await this.serve(url, request, response);
48
- } catch (error) {
49
- await response.session.log("red", error.message);
50
- response.writeHead(codes.InternalServerError);
51
- response.end();
52
- }
53
- }
54
-
55
- async serve_file(url, filename, file, response) {
56
- response.setHeader("Content-Type", mime(filename));
57
- response.setHeader("Etag", file.modified);
58
- await response.session.log("green", url);
59
- return stream(file.read_stream, response);
60
- }
61
-
62
- async serve(url, request, response) {
63
- const filename = join(this.conf.serve_from, url);
64
- const file = await new File(filename);
65
- return await file.is_file
66
- ? this.serve_file(url, filename, file, response)
67
- : this.serve_data(url, request, response);
68
- }
69
-
70
- async serve_data(pathname, request, response) {
71
- const {session} = response;
72
- const {path, params} = await session.route(this.conf.router, pathname);
73
- await session.log("green", `read ${pathname}`);
74
- const data = await session.run({path, params, pathname, "url": pathname});
75
- const handler = StaticServer[data.type] ?? this.index.bind(this);
76
- return handler(data, response);
77
- }
78
-
79
- index(data, response) {
80
- const {hashes, index} = this.conf;
81
- const src = "import {app} from './client/primate.js';"
82
- + `app.client.session.run(${JSON.stringify(data)});`;
83
- const integrity = `${algorithm}-${hash(src)}`;
84
- const view = `<script type="module" integrity="${integrity}">${src}`
85
- + "</script>";
86
- const file = index.replace("<first-view />", () => view);
87
- const script_src = Array.from(hashes)
88
- .concat([integrity])
89
- .reduce((hash_string, next_hash) => hash_string + ` '${next_hash}'`, "");
90
-
91
- response.setHeader("Content-Security-Policy",
92
- this.csp + `script-src 'self'${script_src};`);
93
- response.setHeader("Content-Type", "text/html");
94
- response.setHeader("Referrer-Policy", "same-origin");
95
- return stream(Readable.from([file]), response);
96
- }
97
-
98
- listen(port, host) {
99
- log.reset("on").yellow(`https://${host}:${port}`).nl();
100
- this.server.listen(port, host);
101
- }
102
-
103
- static redirect({location}, response) {
104
- response.setHeader("Location", location);
105
- response.writeHead(codes.Found);
106
- return response.end();
107
- }
108
-
109
- static return({payload}, response) {
110
- if (payload instanceof File) {
111
- return stream(payload.stream, response);
112
- } else {
113
- response.setHeader("Content-Type", "application/json");
114
- return stream(Readable.from([JSON.stringify(payload)]), response);
115
- }
116
- }
117
- }