primate 0.3.1 → 0.5.1
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.
- package/README.md +10 -7
- package/debris.json +5 -0
- package/jsconfig.json +8 -0
- package/package.json +16 -4
- package/source/client/Action.js +20 -22
- package/source/client/Client.js +13 -17
- package/source/client/Context.js +10 -16
- package/source/client/Element.js +18 -14
- package/source/client/Session.js +27 -0
- package/source/client/View.js +1 -2
- package/source/client/document.js +1 -1
- package/source/preset/primate.json +2 -8
- package/source/server/Action.js +57 -75
- package/source/server/App.js +27 -8
- package/source/server/Bundler.js +13 -16
- package/source/server/Context.js +63 -56
- package/source/server/{promises/Eager.js → EagerPromise.js} +4 -2
- package/source/server/File.js +5 -1
- package/source/server/Projector.js +86 -0
- package/source/server/Router.js +15 -24
- package/source/server/Session.js +9 -33
- package/source/server/attributes.js +3 -0
- package/source/server/conf.js +20 -26
- package/source/server/{Crypto.js → crypto.js} +0 -0
- package/source/server/domain/Domain.js +56 -46
- package/source/server/domain/Field.js +34 -36
- package/source/server/domain/Predicate.js +24 -0
- package/source/server/domain/domains.js +17 -1
- package/source/server/errors/InternalServer.js +1 -1
- package/source/server/errors/Predicate.js +1 -1
- package/source/server/errors.js +0 -1
- package/source/server/exports.js +10 -9
- package/source/server/{utils/extend_object.js → extend_object.js} +0 -0
- package/source/server/invariants.js +23 -10
- package/source/server/sanitize.js +10 -0
- package/source/server/servers/Dynamic.js +19 -13
- package/source/server/servers/Server.js +1 -1
- package/source/server/servers/Static.js +25 -20
- package/source/server/store/Store.js +13 -0
- package/source/server/types/Array.js +2 -2
- package/source/server/types/Boolean.js +2 -2
- package/source/server/types/Date.js +2 -2
- package/source/server/types/Domain.js +1 -6
- package/source/server/types/Instance.js +1 -1
- package/source/server/types/Number.js +2 -2
- package/source/server/types/Object.js +2 -2
- package/source/server/types/Primitive.js +1 -1
- package/source/server/types/Storeable.js +12 -19
- package/source/server/types/String.js +2 -2
- package/source/server/view/TreeNode.js +2 -0
- package/source/server/view/View.js +6 -1
- package/source/client/Base.js +0 -5
- package/source/server/Base.js +0 -35
- package/source/server/errors/Fallback.js +0 -1
- package/source/server/fallback.js +0 -11
- package/source/server/promises/Meta.js +0 -42
- package/source/server/promises.js +0 -2
package/source/server/Action.js
CHANGED
|
@@ -1,118 +1,100 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import {resolve} from "path";
|
|
2
|
+
import {default as EagerPromise, eager} from "./EagerPromise.js";
|
|
3
|
+
import View from "./view/View.js";
|
|
4
|
+
import Projector from "./Projector.js";
|
|
5
|
+
import {InternalServerError} from "./errors.js";
|
|
6
|
+
import {assert, defined} from "./invariants.js";
|
|
7
|
+
import sanitize from "./sanitize.js";
|
|
8
|
+
|
|
9
|
+
export default class Action {
|
|
10
|
+
static action = "index";
|
|
11
|
+
static namespace = "default";
|
|
12
|
+
static layout = "default";
|
|
13
|
+
|
|
14
|
+
constructor(request, session, context) {
|
|
15
|
+
this.request = request;
|
|
10
16
|
this.session = session;
|
|
17
|
+
this.context = EagerPromise.resolve(context);
|
|
11
18
|
}
|
|
12
19
|
|
|
13
|
-
static
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
let module;
|
|
19
|
-
const server = this.conf.paths.server;
|
|
20
|
-
const {namespace, action} = data.path;
|
|
20
|
+
static async new(request, session, context) {
|
|
21
|
+
const {namespace = this.namespace, action = this.action} = request.path;
|
|
22
|
+
assert(!namespace.includes("."), () => {
|
|
23
|
+
throw new InternalServerError("namespace may not include a dot");
|
|
24
|
+
});
|
|
21
25
|
const route = `${namespace}/${action}`;
|
|
26
|
+
const path = resolve(`${context.directory}/${route}.js`);
|
|
22
27
|
try {
|
|
23
|
-
|
|
28
|
+
const {"default": UserAction} = await import(path);
|
|
29
|
+
return new UserAction(request, session, context);
|
|
24
30
|
} catch (error) {
|
|
25
|
-
throw new Error(`route
|
|
31
|
+
throw new Error(`route \`${route}\` missing`);
|
|
26
32
|
}
|
|
27
|
-
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get Class() {
|
|
36
|
+
return this.constructor;
|
|
28
37
|
}
|
|
29
38
|
|
|
30
39
|
get layout() {
|
|
31
|
-
return this.Class
|
|
40
|
+
return this.Class.layout;
|
|
32
41
|
}
|
|
33
42
|
|
|
34
43
|
get path() {
|
|
35
|
-
|
|
44
|
+
const {path} = this.request;
|
|
45
|
+
const namespace = path.namespace ?? this.Class.namespace;
|
|
46
|
+
const action = path.action ?? this.Class.action;
|
|
47
|
+
const {_id} = path;
|
|
48
|
+
return {action, namespace, _id};
|
|
36
49
|
}
|
|
37
50
|
|
|
38
51
|
get params() {
|
|
39
|
-
return this.
|
|
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;}, {});
|
|
52
|
+
return this.request.params;
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
async run(type) {
|
|
56
|
+
defined(this[type]);
|
|
58
57
|
this.before && await this.before();
|
|
59
|
-
await this[type](
|
|
58
|
+
await this[type](sanitize(this.request.payload));
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return(data = {}) {
|
|
67
|
-
this.respond("return", () => ({"payload": data}));
|
|
61
|
+
return(payload = {}) {
|
|
62
|
+
this.respond("return", () => ({payload}));
|
|
68
63
|
}
|
|
69
64
|
|
|
70
65
|
view(data = {}, layout = true) {
|
|
71
66
|
this.respond("view", async () => {
|
|
72
|
-
const _view = await this.
|
|
73
|
-
const payload = await this.prepare(data, _view);
|
|
67
|
+
const {_view, payload} = await this.prepare(data);
|
|
74
68
|
const view = layout ? _view.file(this.layout) : _view.content;
|
|
75
69
|
return {view, payload};
|
|
76
70
|
});
|
|
77
71
|
}
|
|
78
72
|
|
|
79
73
|
update(data) {
|
|
80
|
-
this.respond("update", async () =>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return {payload};
|
|
84
|
-
});
|
|
74
|
+
this.respond("update", async () =>
|
|
75
|
+
({"payload": (await this.prepare(data)).payload})
|
|
76
|
+
);
|
|
85
77
|
}
|
|
86
78
|
|
|
87
79
|
redirect(location) {
|
|
88
80
|
this.respond("redirect", () => ({location}));
|
|
89
81
|
}
|
|
90
82
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
async prepare(data = {}, view) {
|
|
100
|
-
(await this.context).Class().prepare(this, data);
|
|
101
|
-
return MetaPromise(data, await view.elements(this.layout));
|
|
83
|
+
async prepare(data = {}) {
|
|
84
|
+
const route = `${this.path.namespace}/${this.path.action}`;
|
|
85
|
+
const path = await eager`${this.context.directory}/${route}`;
|
|
86
|
+
const _view = await View.new(path, await this.context.layouts);
|
|
87
|
+
await this.context.Class.prepare(this, data);
|
|
88
|
+
const payload = await new Projector(data,
|
|
89
|
+
await _view.elements(this.layout)).project(route);
|
|
90
|
+
return {payload, _view};
|
|
102
91
|
}
|
|
103
92
|
|
|
104
|
-
respond(type,
|
|
105
|
-
this
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
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;
|
|
93
|
+
respond(type, how) {
|
|
94
|
+
this.response = async () => {
|
|
95
|
+
const context = await this.context.name;
|
|
96
|
+
const {url} = this.request;
|
|
97
|
+
return {...await how(), type, context, ...this.path, url};
|
|
116
98
|
};
|
|
117
99
|
}
|
|
118
100
|
}
|
package/source/server/App.js
CHANGED
|
@@ -1,24 +1,37 @@
|
|
|
1
1
|
import {resolve} from "path";
|
|
2
|
-
import Base from "./Base.js";
|
|
3
2
|
import Bundler from "./Bundler.js";
|
|
4
3
|
import File from "./File.js";
|
|
5
4
|
import Router from "./Router.js";
|
|
6
5
|
import DynamicServer from "./servers/Dynamic.js";
|
|
7
6
|
import StaticServer from "./servers/Static.js";
|
|
7
|
+
import cache from "./cache.js";
|
|
8
8
|
import log from "./log.js";
|
|
9
|
+
import package_json from "../../package.json" assert {"type": "json"};
|
|
9
10
|
|
|
10
|
-
export default class App
|
|
11
|
-
constructor() {
|
|
12
|
-
|
|
13
|
-
this.router = new Router(this.routes, this.conf);
|
|
11
|
+
export default class App {
|
|
12
|
+
constructor(conf) {
|
|
13
|
+
this.conf = conf;
|
|
14
14
|
this.Bundler = Bundler;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
get routes() {
|
|
18
|
+
return cache(this, "routes", async () => {
|
|
19
|
+
try {
|
|
20
|
+
const path = `${this.conf.root}/routes.json`;
|
|
21
|
+
return (await import(path, {"assert": {"type": "json"}})).default;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
// local routes.json not required
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
17
29
|
async run() {
|
|
18
|
-
log.reset("Primate").yellow(
|
|
30
|
+
log.reset("Primate").yellow(package_json.version);
|
|
19
31
|
|
|
32
|
+
this.router = new Router(await this.routes, this.conf);
|
|
20
33
|
const {index, hashes} = await new this.Bundler(this.conf).bundle();
|
|
21
|
-
const router = this
|
|
34
|
+
const {router} = this;
|
|
22
35
|
|
|
23
36
|
const conf = {index, hashes, router,
|
|
24
37
|
"serve_from": this.conf.paths.public,
|
|
@@ -26,17 +39,23 @@ export default class App extends Base {
|
|
|
26
39
|
"key": File.read_sync(resolve(this.conf.http.ssl.key)),
|
|
27
40
|
"cert": File.read_sync(resolve(this.conf.http.ssl.cert)),
|
|
28
41
|
},
|
|
42
|
+
"context": this.conf.defaults.context,
|
|
29
43
|
};
|
|
30
44
|
this.static_server = new StaticServer(conf);
|
|
31
45
|
await this.static_server.run();
|
|
32
46
|
|
|
33
47
|
this.dynamic_server = new DynamicServer({router,
|
|
34
|
-
"path": this.base,
|
|
48
|
+
"path": this.conf.base,
|
|
35
49
|
"server": this.static_server.server,
|
|
50
|
+
"context": this.conf.defaults.context,
|
|
36
51
|
});
|
|
37
52
|
await this.dynamic_server.run();
|
|
38
53
|
|
|
39
54
|
const {port, host} = this.conf.http;
|
|
40
55
|
this.static_server.listen(port, host);
|
|
41
56
|
}
|
|
57
|
+
|
|
58
|
+
stop() {
|
|
59
|
+
this.static_server.close();
|
|
60
|
+
}
|
|
42
61
|
}
|
package/source/server/Bundler.js
CHANGED
|
@@ -2,7 +2,7 @@ import {dirname} from "path";
|
|
|
2
2
|
import {fileURLToPath} from "url";
|
|
3
3
|
import Directory from "./Directory.js";
|
|
4
4
|
import File from "./File.js";
|
|
5
|
-
import {algorithm, hash} from "./
|
|
5
|
+
import {algorithm, hash} from "./crypto.js";
|
|
6
6
|
|
|
7
7
|
const meta_url = fileURLToPath(import.meta.url);
|
|
8
8
|
const directory = dirname(meta_url);
|
|
@@ -25,17 +25,18 @@ const stringify_actions = ({context, domain, actions = []}, i, j) =>
|
|
|
25
25
|
actions.reduce((s, action, k) =>
|
|
26
26
|
s + stringify_action(context, domain, action, i, j, k), "");
|
|
27
27
|
|
|
28
|
-
const stringify_domain = (domain, i, j) =>
|
|
29
|
-
`routes["${
|
|
28
|
+
const stringify_domain = (context, domain, i, j) =>
|
|
29
|
+
`routes["${context}"]["${domain.domain}"] = {};\n`
|
|
30
30
|
+ stringify_actions(domain, i, j);
|
|
31
31
|
|
|
32
|
-
const stringify_domains = (domains, i) =>
|
|
33
|
-
domains.reduce((s, domain, j) =>
|
|
32
|
+
const stringify_domains = ({context, domains}, i) =>
|
|
33
|
+
domains.reduce((s, domain, j) =>
|
|
34
|
+
s + stringify_domain(context, domain, i, j), "");
|
|
34
35
|
|
|
35
36
|
const stringify_context = (context, i) =>
|
|
36
|
-
`import context${i} from "./${context
|
|
37
|
-
+ `contexts["${context
|
|
38
|
-
+ `routes["${context
|
|
37
|
+
`import context${i} from "./${context.context}/context.js";\n`
|
|
38
|
+
+ `contexts["${context.context}"] = context${i};\n`
|
|
39
|
+
+ `routes["${context.context}"] = {};\n`
|
|
39
40
|
+ stringify_domains(context, i) + "\n";
|
|
40
41
|
|
|
41
42
|
const s_exports = () =>
|
|
@@ -93,18 +94,16 @@ export default class Bundler {
|
|
|
93
94
|
const index_html = await File.read(`${paths.public}/${this.index}`);
|
|
94
95
|
await File.remove(`${paths.public}/${this.index}`);
|
|
95
96
|
|
|
96
|
-
const body =
|
|
97
|
+
const body = `return \`${index_html}\``;
|
|
97
98
|
const index = new Function("conf", "client", body)(this.conf, this.client);
|
|
98
99
|
|
|
99
100
|
return {index, "hashes": this.hashes};
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
async register_scripts() {
|
|
103
|
-
const {paths} = this.conf;
|
|
104
|
-
|
|
105
104
|
const scripts = await this.collect("client");
|
|
106
105
|
Promise.all(scripts.map(async script =>
|
|
107
|
-
this.register(script, await File.read(paths.public, script))
|
|
106
|
+
this.register(script, await File.read(this.conf.paths.public, script))
|
|
108
107
|
));
|
|
109
108
|
}
|
|
110
109
|
|
|
@@ -139,7 +138,7 @@ export default class Bundler {
|
|
|
139
138
|
client += `<script type="module" integrity="${integrity}" src="${src}">`
|
|
140
139
|
+ "</script>\n";
|
|
141
140
|
}
|
|
142
|
-
return client
|
|
141
|
+
return `${client}<first-view />`;
|
|
143
142
|
}
|
|
144
143
|
|
|
145
144
|
async write_exports() {
|
|
@@ -173,8 +172,6 @@ export default class Bundler {
|
|
|
173
172
|
.map(action => action.slice(0, -ending)),
|
|
174
173
|
})))));
|
|
175
174
|
|
|
176
|
-
return
|
|
177
|
-
? [context_domains]
|
|
178
|
-
: actions;
|
|
175
|
+
return context_domains;
|
|
179
176
|
}
|
|
180
177
|
}
|
package/source/server/Context.js
CHANGED
|
@@ -1,40 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {resolve} from "path";
|
|
2
2
|
import File from "./File.js";
|
|
3
|
-
import View from "./view/View.js";
|
|
4
3
|
import Action from "./Action.js";
|
|
5
4
|
import {InternalServerError} from "./errors.js";
|
|
5
|
+
import {assert} from "./invariants.js";
|
|
6
6
|
import cache from "./cache.js";
|
|
7
|
-
import {defined} from "./invariants.js";
|
|
8
|
-
import fallback from "./fallback.js";
|
|
9
7
|
import log from "./log.js";
|
|
10
8
|
|
|
11
|
-
export default class Context
|
|
9
|
+
export default class Context {
|
|
10
|
+
static directory = "server";
|
|
11
|
+
|
|
12
12
|
constructor(name) {
|
|
13
|
-
super();
|
|
14
13
|
this.name = name;
|
|
15
14
|
}
|
|
16
15
|
|
|
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
16
|
static before(action) {
|
|
39
17
|
return action;
|
|
40
18
|
}
|
|
@@ -45,46 +23,75 @@ export default class Context extends Base {
|
|
|
45
23
|
|
|
46
24
|
static get(name) {
|
|
47
25
|
return cache(this, name, async () => {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
throw new InternalServerError(
|
|
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);
|
|
51
34
|
}
|
|
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
35
|
});
|
|
58
36
|
}
|
|
59
37
|
|
|
60
|
-
get
|
|
61
|
-
return
|
|
38
|
+
get Class() {
|
|
39
|
+
return this.constructor;
|
|
62
40
|
}
|
|
63
41
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return new View(path, file, await this.layouts());
|
|
42
|
+
get directory() {
|
|
43
|
+
return `${this.Class.directory}/${this.name}`;
|
|
67
44
|
}
|
|
68
45
|
|
|
69
|
-
layouts() {
|
|
70
|
-
return cache(this, "layouts",
|
|
71
|
-
const
|
|
46
|
+
get layouts() {
|
|
47
|
+
return cache(this, "layouts", () => {
|
|
48
|
+
const ending = -5;
|
|
49
|
+
const path = `${this.directory}/layouts`;
|
|
72
50
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
layouts[file.slice(0,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return layouts;
|
|
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
|
+
}, {});
|
|
79
56
|
});
|
|
80
57
|
}
|
|
81
58
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
+
}
|
|
89
96
|
}
|
|
90
97
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import {inconstructible_function} from "./attributes.js";
|
|
2
|
+
|
|
1
3
|
const $promise = Symbol("#promise");
|
|
2
4
|
|
|
3
5
|
const handler = {
|
|
4
|
-
"get":
|
|
6
|
+
"get": (target, property) => {
|
|
5
7
|
const promise = target[$promise];
|
|
6
8
|
|
|
7
9
|
if (["then", "catch"].includes(property)) {
|
|
@@ -12,7 +14,7 @@ const handler = {
|
|
|
12
14
|
if (property === "bind") {
|
|
13
15
|
return result;
|
|
14
16
|
}
|
|
15
|
-
return
|
|
17
|
+
return inconstructible_function(result[property])
|
|
16
18
|
? result[property].bind(result)
|
|
17
19
|
: result[property];
|
|
18
20
|
}));
|
package/source/server/File.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import {join} from "path";
|
|
3
3
|
import Directory from "./Directory.js";
|
|
4
|
-
import EagerPromise from "./
|
|
4
|
+
import EagerPromise from "./EagerPromise.js";
|
|
5
5
|
|
|
6
6
|
const array = maybe => Array.isArray(maybe) ? maybe : [maybe];
|
|
7
7
|
|
|
@@ -31,6 +31,10 @@ export default class File {
|
|
|
31
31
|
return this.exists && !this.stats.isDirectory();
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
get stream() {
|
|
35
|
+
return this.read_stream;
|
|
36
|
+
}
|
|
37
|
+
|
|
34
38
|
get read_stream() {
|
|
35
39
|
return fs.createReadStream(this.path, {"flags": "r"});
|
|
36
40
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
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
|
+
}
|
package/source/server/Router.js
CHANGED
|
@@ -2,11 +2,10 @@ export default class Router {
|
|
|
2
2
|
constructor(routes = [], conf) {
|
|
3
3
|
this.conf = conf;
|
|
4
4
|
this.routes = {};
|
|
5
|
-
routes.forEach(({from, to, contexts}) =>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}));
|
|
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
|
+
}));
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
context(context) {
|
|
@@ -25,37 +24,29 @@ export default class Router {
|
|
|
25
24
|
const [path, search = ""] = pathname.split("?");
|
|
26
25
|
|
|
27
26
|
const route = this.route_by_context(context, path);
|
|
28
|
-
if (route
|
|
29
|
-
const replace = path.replace(route.from, route.to);
|
|
30
|
-
return `${replace}${replace.includes("?") ? "&" : "?&"}${search}`;
|
|
31
|
-
} else {
|
|
27
|
+
if (route === undefined) {
|
|
32
28
|
return pathname;
|
|
33
29
|
}
|
|
30
|
+
|
|
31
|
+
const replace = path.replace(route.from, route.to);
|
|
32
|
+
return `${replace}${replace.includes("?") ? "" : "?"}&${search}`;
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
resolve(pathname, context) {
|
|
37
|
-
return this.context(context)
|
|
38
|
-
?
|
|
39
|
-
: pathname;
|
|
36
|
+
return this.context(context) === undefined
|
|
37
|
+
? pathname
|
|
38
|
+
: this.resolve_by_context(pathname, context);
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
async route(pathname, context) {
|
|
43
42
|
const resolved = await this.resolve(this.debase(pathname), context);
|
|
44
43
|
const url = new URL(`https://primatejs.com/${resolved}`);
|
|
45
44
|
const parts = url.pathname.split("/").filter(part => part !== "");
|
|
46
|
-
const
|
|
45
|
+
const [namespace, action, _id] = parts;
|
|
47
46
|
return {
|
|
48
|
-
"pathname": url.pathname,
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"path": {
|
|
52
|
-
"namespace": parts[0] ?? namespace,
|
|
53
|
-
"action": parts[1] ?? action,
|
|
54
|
-
"_id": parts[2],
|
|
55
|
-
},
|
|
56
|
-
"params": {
|
|
57
|
-
...Object.fromEntries(url.searchParams),
|
|
58
|
-
},
|
|
47
|
+
"pathname": url.pathname, resolved, parts,
|
|
48
|
+
"path": {namespace, action, _id},
|
|
49
|
+
"params": {...Object.fromEntries(url.searchParams)},
|
|
59
50
|
};
|
|
60
51
|
}
|
|
61
52
|
}
|