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.
Files changed (57) hide show
  1. package/README.md +10 -7
  2. package/debris.json +5 -0
  3. package/jsconfig.json +8 -0
  4. package/package.json +16 -4
  5. package/source/client/Action.js +20 -22
  6. package/source/client/Client.js +13 -17
  7. package/source/client/Context.js +10 -16
  8. package/source/client/Element.js +18 -14
  9. package/source/client/Session.js +27 -0
  10. package/source/client/View.js +1 -2
  11. package/source/client/document.js +1 -1
  12. package/source/preset/primate.json +2 -8
  13. package/source/server/Action.js +57 -75
  14. package/source/server/App.js +27 -8
  15. package/source/server/Bundler.js +13 -16
  16. package/source/server/Context.js +63 -56
  17. package/source/server/{promises/Eager.js → EagerPromise.js} +4 -2
  18. package/source/server/File.js +5 -1
  19. package/source/server/Projector.js +86 -0
  20. package/source/server/Router.js +15 -24
  21. package/source/server/Session.js +9 -33
  22. package/source/server/attributes.js +3 -0
  23. package/source/server/conf.js +20 -26
  24. package/source/server/{Crypto.js → crypto.js} +0 -0
  25. package/source/server/domain/Domain.js +56 -46
  26. package/source/server/domain/Field.js +34 -36
  27. package/source/server/domain/Predicate.js +24 -0
  28. package/source/server/domain/domains.js +17 -1
  29. package/source/server/errors/InternalServer.js +1 -1
  30. package/source/server/errors/Predicate.js +1 -1
  31. package/source/server/errors.js +0 -1
  32. package/source/server/exports.js +10 -9
  33. package/source/server/{utils/extend_object.js → extend_object.js} +0 -0
  34. package/source/server/invariants.js +23 -10
  35. package/source/server/sanitize.js +10 -0
  36. package/source/server/servers/Dynamic.js +19 -13
  37. package/source/server/servers/Server.js +1 -1
  38. package/source/server/servers/Static.js +25 -20
  39. package/source/server/store/Store.js +13 -0
  40. package/source/server/types/Array.js +2 -2
  41. package/source/server/types/Boolean.js +2 -2
  42. package/source/server/types/Date.js +2 -2
  43. package/source/server/types/Domain.js +1 -6
  44. package/source/server/types/Instance.js +1 -1
  45. package/source/server/types/Number.js +2 -2
  46. package/source/server/types/Object.js +2 -2
  47. package/source/server/types/Primitive.js +1 -1
  48. package/source/server/types/Storeable.js +12 -19
  49. package/source/server/types/String.js +2 -2
  50. package/source/server/view/TreeNode.js +2 -0
  51. package/source/server/view/View.js +6 -1
  52. package/source/client/Base.js +0 -5
  53. package/source/server/Base.js +0 -35
  54. package/source/server/errors/Fallback.js +0 -1
  55. package/source/server/fallback.js +0 -11
  56. package/source/server/promises/Meta.js +0 -42
  57. package/source/server/promises.js +0 -2
@@ -1,118 +1,100 @@
1
- import Base from "./Base.js";
2
- import {MetaPromise} from "./promises.js";
3
-
4
- const _response = Symbol("#response");
5
-
6
- export default class Action extends Base {
7
- constructor(data, session) {
8
- super();
9
- this._data = data;
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 get layout() {
14
- return "default";
15
- }
16
-
17
- static async load(data, session, context) {
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
- module = (await import(`${server}/${context}/${route}.js`)).default;
28
+ const {"default": UserAction} = await import(path);
29
+ return new UserAction(request, session, context);
24
30
  } catch (error) {
25
- throw new Error(`route '${route}' missing`);
31
+ throw new Error(`route \`${route}\` missing`);
26
32
  }
27
- return new module(data, session);
33
+ }
34
+
35
+ get Class() {
36
+ return this.constructor;
28
37
  }
29
38
 
30
39
  get layout() {
31
- return this.Class().layout;
40
+ return this.Class.layout;
32
41
  }
33
42
 
34
43
  get path() {
35
- return this._data.path;
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._data.params;
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](this.Class().sanitize(this._data.payload));
58
+ await this[type](sanitize(this.request.payload));
60
59
  }
61
60
 
62
- get response() {
63
- return this[_response]();
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._view();
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
- const _view = await this._view();
82
- const payload = await this.prepare(data, _view);
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
- get context() {
92
- return this.session.actual_context;
93
- }
94
-
95
- switch_context(name, parameters) {
96
- return this.session.switch_context(name, parameters);
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, response) {
105
- this[_response] = async () => {
106
- const data = await response();
107
- data.context = (await this.context).name;
108
- data.namespace = this.path.namespace;
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
  }
@@ -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 extends Base {
11
- constructor() {
12
- super();
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(this.package.version);
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.router;
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
  }
@@ -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 "./Crypto.js";
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["${domain.context}"]["${domain.domain}"] = {};\n`
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) => s + stringify_domain(domain, i, 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[0].context}/context.js";\n`
37
- + `contexts["${context[0].context}"] = context${i};\n`
38
- + `routes["${context[0].context}"] = {};\n`
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 = "return `"+index_html+"`";
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 + "<first-view />";
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 actions.length === 1 && actions[0].length === 0
177
- ? [context_domains]
178
- : actions;
175
+ return context_domains;
179
176
  }
180
177
  }
@@ -1,40 +1,18 @@
1
- import Base from "./Base.js";
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 extends Base {
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 exists = await File.exists(`${this.conf.paths.server}/${name}`);
49
- if (!exists) {
50
- throw new InternalServerError("missing context directory");
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 base_path() {
61
- return `${this.conf.paths.server}/${this.name}`;
38
+ get Class() {
39
+ return this.constructor;
62
40
  }
63
41
 
64
- async view(path) {
65
- const file = await new File(`${this.base_path}/${path}.html`).read();
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", async () => {
71
- const path = `${this.base_path}/layouts`;
46
+ get layouts() {
47
+ return cache(this, "layouts", () => {
48
+ const ending = -5;
49
+ const path = `${this.directory}/layouts`;
72
50
 
73
- const layouts = {};
74
- for (const file of await new File(path).list()) {
75
- layouts[file.slice(0, -5)] = await File.read(`${path}/${file}`);
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
- async run(data, session) {
83
- const {type = "read"} = data;
84
- const action = await Action.load(data, session, this.name);
85
- defined(action[type]);
86
- await this.Class().before(action);
87
- await action.run(type);
88
- return action.response;
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": function(target, property) {
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 typeof result[property] === "function"
17
+ return inconstructible_function(result[property])
16
18
  ? result[property].bind(result)
17
19
  : result[property];
18
20
  }));
@@ -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 "./promises/Eager.js";
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
+ }
@@ -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
- contexts.forEach(context => {
7
- this.routes[context] = this.routes[context] ?? [];
8
- this.routes[context].push({"from": new RegExp("^"+from+"$", "u"), to});
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 !== undefined) {
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) !== undefined
38
- ? this.resolve_by_context(pathname, context)
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 {namespace, action} = this.conf.defaults;
45
+ const [namespace, action, _id] = parts;
47
46
  return {
48
- "pathname": url.pathname,
49
- "resolved": resolved,
50
- "parts": parts,
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
  }