primate 0.3.1 → 0.4.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/package.json +9 -3
- package/source/client/Action.js +12 -15
- package/source/client/Client.js +11 -15
- package/source/client/Context.js +9 -15
- package/source/client/Element.js +8 -3
- package/source/client/Session.js +27 -0
- package/source/client/document.js +1 -1
- package/source/preset/primate.json +2 -6
- package/source/server/Action.js +53 -75
- package/source/server/App.js +26 -7
- package/source/server/Bundler.js +11 -14
- package/source/server/Context.js +64 -56
- package/source/server/{promises/Eager.js → EagerPromise.js} +3 -1
- package/source/server/File.js +1 -1
- package/source/server/Projector.js +86 -0
- package/source/server/Router.js +7 -9
- package/source/server/Session.js +9 -33
- package/source/server/attributes.js +2 -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 +16 -13
- package/source/server/domain/domains.js +16 -0
- package/source/server/errors.js +0 -1
- package/source/server/exports.js +9 -8
- package/source/server/{utils/extend_object.js → extend_object.js} +0 -0
- package/source/server/invariants.js +0 -2
- package/source/server/sanitize.js +5 -0
- package/source/server/servers/Dynamic.js +19 -13
- package/source/server/servers/Static.js +24 -19
- package/source/server/store/Store.js +13 -0
- package/source/server/types/Domain.js +0 -5
- package/source/server/types/Storeable.js +6 -7
- package/source/server/view/TreeNode.js +2 -0
- package/source/server/view/View.js +5 -0
- 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/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, defined} 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,76 @@ 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
|
+
defined(action[type]);
|
|
91
|
+
await this.Class.before(action);
|
|
92
|
+
await action.run(type);
|
|
93
|
+
return action.response();
|
|
94
|
+
} catch(error) {
|
|
95
|
+
return this.catch(error, new Action(request, session, this));
|
|
96
|
+
}
|
|
89
97
|
}
|
|
90
98
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {inconstructible_function} from "./attributes.js";
|
|
2
|
+
|
|
1
3
|
const $promise = Symbol("#promise");
|
|
2
4
|
|
|
3
5
|
const handler = {
|
|
@@ -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
|
@@ -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)[0];
|
|
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_cache(fields[key], key, document, projection)
|
|
44
|
+
: this.by_store(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)[0];
|
|
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(route) {
|
|
72
|
+
return (await Promise.all(Object.keys(this.documents).map(async key => {
|
|
73
|
+
const resolved = this.projection[key] !== undefined
|
|
74
|
+
? await this.resolve(this.documents[key], this.projection[key])
|
|
75
|
+
: undefined;
|
|
76
|
+
return {key, resolved};
|
|
77
|
+
}))).reduce((projected, {key, resolved}) => {
|
|
78
|
+
if (resolved !== undefined) {
|
|
79
|
+
projected[key] = resolved;
|
|
80
|
+
} else {
|
|
81
|
+
log.yellow(` \`${key}\` not projected`).nl();
|
|
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) {
|
|
@@ -27,7 +26,7 @@ export default class Router {
|
|
|
27
26
|
const route = this.route_by_context(context, path);
|
|
28
27
|
if (route !== undefined) {
|
|
29
28
|
const replace = path.replace(route.from, route.to);
|
|
30
|
-
return `${replace}${replace.includes("?") ? "
|
|
29
|
+
return `${replace}${replace.includes("?") ? "" : "?"}&${search}`;
|
|
31
30
|
} else {
|
|
32
31
|
return pathname;
|
|
33
32
|
}
|
|
@@ -43,14 +42,13 @@ export default class Router {
|
|
|
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 {namespace, action} = this.conf.defaults;
|
|
47
45
|
return {
|
|
48
46
|
"pathname": url.pathname,
|
|
49
47
|
"resolved": resolved,
|
|
50
48
|
"parts": parts,
|
|
51
49
|
"path": {
|
|
52
|
-
"namespace": parts[0]
|
|
53
|
-
"action": parts[1]
|
|
50
|
+
"namespace": parts[0],
|
|
51
|
+
"action": parts[1],
|
|
54
52
|
"_id": parts[2],
|
|
55
53
|
},
|
|
56
54
|
"params": {
|
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,7 @@ 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";
|
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
|
|
@@ -1,26 +1,34 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {resolve as path_resolve} from "path";
|
|
2
2
|
import Field from "./Field.js";
|
|
3
3
|
import {PredicateError} from "../errors.js";
|
|
4
|
-
import
|
|
4
|
+
import EagerPromise from "../EagerPromise.js";
|
|
5
|
+
import Store from "../store/Store.js";
|
|
5
6
|
import cache from "../cache.js";
|
|
6
|
-
import
|
|
7
|
+
import DomainType from "../types/Domain.js";
|
|
8
|
+
import {random} from "../crypto.js";
|
|
7
9
|
|
|
8
10
|
const length = 12;
|
|
9
|
-
const preset = "../../preset/data/stores";
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
export default class Domain {
|
|
13
|
+
static stores_directory = "data/stores";
|
|
14
|
+
static store_file = "default.js";
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
static {
|
|
17
|
+
// avoid transitive cyclic dependency between Domain and Field
|
|
18
|
+
DomainType.instance = Domain;
|
|
19
|
+
this.cache = {};
|
|
20
|
+
}
|
|
17
21
|
|
|
22
|
+
constructor(document) {
|
|
18
23
|
const errors = {};
|
|
19
|
-
|
|
24
|
+
this.define("_id", {
|
|
20
25
|
"type": String,
|
|
21
26
|
"predicates": ["unique"],
|
|
22
27
|
"in": value => value ?? random(length).toString("hex"),
|
|
23
28
|
});
|
|
29
|
+
return new Proxy(this, {"get": (target, property, receiver) =>
|
|
30
|
+
Reflect.get(target, property, receiver) ?? target.#proxy(property)
|
|
31
|
+
}).set({...document, errors})
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
get Class() {
|
|
@@ -39,20 +47,9 @@ export default class Domain extends Base {
|
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
static get store() {
|
|
42
|
-
return cache(this, "store", async () =>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
store = await import(create_path(this.conf.paths.data.stores));
|
|
47
|
-
} catch(error) {
|
|
48
|
-
store = await import(create_path(preset));
|
|
49
|
-
}
|
|
50
|
-
return store.default.open();
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
static get store_file() {
|
|
55
|
-
return this.conf.defaults.store;
|
|
50
|
+
return EagerPromise.resolve(cache(this, "store", async () =>
|
|
51
|
+
Store.get(this.stores_directory, this.store_file)
|
|
52
|
+
));
|
|
56
53
|
}
|
|
57
54
|
|
|
58
55
|
static get collection() {
|
|
@@ -97,13 +94,27 @@ export default class Domain extends Base {
|
|
|
97
94
|
return Object.assign(this, document);
|
|
98
95
|
}
|
|
99
96
|
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
#proxy(property) {
|
|
98
|
+
return typeof property === "string" ? this.#link(property) : this[property];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#link(name) {
|
|
102
102
|
const field = this.fields[`${name}_id`];
|
|
103
|
-
|
|
103
|
+
if (field?.is_domain) {
|
|
104
|
+
const collection = field.Type.collection;
|
|
105
|
+
const cache = this.Class.cache;
|
|
106
|
+
if (cache[collection] === undefined) {
|
|
107
|
+
cache[collection] = {};
|
|
108
|
+
}
|
|
109
|
+
if (cache[collection][this[`${name}_id`]] === undefined) {
|
|
110
|
+
cache[collection][this[`${name}_id`]] = field.by_id(this[`${name}_id`]);
|
|
111
|
+
}
|
|
112
|
+
return cache[collection][this[`${name}_id`]];
|
|
113
|
+
} else {
|
|
114
|
+
return undefined
|
|
115
|
+
}
|
|
104
116
|
}
|
|
105
117
|
|
|
106
|
-
// #serialize
|
|
107
118
|
// Serializing is done from the instance's point of view.
|
|
108
119
|
async serialize() {
|
|
109
120
|
const {properties, fields} = this;
|
|
@@ -116,7 +127,6 @@ export default class Domain extends Base {
|
|
|
116
127
|
}, {});
|
|
117
128
|
}
|
|
118
129
|
|
|
119
|
-
// #deserialize
|
|
120
130
|
// Deserializing is done from the class's point of view.
|
|
121
131
|
static deserialize(serialized) {
|
|
122
132
|
const fields = this._fields;
|
|
@@ -138,6 +148,7 @@ export default class Domain extends Base {
|
|
|
138
148
|
get collection() {
|
|
139
149
|
return this.Class.collection;
|
|
140
150
|
}
|
|
151
|
+
|
|
141
152
|
get properties() {
|
|
142
153
|
return this.Class.properties;
|
|
143
154
|
}
|
|
@@ -171,9 +182,12 @@ export default class Domain extends Base {
|
|
|
171
182
|
async savewith(delta, after = () => undefined) {
|
|
172
183
|
const verified = await this.verify(delta);
|
|
173
184
|
if (verified) {
|
|
174
|
-
const store = await this.store;
|
|
175
185
|
const document = await this.serialize();
|
|
176
|
-
await store.save(this.collection, {"_id": document._id}, document);
|
|
186
|
+
await this.store.save(this.collection, {"_id": document._id}, document);
|
|
187
|
+
const cache = this.Class.cache;
|
|
188
|
+
if (cache[this.collection]?.[document._id] !== undefined) {
|
|
189
|
+
delete cache[this.collection][document._id];
|
|
190
|
+
}
|
|
177
191
|
await after();
|
|
178
192
|
}
|
|
179
193
|
return verified;
|
|
@@ -197,26 +211,24 @@ export default class Domain extends Base {
|
|
|
197
211
|
return this;
|
|
198
212
|
}
|
|
199
213
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return store.delete(this.collection, {"_id": this._id});
|
|
214
|
+
delete() {
|
|
215
|
+
return this.store.delete(this.collection, {"_id": this._id});
|
|
203
216
|
}
|
|
204
217
|
|
|
205
|
-
static
|
|
206
|
-
|
|
207
|
-
return store.delete(this.collection, criteria);
|
|
218
|
+
static delete(criteria) {
|
|
219
|
+
return this.store.delete(this.collection, criteria);
|
|
208
220
|
}
|
|
209
221
|
|
|
210
222
|
static by_id(_id) {
|
|
211
223
|
return new EagerPromise(async resolve => {
|
|
212
|
-
const result = await
|
|
213
|
-
resolve(result
|
|
224
|
+
const result = await this.store.find(this.collection, {"_id": await _id});
|
|
225
|
+
resolve(result.length > 0 ? this.deserialize(result[0]) : undefined);
|
|
214
226
|
});
|
|
215
227
|
}
|
|
216
228
|
|
|
217
|
-
static first(criteria) {
|
|
229
|
+
static first(criteria, options) {
|
|
218
230
|
return new EagerPromise(async resolve => {
|
|
219
|
-
const result = await
|
|
231
|
+
const result = await this.store.one(this.collection, criteria, options);
|
|
220
232
|
resolve(result === undefined ? undefined : this.deserialize(result));
|
|
221
233
|
});
|
|
222
234
|
}
|
|
@@ -230,14 +242,12 @@ export default class Domain extends Base {
|
|
|
230
242
|
}
|
|
231
243
|
|
|
232
244
|
static async find(criteria, options) {
|
|
233
|
-
const
|
|
234
|
-
const results = await store.find(this.collection, criteria, options);
|
|
245
|
+
const results = await this.store.find(this.collection, criteria, options);
|
|
235
246
|
return results.map(result => this.deserialize(result));
|
|
236
247
|
}
|
|
237
248
|
|
|
238
|
-
static
|
|
239
|
-
|
|
240
|
-
return store.count(this.collection, criteria);
|
|
249
|
+
static count(criteria) {
|
|
250
|
+
return this.store.count(this.collection, criteria);
|
|
241
251
|
}
|
|
242
252
|
|
|
243
253
|
static async exists(criteria) {
|