primate 0.1.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,8 +18,8 @@ yarn add primate
18
18
  * **Minimal** just one dependency ([ws][])
19
19
  * **Simple** only native web technologies (JavaScript, HTML)
20
20
  * **Full-stack** server and client, both in JavaScript
21
- * **Layered** separation of data (domains), logic (actions) and
22
- presentation (views)
21
+ * **Layered** separation of data (domains), logic (actions) and presentation
22
+ (views)
23
23
  * **Correct** data verification using field definitions in domains
24
24
  * **Secure** serving hash-verified scripts on secure HTTP with a strong CSP
25
25
  * **Roles** access control with contexts
@@ -31,7 +31,7 @@ ad-hoc getters
31
31
 
32
32
  ## Getting started
33
33
 
34
- Coming soon.
34
+ See the [getting started][getting-started] guide.
35
35
 
36
36
  ## Resources
37
37
 
@@ -46,3 +46,6 @@ will move over to semantic versioning.
46
46
  ## License
47
47
 
48
48
  BSD-3-Clause
49
+
50
+ [ws]: https://github.com/websockets/ws
51
+ [getting-started]: https://primatejs.com/getting-started
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primate",
3
- "version": "0.1.0",
3
+ "version": "0.3.1",
4
4
  "author": "Primate core team <core@primatejs.com>",
5
5
  "homepage": "https://primatejs.com",
6
6
  "description": "Server-client framework",
@@ -1,9 +1,7 @@
1
1
  import Element from "../Element.js";
2
2
  import View from "./View.js";
3
3
  import Base from "./Base.js";
4
-
5
- const base = document.baseURI.replace(document.location.origin, "");
6
- const full = `${document.location.origin}${base}`;
4
+ import {origin_base} from "./document.js";
7
5
 
8
6
  export default class Action extends Base {
9
7
  constructor(name, context) {
@@ -75,7 +73,7 @@ export default class Action extends Base {
75
73
  }
76
74
  event.preventDefault();
77
75
  const data = await this.write(target.action, this.get_form_data(target));
78
- return data.pathname === document.location.href.replace(full, "")
76
+ return data.pathname === document.location.href.replace(origin_base, "")
79
77
  ? this.view.update(data.payload)
80
78
  : this.context.run(data);
81
79
  }
@@ -94,8 +92,8 @@ export default class Action extends Base {
94
92
  }
95
93
  if (target !== null) {
96
94
  const href = Element.value(target, "href");
97
- const url = new URL(href, full);
98
- if (event.button === 0 && url.href.startsWith(full)) {
95
+ const url = new URL(href, origin_base);
96
+ if (event.button === 0 && url.href.startsWith(origin_base)) {
99
97
  event.preventDefault();
100
98
  if (href !== "") {
101
99
  const data = await this.read(href);
@@ -1,15 +1,12 @@
1
1
  import Client from "./Client.js";
2
2
 
3
- const host = document.location.host;
4
- const base = document.baseURI.replace(document.location.origin, "");
5
-
6
3
  export default class App {
7
4
  constructor() {
8
5
  this.definitions = {};
9
6
  }
10
7
 
11
8
  run() {
12
- this.client = new Client({host, base});
9
+ this.client = new Client();
13
10
  this.client.open();
14
11
  }
15
12
 
@@ -1,8 +1,10 @@
1
1
  import Context from "./Context.js";
2
2
  import {contexts} from "./exports.js";
3
+ import {base, host, origin_base} from "./document.js";
3
4
 
4
5
  const events = ["submit", "click", "change", "input"];
5
6
  let back = undefined;
7
+ const location = `wss://${host}${base}`;
6
8
 
7
9
  export default class Client {
8
10
  constructor(conf) {
@@ -18,14 +20,10 @@ export default class Client {
18
20
  }
19
21
  }
20
22
 
21
- get location() {
22
- return `wss://${this.conf.host}${this.conf.base}`;
23
- }
24
-
25
23
  open() {
26
24
  return this.connection?.readyState === 1 ? this
27
25
  : new Promise(resolve => {
28
- const connection = new WebSocket(this.location);
26
+ const connection = new WebSocket(location);
29
27
  connection.addEventListener("message", ({data}) => data === "open"
30
28
  ? resolve(this)
31
29
  : this.receive(JSON.parse(data)));
@@ -56,13 +54,9 @@ export default class Client {
56
54
  return this.send("write", url, payload);
57
55
  }
58
56
 
59
- get full() {
60
- return `${document.location.origin}${this.conf.base}`;
61
- }
62
-
63
57
  async send(type, url, payload = {}) {
64
58
  await this.open();
65
- const {pathname, search} = new URL(url, this.full);
59
+ const {pathname, search} = new URL(url, origin_base);
66
60
  return new Promise(resolve => {
67
61
  back = data => resolve(data);
68
62
  this.connection.send(JSON.stringify({type, pathname, search, payload}));
@@ -143,8 +143,10 @@ export default class Element {
143
143
  return this;
144
144
  }
145
145
 
146
- class(name, set) {
147
- this.element.classList.toggle(name, set !== false);
146
+ class(classes, set) {
147
+ classes.split(" ").forEach(name => {
148
+ this.element.classList.toggle(name, set !== false);
149
+ })
148
150
  return this;
149
151
  }
150
152
 
@@ -0,0 +1,6 @@
1
+ const {baseURI} = document;
2
+ const {host, href, origin} = document.location;
3
+ const base = baseURI === href ? "/" : baseURI.replace(origin, "");
4
+ const origin_base = `${origin}${base}`;
5
+
6
+ export {base, origin_base, host};
@@ -125,7 +125,7 @@ export default class Bundler {
125
125
 
126
126
  register(src, source) {
127
127
  const integrity = `${algorithm}-${hash(source)}`;
128
- this.scripts.push({src, integrity});
128
+ this.scripts.push({"src": `${this.conf.base}${src}`, integrity});
129
129
  this.hashes.add(integrity);
130
130
  }
131
131
 
@@ -139,7 +139,7 @@ export default class Bundler {
139
139
  client += `<script type="module" integrity="${integrity}" src="${src}">`
140
140
  + "</script>\n";
141
141
  }
142
- return client + "${view}";
142
+ return client + "<first-view />";
143
143
  }
144
144
 
145
145
  async write_exports() {
@@ -31,10 +31,14 @@ export default class File {
31
31
  return this.exists && !this.stats.isDirectory();
32
32
  }
33
33
 
34
- get stream() {
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},
@@ -1,12 +1,9 @@
1
1
  import Base from "../Base.js";
2
2
  import Field from "./Field.js";
3
- import Store from "../store/Store.js";
4
- import {PredicateError, InternalServerError} from "../errors.js";
3
+ import {PredicateError} from "../errors.js";
5
4
  import {EagerPromise} from "../promises.js";
6
- import {assert} from "../invariants.js";
7
5
  import cache from "../cache.js";
8
6
  import {random} from "../Crypto.js";
9
- import fallback from "../fallback.js";
10
7
 
11
8
  const length = 12;
12
9
  const preset = "../../preset/data/stores";
@@ -43,16 +40,14 @@ export default class Domain extends Base {
43
40
 
44
41
  static get store() {
45
42
  return cache(this, "store", async () => {
46
- const logic = path => import(`${path}/${this.store_file}`);
47
- const store = await fallback(this.conf.paths.data.stores, preset, logic);
48
-
49
- const instance = store.default;
50
-
51
- const message = `${instance.constructor.name} must instance Store`;
52
- const error = () => { throw new InternalServerError(message); };
53
- assert(instance instanceof Store, error);
54
-
55
- return instance.open();
43
+ const create_path = path => `${path}/${this.store_file}`;
44
+ let store;
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();
56
51
  });
57
52
  }
58
53
 
@@ -110,10 +105,10 @@ export default class Domain extends Base {
110
105
 
111
106
  // #serialize
112
107
  // Serializing is done from the instance's point of view.
113
- serialize() {
108
+ async serialize() {
114
109
  const {properties, fields} = this;
115
- return properties.map(property =>
116
- ({property, "value": fields[property].serialize(this[property])}))
110
+ return (await Promise.all(properties.map(async property =>
111
+ ({property, "value": await fields[property].serialize(this[property])}))))
117
112
  .filter(({value}) => value !== undefined)
118
113
  .reduce((document, {property, value}) => {
119
114
  document[property] = value;
@@ -126,7 +121,7 @@ export default class Domain extends Base {
126
121
  static deserialize(serialized) {
127
122
  const fields = this._fields;
128
123
  return new this(Object.keys(serialized)
129
- //.filter(property => fields[property] !== undefined)
124
+ .filter(property => fields[property] !== undefined)
130
125
  .map(property =>
131
126
  ({property,
132
127
  "value": fields[property].deserialize(serialized[property])}))
@@ -177,7 +172,7 @@ export default class Domain extends Base {
177
172
  const verified = await this.verify(delta);
178
173
  if (verified) {
179
174
  const store = await this.store;
180
- const document = this.serialize();
175
+ const document = await this.serialize();
181
176
  await store.save(this.collection, {"_id": document._id}, document);
182
177
  await after();
183
178
  }
@@ -58,7 +58,7 @@ export default class StaticServer extends Server {
58
58
  response.setHeader("Content-Type", mime(filename));
59
59
  response.setHeader("Etag", file.modified);
60
60
  await session.log("green", url);
61
- return stream(file.stream, response);
61
+ return stream(file.read_stream, response);
62
62
  } else {
63
63
  return this.serve_data(url, session, request, response);
64
64
  }
@@ -79,7 +79,7 @@ export default class StaticServer extends Server {
79
79
  const integrity = `${algorithm}-${hash(src)}`;
80
80
  const view = `<script type="module" integrity="${integrity}">${src}`
81
81
  + "</script>";
82
- const file = new Function("view", "return `"+index+"`")(view);
82
+ const file = index.replace("<first-view />", view);
83
83
  const script_src = Array.from(hashes)
84
84
  .concat([integrity])
85
85
  .reduce((hash_string, next_hash) => hash_string + ` '${next_hash}'`, "");
@@ -1,8 +1,6 @@
1
1
  {
2
2
  "default-src": "'self'",
3
3
  "object-src": "'none'",
4
- "style-src": "'self'",
5
- "img-src": "'self' data: https:",
6
4
  "frame-ancestors": "'none'",
7
5
  "form-action": "'self'",
8
6
  "base-uri": "'self'"
@@ -14,7 +14,7 @@ export default class extends Instance {
14
14
  return value instanceof this.instance;
15
15
  }
16
16
 
17
- /*static deserialize(value) {
17
+ static deserialize(value) {
18
18
  return value instanceof this.instance ? value : new this.instance(value);
19
- }*/
19
+ }
20
20
  }
@@ -1,8 +0,0 @@
1
- export default value => {
2
- try {
3
- Reflect.construct(String, [], value);
4
- return true;
5
- } catch (error) {
6
- return false;
7
- }
8
- };