primate 0.8.0 → 0.9.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/LICENSE +17 -23
- package/README.md +202 -210
- package/README.template.md +181 -0
- package/bin/primate.js +5 -0
- package/exports.js +3 -26
- package/html.js +13 -0
- package/module.json +10 -0
- package/package.json +14 -16
- package/{source → src}/Bundler.js +2 -2
- package/{source → src}/cache.js +0 -0
- package/src/conf.js +25 -0
- package/{source → src}/errors/InternalServer.js +0 -0
- package/{source → src}/errors/Predicate.js +0 -0
- package/src/errors/Route.js +1 -0
- package/{source → src}/errors.js +0 -0
- package/{source/extend_object.js → src/extend.js} +3 -3
- package/src/extend.spec.js +111 -0
- package/src/handlers/http.js +1 -0
- package/src/handlers/http404.js +7 -0
- package/src/handlers/json.js +7 -0
- package/src/handlers/stream.js +7 -0
- package/src/handlers/text.js +14 -0
- package/{source → src}/http-codes.json +0 -0
- package/src/log.js +22 -0
- package/{source → src}/mimes.json +0 -0
- package/{source → src}/preset/primate.json +0 -0
- package/{source → src}/preset/stores/default.js +0 -0
- package/src/route.js +61 -0
- package/src/run.js +26 -0
- package/src/serve.js +90 -0
- package/source/App.js +0 -34
- package/source/EagerPromise.js +0 -49
- package/source/Router.js +0 -31
- package/source/Server.js +0 -93
- package/source/Session.js +0 -26
- package/source/attributes.js +0 -14
- package/source/conf.js +0 -26
- package/source/domain/Domain.js +0 -285
- package/source/domain/Field.js +0 -113
- package/source/domain/Predicate.js +0 -24
- package/source/handlers/DOM/Node.js +0 -179
- package/source/handlers/DOM/Parser.js +0 -135
- package/source/handlers/html.js +0 -29
- package/source/handlers/http.js +0 -6
- package/source/handlers/json.js +0 -6
- package/source/handlers/redirect.js +0 -10
- package/source/invariants.js +0 -36
- package/source/map_entries.js +0 -6
- package/source/sanitize.js +0 -8
- package/source/store/Memory.js +0 -60
- package/source/store/Store.js +0 -30
- package/source/types/Array.js +0 -33
- package/source/types/Boolean.js +0 -29
- package/source/types/Date.js +0 -20
- package/source/types/Domain.js +0 -11
- package/source/types/Instance.js +0 -8
- package/source/types/Number.js +0 -45
- package/source/types/Object.js +0 -12
- package/source/types/Primitive.js +0 -7
- package/source/types/Storeable.js +0 -44
- package/source/types/String.js +0 -49
- package/source/types/errors/Array.json +0 -7
- package/source/types/errors/Boolean.json +0 -5
- package/source/types/errors/Date.json +0 -3
- package/source/types/errors/Number.json +0 -9
- package/source/types/errors/Object.json +0 -3
- package/source/types/errors/String.json +0 -11
- package/source/types.js +0 -6
package/LICENSE
CHANGED
|
@@ -1,27 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
reserved.
|
|
1
|
+
Primate JavaScript Framework
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
modification, are permitted provided that the following conditions are met:
|
|
3
|
+
Copyright (c) Terrablue <terrablue@proton.me> and contributors.
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
and/or other materials provided with the distribution.
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
22
|
-
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
23
|
-
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
24
|
-
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
25
|
-
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
26
|
-
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
27
|
-
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
18
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,324 +1,314 @@
|
|
|
1
1
|
# Primate
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
rendering.
|
|
5
|
-
|
|
6
|
-
## Highlights
|
|
7
|
-
|
|
8
|
-
* Flexible HTTP routing, returning HTML, JSON or a custom handler
|
|
9
|
-
* Secure by default with HTTPS, hash-verified scripts and a strong CSP
|
|
10
|
-
* Built-in support for sessions with secure cookies
|
|
11
|
-
* Input verification using data domains
|
|
12
|
-
* Many different data store modules: In-Memory (built-in),
|
|
13
|
-
[File][primate-file-store], [JSON][primate-json-store],
|
|
14
|
-
[MongoDB][primate-mongodb-store]
|
|
15
|
-
* Easy modelling of`1:1`, `1:n` and `n:m` relationships
|
|
16
|
-
* Minimally opinionated with sane, overrideable defaults
|
|
17
|
-
* Supports both Node.js and Deno
|
|
3
|
+
Primal JavaScript framework.
|
|
18
4
|
|
|
19
5
|
## Getting started
|
|
20
6
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
Lay out your app
|
|
7
|
+
Lay out app
|
|
24
8
|
|
|
25
9
|
```sh
|
|
26
|
-
|
|
10
|
+
mkdir -p app/{routes,components,ssl} && cd app
|
|
11
|
+
|
|
27
12
|
```
|
|
28
13
|
|
|
29
|
-
Create a route for `/`
|
|
14
|
+
Create a route for `/` in `routes/site.js`
|
|
30
15
|
|
|
31
16
|
```js
|
|
32
|
-
|
|
17
|
+
import html from "@primate/html";
|
|
33
18
|
|
|
34
|
-
|
|
19
|
+
export default router => {
|
|
20
|
+
router.get("/", () => html`<site-index date="${new Date()}" />`);
|
|
21
|
+
};
|
|
35
22
|
|
|
36
|
-
router.get("/", () => html`<site-index date="${new Date()}" />`);
|
|
37
23
|
```
|
|
38
24
|
|
|
39
|
-
Create a component
|
|
25
|
+
Create a component in `components/site-index.html`
|
|
40
26
|
|
|
41
27
|
```html
|
|
42
|
-
Today's date is
|
|
28
|
+
Today's date is ${date}.
|
|
29
|
+
|
|
43
30
|
```
|
|
44
31
|
|
|
45
|
-
Generate SSL
|
|
32
|
+
Generate SSL files
|
|
46
33
|
|
|
47
34
|
```sh
|
|
48
35
|
openssl req -x509 -out ssl/default.crt -keyout ssl/default.key -newkey rsa:2048 -nodes -sha256 -batch
|
|
49
|
-
```
|
|
50
36
|
|
|
51
|
-
|
|
37
|
+
```
|
|
52
38
|
|
|
53
|
-
|
|
54
|
-
// app.js
|
|
39
|
+
Run
|
|
55
40
|
|
|
56
|
-
|
|
57
|
-
|
|
41
|
+
```sh
|
|
42
|
+
npx primate
|
|
58
43
|
```
|
|
59
44
|
|
|
60
|
-
|
|
45
|
+
## Table of contents
|
|
61
46
|
|
|
62
|
-
|
|
47
|
+
* [Serving content](#serving-content)
|
|
48
|
+
* [Routing](#routing)
|
|
49
|
+
* [Domains](#domains)
|
|
50
|
+
* [Stores](#stores)
|
|
51
|
+
* [Components](#components)
|
|
63
52
|
|
|
64
|
-
|
|
65
|
-
{
|
|
66
|
-
"scripts": {
|
|
67
|
-
"start": "node --experimental-json-modules app.js"
|
|
68
|
-
},
|
|
69
|
-
"type": "module"
|
|
70
|
-
}
|
|
71
|
-
```
|
|
53
|
+
## Serving content
|
|
72
54
|
|
|
73
|
-
|
|
55
|
+
Create a file in `routes` that exports a default function
|
|
74
56
|
|
|
75
|
-
|
|
76
|
-
$ npm install primate
|
|
77
|
-
```
|
|
57
|
+
### Plain text
|
|
78
58
|
|
|
79
|
-
|
|
59
|
+
```js
|
|
60
|
+
export default router => {
|
|
61
|
+
// strings will be served as plain text
|
|
62
|
+
router.get("/user", () => "Donald");
|
|
63
|
+
};
|
|
80
64
|
|
|
81
|
-
```sh
|
|
82
|
-
$ npm start
|
|
83
65
|
```
|
|
84
66
|
|
|
85
|
-
###
|
|
67
|
+
### JSON
|
|
86
68
|
|
|
87
|
-
|
|
69
|
+
```js
|
|
70
|
+
import {File} from "runtime-compat/filesystem";
|
|
88
71
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
"
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
```
|
|
72
|
+
export default router => {
|
|
73
|
+
// any proper JavaScript object will be served as JSON
|
|
74
|
+
router.get("/users", () => [
|
|
75
|
+
{name: "Donald"},
|
|
76
|
+
{name: "Ryan"},
|
|
77
|
+
]);
|
|
97
78
|
|
|
98
|
-
|
|
79
|
+
// load from a file and serve as JSON
|
|
80
|
+
router.get("/users-from-file", () => File.json("users.json"));
|
|
81
|
+
};
|
|
99
82
|
|
|
100
|
-
```
|
|
101
|
-
deno run --import-map=import-map.json app.js
|
|
102
83
|
```
|
|
103
84
|
|
|
104
|
-
|
|
105
|
-
permissions.
|
|
85
|
+
### Streams
|
|
106
86
|
|
|
107
|
-
|
|
87
|
+
```js
|
|
88
|
+
import {File} from "runtime-compat/filesystem";
|
|
108
89
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
90
|
+
export default router => {
|
|
91
|
+
// `File` implements `readable`, which is a ReadableStream
|
|
92
|
+
router.get("/users", () => new File("users.json"));
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
```
|
|
112
96
|
|
|
113
|
-
|
|
97
|
+
### HTML
|
|
114
98
|
|
|
115
|
-
Create
|
|
116
|
-
singleton. You can group your routes across several files or keep them
|
|
117
|
-
in one file.
|
|
99
|
+
Create an HTML component in `components/user-index.html`
|
|
118
100
|
|
|
119
|
-
|
|
101
|
+
```html
|
|
102
|
+
<div for="${users}">
|
|
103
|
+
User ${name}.
|
|
104
|
+
Email ${email}.
|
|
105
|
+
</div>
|
|
120
106
|
|
|
121
|
-
|
|
122
|
-
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Serve the component in your route
|
|
123
110
|
|
|
124
111
|
```js
|
|
125
|
-
|
|
126
|
-
|
|
112
|
+
import html from "@primate/html";
|
|
113
|
+
|
|
114
|
+
export default router => {
|
|
115
|
+
// the HTML tagged template handler loads a component from the `components`
|
|
116
|
+
// directory and serves it as HTML, passing any given data as attributes
|
|
117
|
+
router.get("/users", () => {
|
|
118
|
+
const users = [
|
|
119
|
+
{name: "Donald", email: "donald@the.duck"},
|
|
120
|
+
{name: "Joe", email: "joe@was.absent"},
|
|
121
|
+
];
|
|
122
|
+
return html`<user-index users="${users}" />`;
|
|
123
|
+
});
|
|
124
|
+
};
|
|
127
125
|
|
|
128
|
-
// on matching the exact pathname /, returns {"foo": "bar"} as JSON
|
|
129
|
-
router.get("/", () => json`${{"foo": "bar"}}`);
|
|
130
126
|
```
|
|
131
127
|
|
|
132
|
-
|
|
133
|
-
[section on handlers for common handlers](#handlers).
|
|
128
|
+
## Routing
|
|
134
129
|
|
|
135
|
-
|
|
130
|
+
Routes map requests to responses. All routes are loaded from `routes`.
|
|
136
131
|
|
|
137
|
-
|
|
132
|
+
The order in which routes are declared is irrelevant. Redeclaring a route
|
|
133
|
+
(same pathname and same HTTP verb) throws a `RouteError`.
|
|
138
134
|
|
|
139
|
-
|
|
135
|
+
### Basic GET route
|
|
140
136
|
|
|
141
137
|
```js
|
|
142
|
-
import
|
|
138
|
+
import html from "@primate/html";
|
|
139
|
+
|
|
140
|
+
export default router => {
|
|
141
|
+
// accessing /site/login will serve the contents of
|
|
142
|
+
// `components/site-login.html` as HTML
|
|
143
|
+
router.get("/site/login", () => html`<site-login />`);
|
|
144
|
+
};
|
|
143
145
|
|
|
144
|
-
router.get("/site/login", request => json`${{"path": request.path}}`);
|
|
145
|
-
// accessing /site/login -> {"path":["site","login"]}
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
### Working with the request path
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
export default router => {
|
|
152
|
+
// accessing /site/login will serve `["site", "login"]` as JSON
|
|
153
|
+
router.get("/site/login", request => request.path);
|
|
154
|
+
};
|
|
149
155
|
|
|
150
|
-
|
|
156
|
+
```
|
|
151
157
|
|
|
152
|
-
|
|
158
|
+
### Regular expressions
|
|
153
159
|
|
|
154
160
|
```js
|
|
155
|
-
|
|
161
|
+
export default router => {
|
|
162
|
+
// accessing /user/view/1234 will serve `1234` as plain text
|
|
163
|
+
// accessing /user/view/abcd will show a 404 error
|
|
164
|
+
router.get("/user/view/([0-9])+", request => request[2]);
|
|
165
|
+
};
|
|
156
166
|
|
|
157
|
-
router.get("/user/view/([0-9])+", request => json`${{"path": request.path}}`);
|
|
158
|
-
// accessing /user/view/1234 -> {"path":["site","login","1234"]}
|
|
159
|
-
// accessing /user/view/abcd -> error 404
|
|
160
167
|
```
|
|
161
168
|
|
|
162
|
-
###
|
|
169
|
+
### Named groups
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
export default router => {
|
|
173
|
+
// named groups are mapped to properties of `request.named`
|
|
174
|
+
// accessing /user/view/1234 will serve `1234` as plain text
|
|
175
|
+
router.get("/user/view/(?<_id>[0-9])+", ({named}) => named._id);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
```
|
|
163
179
|
|
|
164
|
-
|
|
165
|
-
applied before matching.
|
|
180
|
+
### Aliasing
|
|
166
181
|
|
|
167
182
|
```js
|
|
168
|
-
|
|
183
|
+
export default router => {
|
|
184
|
+
// will replace `"_id"` in any path with `"([0-9])+"`
|
|
185
|
+
router.alias("_id", "([0-9])+");
|
|
169
186
|
|
|
170
|
-
router.
|
|
187
|
+
// equivalent to `router.get("/user/view/([0-9])+", ...)`
|
|
188
|
+
// will return id if matched, 404 otherwise
|
|
189
|
+
router.get("/user/view/_id", request => request.path[2]);
|
|
171
190
|
|
|
172
|
-
|
|
191
|
+
// can be combined with named groups
|
|
192
|
+
router.alias("_name", "(<name>[a-z])+");
|
|
173
193
|
|
|
174
|
-
|
|
175
|
-
|
|
194
|
+
// will return name if matched, 404 otherwise
|
|
195
|
+
router.get("/user/view/_name", request => request.named.name);
|
|
196
|
+
};
|
|
176
197
|
|
|
177
|
-
|
|
198
|
+
```
|
|
178
199
|
|
|
179
|
-
|
|
180
|
-
function has the same signature as `router[get|post]`.
|
|
200
|
+
### Sharing logic across HTTP verbs
|
|
181
201
|
|
|
182
202
|
```js
|
|
183
|
-
import
|
|
203
|
+
import html from "@primate/html";
|
|
204
|
+
import redirect from "@primate/redirect";
|
|
184
205
|
|
|
185
|
-
router
|
|
206
|
+
export default router => {
|
|
207
|
+
// declare `"edit-user"` as alias of `"/user/edit/([0-9])+"`
|
|
208
|
+
router.alias("edit-user", "/user/edit/([0-9])+");
|
|
186
209
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
// return original request and user
|
|
190
|
-
return {...request, user};
|
|
191
|
-
});
|
|
210
|
+
// pass user instead of request to all verbs with this route
|
|
211
|
+
router.map("edit-user", () => ({name: "Donald"}));
|
|
192
212
|
|
|
193
|
-
router.get("/user/edit/_id", request => {
|
|
194
213
|
// show user edit form
|
|
195
|
-
|
|
196
|
-
});
|
|
214
|
+
router.get("edit-user", user => html`<user-edit user="${user}" />`);
|
|
197
215
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
// verify form and save / show errors
|
|
201
|
-
return await user.save()
|
|
216
|
+
// verify form and save, or show errors
|
|
217
|
+
router.post("edit-user", async user => await user.save()
|
|
202
218
|
? redirect`/users`
|
|
203
|
-
: html`<user-edit user="${user}" />`
|
|
204
|
-
}
|
|
219
|
+
: html`<user-edit user="${user}" />`);
|
|
220
|
+
};
|
|
221
|
+
|
|
205
222
|
```
|
|
206
223
|
|
|
207
|
-
##
|
|
224
|
+
## Domains
|
|
208
225
|
|
|
209
|
-
|
|
226
|
+
Domains represent a collection in a store. All domains are loaded from
|
|
227
|
+
`domains`.
|
|
210
228
|
|
|
211
|
-
|
|
229
|
+
A collection is primarily described using the class `fields` property.
|
|
212
230
|
|
|
213
|
-
|
|
214
|
-
specified attributes and their values. Returns an HTTP 200 response with the
|
|
215
|
-
`text/html` content type.
|
|
231
|
+
### Fields
|
|
216
232
|
|
|
217
|
-
|
|
233
|
+
Field types delimit acceptable values for a field.
|
|
218
234
|
|
|
219
|
-
|
|
220
|
-
|
|
235
|
+
```js
|
|
236
|
+
import {Domain} from "@primate/domains";
|
|
237
|
+
|
|
238
|
+
// A basic domain that contains two string properies
|
|
239
|
+
export default class User extends Domain {
|
|
240
|
+
static fields = {
|
|
241
|
+
// a user's name must be a string
|
|
242
|
+
name: String,
|
|
243
|
+
// a user's age must be a number
|
|
244
|
+
age: Number,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
221
247
|
|
|
222
|
-
### ``redirect`${url}` ``
|
|
223
248
|
|
|
224
|
-
|
|
249
|
+
```
|
|
225
250
|
|
|
226
|
-
|
|
251
|
+
### Short notation
|
|
227
252
|
|
|
228
|
-
|
|
229
|
-
|
|
253
|
+
Field types may be any constructible JavaScript object, including other
|
|
254
|
+
domains. When using other domains as types, data integrity (on saving) is
|
|
255
|
+
ensured.
|
|
230
256
|
|
|
231
257
|
```js
|
|
232
|
-
|
|
233
|
-
import
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
258
|
+
import {Domain} from "@primate/domains";
|
|
259
|
+
import House from "./House.js";
|
|
260
|
+
|
|
261
|
+
export default class User extends Domain {
|
|
262
|
+
static fields = {
|
|
263
|
+
// a user's name must be a string
|
|
264
|
+
name: String,
|
|
265
|
+
// a user's age must be a number
|
|
266
|
+
age: Number,
|
|
267
|
+
// a user's house must have the foreign id of a house record
|
|
268
|
+
house_id: House,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
242
271
|
|
|
243
|
-
router.get("/user/edit/_id", request => {
|
|
244
|
-
// show user edit form
|
|
245
|
-
return html`<user-edit user="${request.user}" />`
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
router.post("/user/edit/_id", request => {
|
|
249
|
-
const {user, payload} = request;
|
|
250
|
-
// verify form and save / show errors
|
|
251
|
-
// this assumes `user` has a method `save` to verify data
|
|
252
|
-
return await user.save(payload)
|
|
253
|
-
? redirect`/users`
|
|
254
|
-
: html`<user-edit user="${user}" />`
|
|
255
|
-
});
|
|
256
272
|
```
|
|
257
273
|
|
|
258
|
-
|
|
259
|
-
<!-- components/edit-user.html -->
|
|
260
|
-
<form method="post">
|
|
261
|
-
<h1>Edit user</h1>
|
|
262
|
-
<p>
|
|
263
|
-
<input name="name" value="${user.name}"></textarea>
|
|
264
|
-
</p>
|
|
265
|
-
<p>
|
|
266
|
-
<input name="email" value="${user.email}"></textarea>
|
|
267
|
-
</p>
|
|
268
|
-
<input type="submit" value="Save user" />
|
|
269
|
-
</form>
|
|
270
|
-
```
|
|
274
|
+
### Predicates
|
|
271
275
|
|
|
272
|
-
|
|
276
|
+
Field types may also be specified as an array, to specify additional predicates
|
|
277
|
+
aside from the type.
|
|
273
278
|
|
|
274
|
-
|
|
279
|
+
```js
|
|
280
|
+
import {Domain} from "@primate/domains";
|
|
281
|
+
import House from "./House.js";
|
|
282
|
+
|
|
283
|
+
export default class User extends Domain {
|
|
284
|
+
static fields = {
|
|
285
|
+
// a user's name must be a string and unique across the user collection
|
|
286
|
+
name: [String, "unique"],
|
|
287
|
+
// a user's age must be a positive integer
|
|
288
|
+
age: [Number, "integer", "positive"],
|
|
289
|
+
// a user's house must have the foreign id of a house record and no two
|
|
290
|
+
// users may have the same house
|
|
291
|
+
house_id: [House, "unique"],
|
|
292
|
+
};
|
|
293
|
+
}
|
|
275
294
|
|
|
276
|
-
```html
|
|
277
|
-
<!-- components/edit-user.html -->
|
|
278
|
-
<form for="${user}" method="post">
|
|
279
|
-
<h1>Edit user</h1>
|
|
280
|
-
<p>
|
|
281
|
-
<input name="name" value="${name}" />
|
|
282
|
-
</p>
|
|
283
|
-
<p>
|
|
284
|
-
<input name="email" value="${email}" />
|
|
285
|
-
</p>
|
|
286
|
-
<input type="submit" value="Save user" />
|
|
287
|
-
</form>
|
|
288
295
|
```
|
|
289
296
|
|
|
290
|
-
|
|
297
|
+
## Stores
|
|
291
298
|
|
|
292
|
-
|
|
299
|
+
Stores interface data. Primate comes with volatile in-memory store used as a
|
|
300
|
+
default. Other stores can be imported as modules.
|
|
293
301
|
|
|
294
|
-
|
|
295
|
-
// in routes/user.js
|
|
296
|
-
import {router, html} from "primate";
|
|
297
|
-
|
|
298
|
-
router.get("/users", request => {
|
|
299
|
-
const users = [
|
|
300
|
-
{"name": "Donald", "email": "donald@was.here"},
|
|
301
|
-
{"name": "Ryan", "email": "ryan@was.here"},
|
|
302
|
-
];
|
|
303
|
-
return html`<user-index users="${users}" />`;
|
|
304
|
-
});
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
```html
|
|
308
|
-
<!-- in components/user-index.html -->
|
|
309
|
-
<div for="${users}">
|
|
310
|
-
User <span value="${name}"></span>
|
|
311
|
-
Email <span value="${email}"></span>
|
|
312
|
-
</div>
|
|
313
|
-
```
|
|
302
|
+
All stores are loaded from `stores`.
|
|
314
303
|
|
|
315
|
-
|
|
304
|
+
### Resources
|
|
316
305
|
|
|
317
|
-
*
|
|
306
|
+
* Website: https://primatejs.com
|
|
307
|
+
* IRC: Join the `#primate` channel on `irc.libera.chat`.
|
|
318
308
|
|
|
319
309
|
## License
|
|
320
310
|
|
|
321
|
-
|
|
311
|
+
MIT
|
|
322
312
|
|
|
323
313
|
[getting-started]: https://primatejs.com/getting-started
|
|
324
314
|
[source-code]: https://github.com/primatejs/primate
|
|
@@ -326,3 +316,5 @@ BSD-3-Clause
|
|
|
326
316
|
[primate-file-store]: https://npmjs.com/primate-file-store
|
|
327
317
|
[primate-json-store]: https://npmjs.com/primate-json-store
|
|
328
318
|
[primate-mongodb-store]: https://npmjs.com/primate-mongodb-store
|
|
319
|
+
[primate-react]: https://github.com/primatejs/primate-react
|
|
320
|
+
[primate-vue]: https://github.com/primatejs/primate-vue
|