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
|
@@ -30,7 +30,7 @@ export default class Field {
|
|
|
30
30
|
this.property = property;
|
|
31
31
|
this.definition = parse(definition);
|
|
32
32
|
this.options = options ?? {"transient": false, "optional": false};
|
|
33
|
-
is_constructible(this.
|
|
33
|
+
is_constructible(this.Type);
|
|
34
34
|
instances(this.type.prototype, Storeable, "type must extend Storeable");
|
|
35
35
|
is_array(this.predicates);
|
|
36
36
|
}
|
|
@@ -46,15 +46,19 @@ export default class Field {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
get type() {
|
|
49
|
-
return builtins[this.
|
|
49
|
+
return builtins[this.Type] ?? this.custom;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
get custom() {
|
|
53
|
-
return this.is_domain ? DomainType : this.
|
|
53
|
+
return this.is_domain ? DomainType : this.Type;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
get is_domain() {
|
|
57
|
-
return this.
|
|
57
|
+
return this.Type.prototype instanceof DomainType.instance;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get Type(){
|
|
61
|
+
return this.definition.type;
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
get predicates() {
|
|
@@ -62,7 +66,7 @@ export default class Field {
|
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
by_id(id) {
|
|
65
|
-
return this.
|
|
69
|
+
return this.Type.by_id(id);
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
in(property, document) {
|
|
@@ -84,11 +88,10 @@ export default class Field {
|
|
|
84
88
|
return this.options.optional ? true : "Must not be empty";
|
|
85
89
|
}
|
|
86
90
|
|
|
87
|
-
async verify_defined(property,
|
|
91
|
+
async verify_defined(property, document) {
|
|
88
92
|
try {
|
|
89
93
|
const predicates = this.override_predicates(document);
|
|
90
|
-
|
|
91
|
-
this.definition.type);
|
|
94
|
+
await this.type.verify(property, document, predicates, this.Type);
|
|
92
95
|
return true;
|
|
93
96
|
} catch (error) {
|
|
94
97
|
if (error instanceof PredicateError) {
|
|
@@ -99,17 +102,17 @@ export default class Field {
|
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
async verify(property, document) {
|
|
102
|
-
|
|
103
|
-
return
|
|
105
|
+
document[property] = await this.in(property, document);
|
|
106
|
+
return document[property] === undefined
|
|
104
107
|
? this.verify_undefined()
|
|
105
|
-
: this.verify_defined(property,
|
|
108
|
+
: this.verify_defined(property, document);
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
serialize(value) {
|
|
109
|
-
return value
|
|
112
|
+
return value === undefined ? undefined : this.type.serialize(value);
|
|
110
113
|
}
|
|
111
114
|
|
|
112
115
|
deserialize(value) {
|
|
113
|
-
return value
|
|
116
|
+
return value === undefined ? undefined : this.type.deserialize(value);
|
|
114
117
|
}
|
|
115
118
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import conf from "../conf.js";
|
|
2
2
|
import File from "../File.js";
|
|
3
3
|
import Field from "./Field.js";
|
|
4
|
+
import Domain from "./Domain.js";
|
|
4
5
|
|
|
5
6
|
const domains = {};
|
|
6
7
|
const base = conf().paths.data.domains;
|
|
@@ -12,4 +13,19 @@ for (const domain of await new File(base).list(".js")) {
|
|
|
12
13
|
});
|
|
13
14
|
}
|
|
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
|
+
|
|
15
31
|
export default domains;
|
package/source/server/errors.js
CHANGED
package/source/server/exports.js
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
|
+
import conf from "./conf.js";
|
|
2
|
+
import App from "./App.js";
|
|
3
|
+
|
|
4
|
+
export {App};
|
|
1
5
|
export {default as Action} from "./Action.js";
|
|
2
6
|
export {default as Bundler} from "./Bundler.js";
|
|
3
7
|
export {default as Context} from "./Context.js";
|
|
4
8
|
export {default as Directory} from "./Directory.js";
|
|
5
9
|
export {default as File} from "./File.js";
|
|
10
|
+
export {default as EagerPromise, eager} from "./EagerPromise.js" ;
|
|
6
11
|
|
|
7
12
|
export {default as Domain} from "./domain/Domain.js";
|
|
8
13
|
export {default as domains} from "./domain/domains.js";
|
|
9
14
|
export {default as Storeable} from "./types/Storeable.js";
|
|
10
15
|
|
|
11
16
|
export * from "./errors.js";
|
|
12
|
-
export * from "./promises.js" ;
|
|
13
17
|
|
|
14
18
|
export {default as MemoryStore} from "./store/Memory.js";
|
|
15
19
|
export {default as Store} from "./store/Store.js";
|
|
16
20
|
|
|
17
21
|
export {assert, defined} from "./invariants.js";
|
|
18
22
|
export {default as log} from "./log.js";
|
|
19
|
-
export {default as extend_object} from "./
|
|
20
|
-
export {default as
|
|
21
|
-
|
|
22
|
-
import App from "./App.js";
|
|
23
|
+
export {default as extend_object} from "./extend_object.js";
|
|
24
|
+
export {default as sanitize} from "./sanitize.js";
|
|
23
25
|
|
|
24
|
-
const app = new App();
|
|
25
|
-
const conf = app.conf;
|
|
26
|
+
const app = new App(conf());
|
|
26
27
|
|
|
27
|
-
export {app
|
|
28
|
+
export {app};
|
|
File without changes
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import {FallbackError} from "./errors.js";
|
|
2
1
|
import {constructible} from "./attributes.js";
|
|
3
2
|
|
|
4
3
|
const errored = error => {
|
|
5
4
|
if (typeof error === "function") { // fallback
|
|
6
5
|
error();
|
|
7
|
-
throw new FallbackError();
|
|
8
6
|
} else { // error
|
|
9
7
|
throw new Error(error);
|
|
10
8
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export default (payload = {}) => Object.keys(payload)
|
|
2
|
+
.map(key => ({key, "value": payload[key].toString().trim()}))
|
|
3
|
+
.map(datum => { datum.value = datum.value !== "" ? datum.value : undefined;
|
|
4
|
+
return datum; })
|
|
5
|
+
.reduce((data, {key, value}) => { data[key] = value; return data; }, {});
|
|
@@ -12,7 +12,8 @@ export default class DynamicServer extends Server {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
async connected(socket, request) {
|
|
15
|
-
|
|
15
|
+
const {context} = this.conf;
|
|
16
|
+
socket.session = await Session.get(request.headers.cookie, context);
|
|
16
17
|
socket.on("message", event => this.try(socket, event));
|
|
17
18
|
// inform client that we're connected and it can start sending messages
|
|
18
19
|
socket.send("open");
|
|
@@ -20,31 +21,36 @@ export default class DynamicServer extends Server {
|
|
|
20
21
|
|
|
21
22
|
async try(socket, event) {
|
|
22
23
|
const {session} = socket;
|
|
23
|
-
const
|
|
24
|
-
const url = parsed.pathname + (parsed.search ?? "");
|
|
24
|
+
const request = JSON.parse(event);
|
|
25
25
|
try {
|
|
26
|
-
await this.onmessage(
|
|
27
|
-
await session.log("green", `${
|
|
26
|
+
await this.onmessage(request, socket);
|
|
27
|
+
await session.log("green", `${request.type} ${request.url}`);
|
|
28
28
|
} catch(error) {
|
|
29
29
|
await session.log("red", error.message);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
async onmessage(
|
|
33
|
+
async onmessage(request, socket) {
|
|
34
34
|
const {router} = this.conf;
|
|
35
35
|
const {session} = socket;
|
|
36
|
-
const {path, params} = await session.route(router, url);
|
|
37
|
-
const
|
|
38
|
-
|
|
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.send(socket, response);
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
static send(socket, response) {
|
|
44
|
+
socket.send(JSON.stringify(response));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
redirect(socket, {location}) {
|
|
48
|
+
if (location.startsWith("https://")) {
|
|
43
49
|
// redirect externally
|
|
44
50
|
return undefined;
|
|
45
51
|
} else {
|
|
46
|
-
const
|
|
47
|
-
this.try(socket, JSON.stringify({pathname, "type": "read"}));
|
|
52
|
+
const url = location;
|
|
53
|
+
this.try(socket, JSON.stringify({"pathname": url, url, "type": "read"}));
|
|
48
54
|
return true;
|
|
49
55
|
}
|
|
50
56
|
}
|
|
@@ -6,7 +6,8 @@ import {parse} from "url";
|
|
|
6
6
|
import Server from "./Server.js";
|
|
7
7
|
import Session from "../Session.js";
|
|
8
8
|
import File from "../File.js";
|
|
9
|
-
import {algorithm, hash} from "../
|
|
9
|
+
import {algorithm, hash} from "../crypto.js";
|
|
10
|
+
import {assert} from "../invariants.js";
|
|
10
11
|
import log from "../log.js";
|
|
11
12
|
import codes from "./http-codes.json" assert {"type": "json"};
|
|
12
13
|
import mimes from "./mimes.json" assert {"type": "json"};
|
|
@@ -28,46 +29,50 @@ const stream = (from, response) => {
|
|
|
28
29
|
|
|
29
30
|
export default class StaticServer extends Server {
|
|
30
31
|
async run() {
|
|
31
|
-
const {http} = this.conf;
|
|
32
|
+
const {http, context} = this.conf;
|
|
32
33
|
|
|
33
34
|
this.server = await createServer(http, async (request, response) => {
|
|
34
|
-
const session = await Session.get(request.headers.cookie);
|
|
35
|
+
const session = await Session.get(request.headers.cookie, context);
|
|
35
36
|
if (!session.has_cookie) {
|
|
36
37
|
response.setHeader("Set-Cookie", session.cookie);
|
|
37
38
|
}
|
|
39
|
+
response.session = session;
|
|
38
40
|
request.on("end", () =>
|
|
39
|
-
this.try(parse(request.url).path,
|
|
41
|
+
this.try(parse(request.url).path, request, response)
|
|
40
42
|
).resume();
|
|
41
43
|
});
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
async try(url,
|
|
46
|
+
async try(url, request, response) {
|
|
45
47
|
try {
|
|
46
|
-
await this.serve(url,
|
|
48
|
+
await this.serve(url, request, response);
|
|
47
49
|
} catch (error) {
|
|
48
|
-
await session.log("red", error.message);
|
|
50
|
+
await response.session.log("red", error.message);
|
|
49
51
|
response.writeHead(codes.InternalServerError);
|
|
50
52
|
response.end();
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
async
|
|
56
|
+
async serve_file(url, filename, file, response) {
|
|
57
|
+
response.setHeader("Content-Type", mime(filename));
|
|
58
|
+
response.setHeader("Etag", file.modified);
|
|
59
|
+
await response.session.log("green", url);
|
|
60
|
+
return stream(file.read_stream, response);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async serve(url, request, response) {
|
|
55
64
|
const filename = join(this.conf.serve_from, url);
|
|
56
65
|
const file = await new File(filename);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
await session.log("green", url);
|
|
61
|
-
return stream(file.read_stream, response);
|
|
62
|
-
} else {
|
|
63
|
-
return this.serve_data(url, session, request, response);
|
|
64
|
-
}
|
|
66
|
+
return await file.is_file
|
|
67
|
+
? this.serve_file(url, filename, file, response)
|
|
68
|
+
: this.serve_data(url, request, response);
|
|
65
69
|
}
|
|
66
70
|
|
|
67
|
-
async serve_data(pathname,
|
|
71
|
+
async serve_data(pathname, request, response) {
|
|
72
|
+
const {session} = response;
|
|
68
73
|
const {path, params} = await session.route(this.conf.router, pathname);
|
|
69
74
|
await session.log("green", `read ${pathname}`);
|
|
70
|
-
const data = await session.run({path, params, pathname});
|
|
75
|
+
const data = await session.run({path, params, pathname, "url": pathname});
|
|
71
76
|
const handler = StaticServer[data.type] ?? this.index.bind(this);
|
|
72
77
|
return handler(data, response);
|
|
73
78
|
}
|
|
@@ -75,7 +80,7 @@ export default class StaticServer extends Server {
|
|
|
75
80
|
index(data, response) {
|
|
76
81
|
const {hashes, index} = this.conf;
|
|
77
82
|
const src = "import {app} from './client/primate.js';"
|
|
78
|
-
+ `app.client.
|
|
83
|
+
+ `app.client.session.run(${JSON.stringify(data)});`;
|
|
79
84
|
const integrity = `${algorithm}-${hash(src)}`;
|
|
80
85
|
const view = `<script type="module" integrity="${integrity}">${src}`
|
|
81
86
|
+ "</script>";
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {resolve} from "path";
|
|
2
|
+
const preset = "../../preset/data/stores";
|
|
3
|
+
|
|
1
4
|
export default class Store {
|
|
2
5
|
constructor(conf = {}) {
|
|
3
6
|
this.conf = conf;
|
|
@@ -14,4 +17,14 @@ export default class Store {
|
|
|
14
17
|
open() {
|
|
15
18
|
return this;
|
|
16
19
|
}
|
|
20
|
+
|
|
21
|
+
static async get(directory, file) {
|
|
22
|
+
let store;
|
|
23
|
+
try {
|
|
24
|
+
store = await import(resolve(`${directory}/${file}`));
|
|
25
|
+
} catch(error) {
|
|
26
|
+
store = await import(`${preset}/${file}`);
|
|
27
|
+
}
|
|
28
|
+
return store.default.open();
|
|
29
|
+
}
|
|
17
30
|
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import {PredicateError} from "../errors.js";
|
|
2
2
|
|
|
3
3
|
export default class {
|
|
4
|
-
static async verify(property,
|
|
5
|
-
|
|
6
|
-
if (!await this.is(
|
|
4
|
+
static async verify(property, document, predicates, type) {
|
|
5
|
+
document[property] = this.coerce(document[property]);
|
|
6
|
+
if (!await this.is(document[property], type)) {
|
|
7
7
|
throw new PredicateError(this.type_error(type));
|
|
8
8
|
}
|
|
9
|
-
await this.has(property,
|
|
10
|
-
return coerced;
|
|
9
|
+
await this.has(property, document, predicates);
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
static type_error() {
|
|
@@ -18,13 +17,13 @@ export default class {
|
|
|
18
17
|
throw new Error("must be implemented");
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
static async has(property,
|
|
20
|
+
static async has(property, document, predicates) {
|
|
22
21
|
for (const predicate of predicates) {
|
|
23
22
|
if (typeof predicate === "object") {
|
|
24
23
|
await predicate.function(property, ...predicate.params);
|
|
25
24
|
} else {
|
|
26
25
|
const [name, ...params] = predicate.split(":");
|
|
27
|
-
if (!this[name](
|
|
26
|
+
if (!this[name](document[property], ...params)) {
|
|
28
27
|
let error = this.errors[name];
|
|
29
28
|
for (let i = 0; i < params.length; i++) {
|
|
30
29
|
error = error.replace(`$${i+1}`, params[i]);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Parser from "./Parser.js";
|
|
2
|
+
import File from "../File.js";
|
|
2
3
|
import {InternalServerError} from "../errors.js";
|
|
3
4
|
|
|
4
5
|
const $content = "${content}";
|
|
@@ -10,6 +11,10 @@ export default class View {
|
|
|
10
11
|
this.layouts = layouts;
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
static async new(path, layouts) {
|
|
15
|
+
return new View(path, await File.read(`${path}.html`), layouts);
|
|
16
|
+
}
|
|
17
|
+
|
|
13
18
|
elements(layout) {
|
|
14
19
|
try {
|
|
15
20
|
return Parser.parse(layout === undefined
|
package/source/client/Base.js
DELETED
package/source/server/Base.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import conf from "./conf.js";
|
|
2
|
-
import log from "./log.js";
|
|
3
|
-
import package_json from "../../package.json" assert {"type": "json" };
|
|
4
|
-
|
|
5
|
-
const _conf = conf();
|
|
6
|
-
let routes;
|
|
7
|
-
try {
|
|
8
|
-
routes = await import(`${_conf.root}/routes.json`, {
|
|
9
|
-
"assert": {"type": "json"}
|
|
10
|
-
});
|
|
11
|
-
} catch (error) {
|
|
12
|
-
// local routes.json not required
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export default class Base {
|
|
16
|
-
static get conf() {
|
|
17
|
-
return _conf;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
get conf() {
|
|
21
|
-
return this.Class().conf;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
get package() {
|
|
25
|
-
return package_json;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
get routes() {
|
|
29
|
-
return routes?.default ?? [];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
Class() {
|
|
33
|
-
return this.constructor;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default class extends Error {}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
const fallback = async (from, to, logic_from, logic_to) => {
|
|
2
|
-
try {
|
|
3
|
-
return await logic_from(from);
|
|
4
|
-
} catch (error) {
|
|
5
|
-
return logic_to(to);
|
|
6
|
-
}
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export default (from, to, logic) => logic !== undefined
|
|
10
|
-
? fallback(from, to, logic, logic)
|
|
11
|
-
: fallback(undefined, undefined, from, to);
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import Domain from "../domain/Domain.js";
|
|
2
|
-
|
|
3
|
-
const identity = value => value;
|
|
4
|
-
const by = {
|
|
5
|
-
"undefined": () => undefined,
|
|
6
|
-
"boolean": identity,
|
|
7
|
-
"string": identity,
|
|
8
|
-
"number": identity,
|
|
9
|
-
"array": identity,
|
|
10
|
-
"true_object": value => value instanceof Domain ? {} : value,
|
|
11
|
-
"object": value => by[Array.isArray(value) ? "array" : "true_object"](value),
|
|
12
|
-
};
|
|
13
|
-
const resolve_type = async (document, property) => {
|
|
14
|
-
if (document === undefined) {
|
|
15
|
-
return undefined;
|
|
16
|
-
}
|
|
17
|
-
const resolved = await document[property];
|
|
18
|
-
return by[typeof resolved](resolved);
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export default async function resolve(document, projection = []) {
|
|
22
|
-
const resolved = {};
|
|
23
|
-
if (Array.isArray(document)) {
|
|
24
|
-
return Promise.all(document.map(sub => resolve(sub, projection)));
|
|
25
|
-
}
|
|
26
|
-
for (const property of projection) {
|
|
27
|
-
if (typeof property === "string") {
|
|
28
|
-
const result = await resolve_type(document, property);
|
|
29
|
-
if (result !== undefined) {
|
|
30
|
-
resolved[property] = result;
|
|
31
|
-
}
|
|
32
|
-
} else if (typeof property === "object") {
|
|
33
|
-
for (const property2 in property) {
|
|
34
|
-
const value = await document[property2];
|
|
35
|
-
if (value !== undefined) {
|
|
36
|
-
resolved[property2] = await resolve(value, property[property2]);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return resolved;
|
|
42
|
-
}
|