primate 0.2.0 → 0.5.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.
- package/README.md +11 -5
- package/package.json +9 -3
- 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 +21 -15
- 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 -6
- package/source/server/Action.js +56 -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 +6 -2
- 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 +60 -48
- package/source/server/domain/Field.js +34 -36
- package/source/server/domain/Predicate.js +24 -0
- package/source/server/domain/domains.js +16 -0
- 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/servers/content-security-policy.json +0 -1
- 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/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,10 +31,14 @@ export default class File {
|
|
|
31
31
|
return this.exists && !this.stats.isDirectory();
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
get
|
|
34
|
+
get read_stream() {
|
|
35
35
|
return fs.createReadStream(this.path, {"flags": "r"});
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
get write_stream() {
|
|
39
|
+
return fs.createWriteStream(this.path);
|
|
40
|
+
}
|
|
41
|
+
|
|
38
42
|
remove() {
|
|
39
43
|
return new Promise((resolve, reject) => fs.rm(this.path,
|
|
40
44
|
{"recursive": true, "force": true},
|
|
@@ -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(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
|
}
|
package/source/server/Session.js
CHANGED
|
@@ -1,47 +1,37 @@
|
|
|
1
1
|
import Context from "./Context.js";
|
|
2
2
|
import Domain from "./domain/Domain.js";
|
|
3
|
-
import {FallbackError, InternalServerError} from "./errors.js";
|
|
4
3
|
|
|
5
|
-
const extract_id = cookie_header => cookie_header
|
|
6
|
-
|
|
7
|
-
: cookie_header
|
|
8
|
-
.split(";")
|
|
9
|
-
.filter(cookie => cookie.includes("session_id="))[0]
|
|
10
|
-
?.split("=")[1];
|
|
4
|
+
const extract_id = cookie_header => cookie_header
|
|
5
|
+
?.split(";").filter(text => text.includes("session_id="))[0]?.split("=")[1];
|
|
11
6
|
|
|
12
7
|
export default class Session extends Domain {
|
|
13
8
|
static get fields() {
|
|
14
|
-
const default_context = this.conf.defaults.context;
|
|
15
9
|
return {
|
|
16
10
|
"?data": Object,
|
|
17
|
-
"context" :
|
|
11
|
+
"context" : String,
|
|
18
12
|
"created": value => value ?? new Date(),
|
|
19
13
|
};
|
|
20
14
|
}
|
|
21
15
|
|
|
22
16
|
async log(color, message) {
|
|
23
|
-
(await this.
|
|
17
|
+
(await Context.get(this.context)).log(color, message);
|
|
24
18
|
}
|
|
25
19
|
|
|
26
20
|
route(router, url) {
|
|
27
21
|
return router.route(url, this.context);
|
|
28
22
|
}
|
|
29
23
|
|
|
30
|
-
get
|
|
31
|
-
return Context.get(this.context);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
static async get(cookie_header) {
|
|
24
|
+
static async get(cookie_header, default_context) {
|
|
35
25
|
const session = await Session.touch({"_id": extract_id(cookie_header)});
|
|
36
26
|
if (session.new) {
|
|
37
|
-
await session.save();
|
|
27
|
+
await session.save({"context": default_context});
|
|
38
28
|
session.has_cookie = false;
|
|
39
29
|
}
|
|
40
30
|
return session;
|
|
41
31
|
}
|
|
42
32
|
|
|
43
33
|
get cookie() {
|
|
44
|
-
return `session_id=${this._id}; Secure; HttpOnly; Path=/`;
|
|
34
|
+
return `session_id=${this._id}; Secure; SameSite=Strict; HttpOnly; Path=/`;
|
|
45
35
|
}
|
|
46
36
|
|
|
47
37
|
async switch_context(context, data = {}) {
|
|
@@ -49,21 +39,7 @@ export default class Session extends Domain {
|
|
|
49
39
|
return this;
|
|
50
40
|
}
|
|
51
41
|
|
|
52
|
-
async run(
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
context = await this.actual_context;
|
|
56
|
-
response = await context.run(data, this);
|
|
57
|
-
} catch (error) {
|
|
58
|
-
if (error instanceof FallbackError) {
|
|
59
|
-
// do nothing
|
|
60
|
-
} else if (error instanceof InternalServerError) {
|
|
61
|
-
// cannot proceed, throw up
|
|
62
|
-
throw error;
|
|
63
|
-
} else {
|
|
64
|
-
return context.errored(data, error, this);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return response;
|
|
42
|
+
async run(request) {
|
|
43
|
+
return (await Context.get(this.context)).run(request, this);
|
|
68
44
|
}
|
|
69
45
|
}
|
|
@@ -7,5 +7,8 @@ export const constructible = value => {
|
|
|
7
7
|
}
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
+
export const inconstructible_function = value =>
|
|
11
|
+
typeof value === "function" && !constructible(value);
|
|
10
12
|
export const numeric = value => !isNaN(parseFloat(value)) && isFinite(value);
|
|
11
13
|
export const boolish = value => value === "true" || value === "false";
|
|
14
|
+
export const nullish = value => value === undefined || value === null;
|
package/source/server/conf.js
CHANGED
|
@@ -1,33 +1,27 @@
|
|
|
1
1
|
import {join, resolve} from "path";
|
|
2
|
-
import log from "./log.js";
|
|
3
2
|
import cache from "./cache.js";
|
|
4
3
|
import File from "./File.js";
|
|
5
|
-
import extend_object from "./
|
|
4
|
+
import extend_object from "./extend_object.js";
|
|
6
5
|
import primate_json from "../preset/primate.json" assert {"type": "json" };
|
|
7
6
|
|
|
8
|
-
const qualify = (root, paths) =>
|
|
9
|
-
|
|
10
|
-
for (const key in paths) {
|
|
7
|
+
const qualify = (root, paths) =>
|
|
8
|
+
Object.keys(paths).reduce((sofar, key) => {
|
|
11
9
|
const value = paths[key];
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
return object;
|
|
19
|
-
}
|
|
10
|
+
sofar[key] = typeof value === "string"
|
|
11
|
+
? join(root, value)
|
|
12
|
+
: qualify(`${root}/${key}`, value);
|
|
13
|
+
return sofar;
|
|
14
|
+
}, {});
|
|
20
15
|
|
|
21
|
-
export default (file = "primate.json") =>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
});
|
|
16
|
+
export default (file = "primate.json") => cache("conf", file, () => {
|
|
17
|
+
let conf = primate_json;
|
|
18
|
+
const root = resolve();
|
|
19
|
+
try {
|
|
20
|
+
conf = extend_object(conf, JSON.parse(File.read_sync(join(root, file))));
|
|
21
|
+
} catch (error) {
|
|
22
|
+
// local primate.json not required
|
|
23
|
+
}
|
|
24
|
+
conf.paths = qualify(root, conf.paths);
|
|
25
|
+
conf.root = root;
|
|
26
|
+
return conf;
|
|
27
|
+
});
|
|
File without changes
|