primate 0.7.4 → 0.8.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 +28 -27
- package/package.json +2 -2
- package/source/Router.js +10 -7
- package/source/handlers/DOM/Node.js +32 -36
- package/source/handlers/html.js +2 -2
- package/debris.json +0 -4
- package/jsconfig.json +0 -8
package/README.md
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
# Primate
|
|
1
|
+
# Primate
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
concentrate on writing effective, expressive code.
|
|
3
|
+
A full-stack Javascript framework with data verification and server-side HTML
|
|
4
|
+
rendering.
|
|
6
5
|
|
|
7
6
|
## Highlights
|
|
8
7
|
|
|
@@ -34,13 +33,13 @@ Create a route for `/`
|
|
|
34
33
|
|
|
35
34
|
import {router, html} from "primate";
|
|
36
35
|
|
|
37
|
-
router.get("/", () => html`<site-index date
|
|
36
|
+
router.get("/", () => html`<site-index date="${new Date()}" />`);
|
|
38
37
|
```
|
|
39
38
|
|
|
40
39
|
Create a component for your route (in `components/site-index.html`)
|
|
41
40
|
|
|
42
41
|
```html
|
|
43
|
-
Today's date is <span
|
|
42
|
+
Today's date is <span value="${date}"></span>.
|
|
44
43
|
```
|
|
45
44
|
|
|
46
45
|
Generate SSL key/certificate
|
|
@@ -110,8 +109,6 @@ permissions.
|
|
|
110
109
|
* [Routes](#routes)
|
|
111
110
|
* [Handlers](#handlers)
|
|
112
111
|
* [Components](#components)
|
|
113
|
-
* [Domains](#domains)
|
|
114
|
-
* [Verification](#verification)
|
|
115
112
|
|
|
116
113
|
## Routes
|
|
117
114
|
|
|
@@ -195,13 +192,15 @@ router.map("/user/edit/_id", request => {
|
|
|
195
192
|
|
|
196
193
|
router.get("/user/edit/_id", request => {
|
|
197
194
|
// show user edit form
|
|
198
|
-
return html`<user-edit user
|
|
195
|
+
return html`<user-edit user="${request.user}" />`
|
|
199
196
|
});
|
|
200
197
|
|
|
201
198
|
router.post("/user/edit/_id", request => {
|
|
202
199
|
const {user} = request;
|
|
203
200
|
// verify form and save / show errors
|
|
204
|
-
return await user.save()
|
|
201
|
+
return await user.save()
|
|
202
|
+
? redirect`/users`
|
|
203
|
+
: html`<user-edit user="${user}" />`
|
|
205
204
|
});
|
|
206
205
|
```
|
|
207
206
|
|
|
@@ -209,7 +208,7 @@ router.post("/user/edit/_id", request => {
|
|
|
209
208
|
|
|
210
209
|
Handlers are tagged template functions usually associated with data.
|
|
211
210
|
|
|
212
|
-
### ``html`<component-name attribute
|
|
211
|
+
### ``html`<component-name attribute="${value}" />` ``
|
|
213
212
|
|
|
214
213
|
Compiles and serves a component from the `components` directory and with the
|
|
215
214
|
specified attributes and their values. Returns an HTTP 200 response with the
|
|
@@ -226,8 +225,8 @@ Redirects to `url`. Returns an HTTP 302 response.
|
|
|
226
225
|
|
|
227
226
|
## Components
|
|
228
227
|
|
|
229
|
-
Create HTML components in the `components` directory. Use
|
|
230
|
-
|
|
228
|
+
Create HTML components in the `components` directory. Use attributes to expose
|
|
229
|
+
passed data within your component.
|
|
231
230
|
|
|
232
231
|
```js
|
|
233
232
|
// in routes/user.js
|
|
@@ -243,14 +242,16 @@ router.map("/user/edit/_id", request => {
|
|
|
243
242
|
|
|
244
243
|
router.get("/user/edit/_id", request => {
|
|
245
244
|
// show user edit form
|
|
246
|
-
return html`<user-edit user
|
|
245
|
+
return html`<user-edit user="${request.user}" />`
|
|
247
246
|
});
|
|
248
247
|
|
|
249
248
|
router.post("/user/edit/_id", request => {
|
|
250
249
|
const {user, payload} = request;
|
|
251
250
|
// verify form and save / show errors
|
|
252
251
|
// this assumes `user` has a method `save` to verify data
|
|
253
|
-
return await user.save(payload)
|
|
252
|
+
return await user.save(payload)
|
|
253
|
+
? redirect`/users`
|
|
254
|
+
: html`<user-edit user="${user}" />`
|
|
254
255
|
});
|
|
255
256
|
```
|
|
256
257
|
|
|
@@ -259,28 +260,28 @@ router.post("/user/edit/_id", request => {
|
|
|
259
260
|
<form method="post">
|
|
260
261
|
<h1>Edit user</h1>
|
|
261
262
|
<p>
|
|
262
|
-
<input name="
|
|
263
|
+
<input name="name" value="${user.name}"></textarea>
|
|
263
264
|
</p>
|
|
264
265
|
<p>
|
|
265
|
-
<input name="
|
|
266
|
+
<input name="email" value="${user.email}"></textarea>
|
|
266
267
|
</p>
|
|
267
268
|
<input type="submit" value="Save user" />
|
|
268
269
|
</form>
|
|
269
270
|
```
|
|
270
271
|
|
|
271
|
-
### Grouping objects with `
|
|
272
|
+
### Grouping objects with `for`
|
|
272
273
|
|
|
273
|
-
You can use the special attribute `
|
|
274
|
+
You can use the special attribute `for` to group objects.
|
|
274
275
|
|
|
275
276
|
```html
|
|
276
277
|
<!-- components/edit-user.html -->
|
|
277
|
-
<form
|
|
278
|
+
<form for="${user}" method="post">
|
|
278
279
|
<h1>Edit user</h1>
|
|
279
280
|
<p>
|
|
280
|
-
<input name="name"
|
|
281
|
+
<input name="name" value="${name}" />
|
|
281
282
|
</p>
|
|
282
283
|
<p>
|
|
283
|
-
<input name="email"
|
|
284
|
+
<input name="email" value="${email}" />
|
|
284
285
|
</p>
|
|
285
286
|
<input type="submit" value="Save user" />
|
|
286
287
|
</form>
|
|
@@ -288,7 +289,7 @@ You can use the special attribute `data-for` to group objects.
|
|
|
288
289
|
|
|
289
290
|
### Expanding arrays
|
|
290
291
|
|
|
291
|
-
`
|
|
292
|
+
`for` can also be used to expand arrays.
|
|
292
293
|
|
|
293
294
|
```js
|
|
294
295
|
// in routes/user.js
|
|
@@ -299,15 +300,15 @@ router.get("/users", request => {
|
|
|
299
300
|
{"name": "Donald", "email": "donald@was.here"},
|
|
300
301
|
{"name": "Ryan", "email": "ryan@was.here"},
|
|
301
302
|
];
|
|
302
|
-
return html`<user-index users
|
|
303
|
+
return html`<user-index users="${users}" />`;
|
|
303
304
|
});
|
|
304
305
|
```
|
|
305
306
|
|
|
306
307
|
```html
|
|
307
308
|
<!-- in components/user-index.html -->
|
|
308
|
-
<div
|
|
309
|
-
User <span
|
|
310
|
-
Email <span
|
|
309
|
+
<div for="${users}">
|
|
310
|
+
User <span value="${name}"></span>
|
|
311
|
+
Email <span value="${email}"></span>
|
|
311
312
|
</div>
|
|
312
313
|
```
|
|
313
314
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"author": "Primate core team <core@primatejs.com>",
|
|
5
5
|
"homepage": "https://primatejs.com",
|
|
6
6
|
"bugs": "https://github.com/primatejs/primate/issues",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"debris": "^0.2.2",
|
|
22
|
-
"eslint": "^8.
|
|
22
|
+
"eslint": "^8.14.0",
|
|
23
23
|
"eslint-plugin-json": "^3.1.0",
|
|
24
24
|
"nyc": "^15.1.0"
|
|
25
25
|
},
|
package/source/Router.js
CHANGED
|
@@ -6,8 +6,8 @@ const dealias = path => aliases.reduce((dealiased, {key, value}) =>
|
|
|
6
6
|
dealiased.replace(key, () => value), path);
|
|
7
7
|
const push = (type, path, handler) =>
|
|
8
8
|
routes.push({type, "path": new RegExp(`^${dealias(path)}$`, "u"), handler});
|
|
9
|
-
const find = (type, path, fallback) => routes.find(route =>
|
|
10
|
-
route.type === type && route.path.test(path))
|
|
9
|
+
const find = (type, path, fallback = {"handler": r => r}) => routes.find(route =>
|
|
10
|
+
route.type === type && route.path.test(path)) ?? fallback;
|
|
11
11
|
|
|
12
12
|
export default {
|
|
13
13
|
"map": (path, callback) => push("map", path, callback),
|
|
@@ -19,10 +19,13 @@ export default {
|
|
|
19
19
|
const url = new URL(`https://primatejs.com${original_request.pathname}`);
|
|
20
20
|
const {pathname, searchParams} = url;
|
|
21
21
|
const params = Object.fromEntries(searchParams);
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
const verb = find(method, pathname, () => ({"handler": http404``}));
|
|
23
|
+
const path = pathname.split("/").filter(path => path !== "");
|
|
24
|
+
Object.entries(verb.path.exec(pathname)?.groups ?? [])
|
|
25
|
+
.filter(([key]) => path[key] === undefined)
|
|
26
|
+
.forEach(([key, value]) => Object.defineProperty(path, key, {value}));
|
|
27
|
+
|
|
28
|
+
const request = {...original_request, pathname, params, path};
|
|
29
|
+
return verb.handler(await find("map", pathname).handler(request));
|
|
27
30
|
},
|
|
28
31
|
};
|
|
@@ -2,6 +2,7 @@ import Parser from "./Parser.js";
|
|
|
2
2
|
|
|
3
3
|
const replacement_regex = /^\$([0-9]*)$/;
|
|
4
4
|
const data_regex = /\${([^}]*)}/g;
|
|
5
|
+
const attributes_regex = /([-a-z]*="[^"]+")/g;
|
|
5
6
|
const replace = (attribute, source) => {
|
|
6
7
|
if (attribute.includes(".")) {
|
|
7
8
|
const index = attribute.indexOf(".");
|
|
@@ -18,16 +19,17 @@ const fulfill = (attribute, source) => {
|
|
|
18
19
|
if (source === undefined) {
|
|
19
20
|
return undefined;
|
|
20
21
|
}
|
|
21
|
-
let value;
|
|
22
|
+
let value = attribute;
|
|
22
23
|
const matches = [...attribute.matchAll(data_regex)];
|
|
23
24
|
if (matches.length > 0) {
|
|
24
25
|
for (const match of matches) {
|
|
25
26
|
const [key] = match;
|
|
26
27
|
const new_value = replace(match[1], source);
|
|
28
|
+
if (attribute === key) {
|
|
29
|
+
return new_value;
|
|
30
|
+
}
|
|
27
31
|
value = attribute.replace(key, new_value);
|
|
28
32
|
}
|
|
29
|
-
} else {
|
|
30
|
-
value = replace(attribute, source);
|
|
31
33
|
}
|
|
32
34
|
return value;
|
|
33
35
|
};
|
|
@@ -43,7 +45,8 @@ export default class Node {
|
|
|
43
45
|
this.#data = data;
|
|
44
46
|
this.attributes = {};
|
|
45
47
|
if (content !== undefined) {
|
|
46
|
-
const [tag_name
|
|
48
|
+
const [tag_name] = content.split(" ");
|
|
49
|
+
const attributes = content.match(attributes_regex) ?? [];
|
|
47
50
|
this.tag_name = tag_name;
|
|
48
51
|
for (const attribute of attributes
|
|
49
52
|
.map(a => a.replaceAll("\"", ""))
|
|
@@ -64,11 +67,7 @@ export default class Node {
|
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
get data() {
|
|
67
|
-
|
|
68
|
-
return this.#data;
|
|
69
|
-
} else {
|
|
70
|
-
return this.parent?.data;
|
|
71
|
-
}
|
|
70
|
+
return this.#data ?? this.parent?.data;
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
set data(value) {
|
|
@@ -119,13 +118,13 @@ export default class Node {
|
|
|
119
118
|
return tag;
|
|
120
119
|
}
|
|
121
120
|
|
|
122
|
-
compose(components) {
|
|
121
|
+
async compose(components) {
|
|
123
122
|
if (components[this.tag_name]) {
|
|
124
|
-
return Parser.parse(components[this.tag_name], this.attributes)
|
|
125
|
-
|
|
126
|
-
for (let i = 0; i < this.children.length; i++) {
|
|
127
|
-
this.children[i] = this.children[i].compose(components);
|
|
123
|
+
return Parser.parse(components[this.tag_name], this.attributes)
|
|
124
|
+
.compose(components);
|
|
128
125
|
}
|
|
126
|
+
this.children = await Promise.all(this.children.map(child =>
|
|
127
|
+
child.compose(components)));
|
|
129
128
|
return this;
|
|
130
129
|
}
|
|
131
130
|
|
|
@@ -136,15 +135,15 @@ export default class Node {
|
|
|
136
135
|
cloned.attributes[attribute] = this.attributes[attribute];
|
|
137
136
|
}
|
|
138
137
|
for (const child of this.children) {
|
|
139
|
-
child.clone(cloned);
|
|
138
|
+
child.clone(cloned, child.data !== this.data ? child.data : undefined);
|
|
140
139
|
}
|
|
141
140
|
}
|
|
142
141
|
|
|
143
142
|
async expand() {
|
|
144
|
-
if (this.attributes["
|
|
145
|
-
const key = this.attributes["
|
|
146
|
-
delete this.attributes["
|
|
147
|
-
const value = await this.data
|
|
143
|
+
if (this.attributes["for"] !== undefined) {
|
|
144
|
+
const key = this.attributes["for"];
|
|
145
|
+
delete this.attributes["for"];
|
|
146
|
+
const value = await fulfill(key, this.data);
|
|
148
147
|
const arr = Array.isArray(value) ? value : [value];
|
|
149
148
|
const newparent = new Node();
|
|
150
149
|
for (const val of arr) {
|
|
@@ -154,21 +153,18 @@ export default class Node {
|
|
|
154
153
|
return newparent.expand();
|
|
155
154
|
}
|
|
156
155
|
for (const attribute in this.attributes) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
break;
|
|
170
|
-
}
|
|
171
|
-
delete this.attributes[attribute];
|
|
156
|
+
const fulfilled = fulfill(this.attributes[attribute], this.data);
|
|
157
|
+
switch(attribute) {
|
|
158
|
+
case "value":
|
|
159
|
+
if (this.tag_name === "input") {
|
|
160
|
+
this.attributes.value = fulfilled;
|
|
161
|
+
} else {
|
|
162
|
+
this.text = fulfilled;
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
default:
|
|
166
|
+
this.attributes[attribute] = fulfilled;
|
|
167
|
+
break;
|
|
172
168
|
}
|
|
173
169
|
}
|
|
174
170
|
for (let i = 0; i < this.children.length; i++) {
|
|
@@ -177,7 +173,7 @@ export default class Node {
|
|
|
177
173
|
return this;
|
|
178
174
|
}
|
|
179
175
|
|
|
180
|
-
unfold(components) {
|
|
181
|
-
return this.compose(components).expand();
|
|
176
|
+
async unfold(components) {
|
|
177
|
+
return (await this.compose(components)).expand();
|
|
182
178
|
}
|
|
183
179
|
}
|
package/source/handlers/html.js
CHANGED
|
@@ -16,11 +16,11 @@ if (await File.exists(path)) {
|
|
|
16
16
|
|
|
17
17
|
export default async (strings, ...keys) => {
|
|
18
18
|
const awaited_keys = await Promise.all(keys);
|
|
19
|
-
const rendered = await (await Parser.parse(strings
|
|
19
|
+
const rendered = await (await (await Parser.parse(strings
|
|
20
20
|
.slice(0, last)
|
|
21
21
|
.map((string, i) => `${string}$${i}`)
|
|
22
22
|
.join("") + strings[strings.length+last], awaited_keys)
|
|
23
|
-
.unfold(components))
|
|
23
|
+
.unfold(components)))
|
|
24
24
|
.render();
|
|
25
25
|
const body = (await index(conf)).replace("<body>", () => `<body>${rendered}`);
|
|
26
26
|
const code = 200;
|
package/debris.json
DELETED