@loadbare/app 0.8.2 → 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/dist/core/lb-constants.d.ts +1 -0
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +10 -0
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/core/lb-types.d.ts +11 -0
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js +20 -0
- package/dist/core/lb-types.js.map +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +50 -8
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/dist/server/lb-express.d.ts +17 -10
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +58 -29
- package/dist/server/lb-express.js.map +1 -1
- package/dist/server/lb-server.d.ts +23 -0
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +53 -3
- package/dist/server/lb-server.js.map +1 -1
- package/docs/TECHREF-1.0.md +70 -9
- package/docs/reference/data-binding.md +6 -1
- package/docs/reference/page-files.md +39 -0
- package/docs/reference/server.md +12 -6
- package/docs/roadmap.md +12 -0
- package/package.json +1 -1
- package/skills/loadbare-app/SKILL.md +16 -5
- package/skills/loadbare-app/references/TECHREF-1.0.md +70 -9
- package/skills/loadbare-app/references/data-binding.md +6 -1
- package/skills/loadbare-app/references/page-files.md +39 -0
- package/skills/loadbare-app/references/server.md +12 -6
|
@@ -169,11 +169,22 @@ shows, a filter, a date range: each is a query parm,
|
|
|
169
169
|
shows the same thing. A control writes one with `lb-query-parm="acct"`, which
|
|
170
170
|
replaces the history entry, reloads the page at the new URL, and sends no
|
|
171
171
|
request. A link writes several with an ordinary `lb-nav-link` href. Queries
|
|
172
|
-
still take no arguments: `contextFor`
|
|
173
|
-
query reads
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
172
|
+
still take no arguments: `contextFor(req, parms)` puts parms onto `ctx`, and
|
|
173
|
+
a query reads them there. Read them from `parms`, never `req.query`, because
|
|
174
|
+
`contextFor` may be called twice for one request. A parm is user input, so
|
|
175
|
+
validate it where you read it. Do not keep a selection in server state set
|
|
176
|
+
by an action; that forces `refresh: []` and a hand-written re-answer of
|
|
177
|
+
everything the selection touches.
|
|
178
|
+
|
|
179
|
+
**When a server response changes the query parms.** After an insert, only
|
|
180
|
+
the server knows the new key. After a delete, only the server knows the parm
|
|
181
|
+
should go. Have `run` return `queryParms({ acct: String(id) })`, or
|
|
182
|
+
`queryParms({ acct: "" })` to remove the parm. The page loads at the new
|
|
183
|
+
query string in the same round trip, and the history entry is replaced to
|
|
184
|
+
match. This is Post/Redirect/Get without the redirect: the path never
|
|
185
|
+
changes, and there is no second request. Do not reach for a redirect, a
|
|
186
|
+
hand-written refresh of every query, or server state holding the selection.
|
|
187
|
+
See [refresh and patch](references/page-files.md#refresh-and-patch).
|
|
177
188
|
|
|
178
189
|
**Loadbare is for applications, not sites.** Every route is answered with
|
|
179
190
|
`app.html`, and a path that names no page is found in the browser, not
|
|
@@ -501,6 +501,9 @@ not a way to show a different detail per row.
|
|
|
501
501
|
|
|
502
502
|
Which master a page shows is a query parm, which a query reads off `ctx`
|
|
503
503
|
since it takes no argument from the browser — see [Query parms](#query-parms).
|
|
504
|
+
When a write creates or removes the master, the server response changes that
|
|
505
|
+
parm — see
|
|
506
|
+
[When a server response changes the query parms](#when-a-server-response-changes-the-query-parms).
|
|
504
507
|
|
|
505
508
|
### Requests
|
|
506
509
|
|
|
@@ -746,9 +749,54 @@ A control that writes a query parm sends no request. One that also carries
|
|
|
746
749
|
sent twice.
|
|
747
750
|
|
|
748
751
|
Every round trip carries the query string the browser is showing, a page
|
|
749
|
-
load and an action alike. The server
|
|
750
|
-
|
|
751
|
-
|
|
752
|
+
load and an action alike. The server hands its parms to `contextFor` — see
|
|
753
|
+
[The Express server](#the-express-server). Nothing in Loadbare assigns a
|
|
754
|
+
parm a meaning.
|
|
755
|
+
|
|
756
|
+
#### When a server response changes the query parms
|
|
757
|
+
|
|
758
|
+
Some query parms can only be known once a write has run. After an insert,
|
|
759
|
+
the key of the new row exists only on the server. After a delete, only the
|
|
760
|
+
server knows that the row the page was showing is gone.
|
|
761
|
+
|
|
762
|
+
The usual web answer is Post/Redirect/Get: the server answers the write with
|
|
763
|
+
a redirect to a URL naming the result, and the browser makes a second request
|
|
764
|
+
to load it. Loadbare/app does the same work in one round trip, and never
|
|
765
|
+
changes the path.
|
|
766
|
+
|
|
767
|
+
A request's `run` returns `queryParms()`, naming the parms the write decided.
|
|
768
|
+
The server loads the page with those parms set, as a cold load of the
|
|
769
|
+
resulting URL would: `onPageEnter`, then every query. The server response
|
|
770
|
+
carries the parms and that load together. The hub sets the parms in the URL,
|
|
771
|
+
replacing the history entry, and then lands the load.
|
|
772
|
+
|
|
773
|
+
```ts
|
|
774
|
+
rowInsert: {
|
|
775
|
+
run: async (ctx, { values }) => {
|
|
776
|
+
const id = await ctx.db.addAccount(values);
|
|
777
|
+
return queryParms({ acct: String(id) });
|
|
778
|
+
},
|
|
779
|
+
refresh: [],
|
|
780
|
+
},
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
- Only query parms change. A server response cannot send the browser to
|
|
784
|
+
another page.
|
|
785
|
+
- Parms the response does not name are left as they are. An empty value
|
|
786
|
+
removes its parm.
|
|
787
|
+
- At least one parm is named, and every value is a string. Otherwise the
|
|
788
|
+
server warns, ignores the parms, and runs the refresh set as usual.
|
|
789
|
+
- The refresh set does not run, and anything else `run` returned is dropped.
|
|
790
|
+
Both were answers for the query string the page is leaving.
|
|
791
|
+
- If loading the page fails, the write has still happened. The response
|
|
792
|
+
carries the parms alone, and the hub loads the page itself. A failure
|
|
793
|
+
there is a page load's, and is not stamped on the element that sent the
|
|
794
|
+
request.
|
|
795
|
+
- A user who has left the page by the time the response arrives keeps the
|
|
796
|
+
URL they are on.
|
|
797
|
+
|
|
798
|
+
The page is loaded with a second context, built by `contextFor` from the new
|
|
799
|
+
parms — see [The Express server](#the-express-server).
|
|
752
800
|
|
|
753
801
|
### lb-navigation
|
|
754
802
|
|
|
@@ -929,17 +977,23 @@ only real requirement is that the catch-all for app.html is at the end,
|
|
|
929
977
|
so it does not catch any other files.
|
|
930
978
|
|
|
931
979
|
`hubRoutes` answers `POST /lb/<page>`, with the query string the browser is
|
|
932
|
-
showing after it, verbatim.
|
|
933
|
-
`contextFor` puts on the context whatever a query
|
|
934
|
-
user input:
|
|
980
|
+
showing after it, verbatim. It hands `contextFor` that query string's parms
|
|
981
|
+
as a second argument, and `contextFor` puts on the context whatever a query
|
|
982
|
+
reads from them. They are user input:
|
|
935
983
|
|
|
936
984
|
```ts
|
|
937
|
-
function contextFor(req: Request): HubContext {
|
|
938
|
-
|
|
939
|
-
return { db: openDb(), acct: typeof acct === "string" ? acct : "" };
|
|
985
|
+
function contextFor(req: Request, parms: URLSearchParams): HubContext {
|
|
986
|
+
return { db: openDb(), acct: parms.get("acct") ?? "" };
|
|
940
987
|
}
|
|
941
988
|
```
|
|
942
989
|
|
|
990
|
+
Read the parms from that argument, not from `req.query`. After a request
|
|
991
|
+
whose `run` returned `queryParms()`, `contextFor` is called a second time for
|
|
992
|
+
the same request, with the new parms, to load the page at them. So it must
|
|
993
|
+
be safe to call twice, and a write must be visible to the second context by
|
|
994
|
+
the time its `run` returns. A handle opened per request without a
|
|
995
|
+
transaction around it is both.
|
|
996
|
+
|
|
943
997
|
Give the server the origin root. The hub reaches its own endpoints by
|
|
944
998
|
absolute path, so an application cannot be hosted under a subpath such as
|
|
945
999
|
`example.com/myapp/`, and anything proxying in front of the server passes
|
|
@@ -1010,6 +1064,13 @@ interaction happened, as `list`, `row`, `key`, `cell` and `value`. Under
|
|
|
1010
1064
|
ones. That is how a delta reaches the browser: wrap it in `patch()`, naming
|
|
1011
1065
|
the rows that arrived or changed and the keys that went.
|
|
1012
1066
|
|
|
1067
|
+
`run` may instead return `queryParms()`, naming query parms only the write
|
|
1068
|
+
can know, such as the key of a row it inserted. The page then loads at them
|
|
1069
|
+
in the same round trip, in place of the refresh set — see
|
|
1070
|
+
[When a server response changes the query parms](#when-a-server-response-changes-the-query-parms).
|
|
1071
|
+
A `rowDelete` that removes the row on screen returns `queryParms({ acct: "" })`
|
|
1072
|
+
and leaves any other delete to its refresh set.
|
|
1073
|
+
|
|
1013
1074
|
```ts
|
|
1014
1075
|
// members.requests.ts
|
|
1015
1076
|
import { patch, type Requests } from "@loadbare/app/server";
|
|
@@ -328,7 +328,12 @@ an absent parm lands empty, so the control shows what the address bar says.
|
|
|
328
328
|
A control that writes a query parm sends no request, and one that also
|
|
329
329
|
carries `lb-action` has that request refused.
|
|
330
330
|
|
|
331
|
-
|
|
331
|
+
A request can write parms too, when only the write knows their value, such as
|
|
332
|
+
the key of a row it inserted; see
|
|
333
|
+
[refresh and patch](./page-files.md#refresh-and-patch). The page loads at
|
|
334
|
+
them in the same round trip, and they land on their controls as above.
|
|
335
|
+
|
|
336
|
+
The server hands the parms to `contextFor`; see
|
|
332
337
|
[the Express server](./server.md#database-layer). See
|
|
333
338
|
[Query parms](./TECHREF-1.0.md#query-parms) for the whole rule.
|
|
334
339
|
|
|
@@ -192,3 +192,42 @@ resetRoster: {
|
|
|
192
192
|
refresh: ["roster"],
|
|
193
193
|
},
|
|
194
194
|
```
|
|
195
|
+
|
|
196
|
+
Return `queryParms()` instead when the server response changes the query
|
|
197
|
+
parms: after an insert, only the server knows the new key, and after a delete,
|
|
198
|
+
only the server knows the parm should go. The page loads at the query string
|
|
199
|
+
with those parms set, in the same round trip, and the hub writes them into the
|
|
200
|
+
URL, replacing the history entry. This is Post/Redirect/Get without the
|
|
201
|
+
redirect. The refresh set does not run, and nothing else `run` returned is
|
|
202
|
+
sent, since both answered for the query string the page left. Name at least
|
|
203
|
+
one parm, give each a string, and use an empty string to take one out:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
// src/pages/accounts.requests.ts
|
|
207
|
+
import { queryParms, type Requests } from "@loadbare/app/server";
|
|
208
|
+
|
|
209
|
+
export const requests: Requests = {
|
|
210
|
+
crud: {
|
|
211
|
+
accounts: {
|
|
212
|
+
rowInsert: {
|
|
213
|
+
run: async (ctx, { values }) => {
|
|
214
|
+
const id = await ctx.db.addAccount(values);
|
|
215
|
+
return queryParms({ acct: String(id) });
|
|
216
|
+
},
|
|
217
|
+
refresh: [],
|
|
218
|
+
},
|
|
219
|
+
rowDelete: {
|
|
220
|
+
run: async (ctx, { key }) => {
|
|
221
|
+
await ctx.db.deleteAccount(key);
|
|
222
|
+
if (key === ctx.acct) return queryParms({ acct: "" });
|
|
223
|
+
},
|
|
224
|
+
refresh: ["accounts"],
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Only parms: `run` cannot send the browser to another page. The loaded page
|
|
232
|
+
reads the new parms through `contextFor` like any others; see
|
|
233
|
+
[the Express server](./server.md#database-layer).
|
|
@@ -137,17 +137,23 @@ declare module "@loadbare/app/server" {
|
|
|
137
137
|
}
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
-
The query string the browser is showing arrives on every data request,
|
|
141
|
-
`
|
|
142
|
-
reads from them, and treat them as user input:
|
|
140
|
+
The query string the browser is showing arrives on every data request, and
|
|
141
|
+
`contextFor` gets its parms as a second argument. Put on the context whatever
|
|
142
|
+
a query reads from them, and treat them as user input:
|
|
143
143
|
|
|
144
144
|
```ts
|
|
145
|
-
function contextFor(req: Request): HubContext {
|
|
146
|
-
|
|
147
|
-
return { db: openDb(), team: typeof team === "string" ? team : "" };
|
|
145
|
+
function contextFor(req: Request, parms: URLSearchParams): HubContext {
|
|
146
|
+
return { db: openDb(), team: parms.get("team") ?? "" };
|
|
148
147
|
}
|
|
149
148
|
```
|
|
150
149
|
|
|
150
|
+
Read the parms from that argument, not from `req.query`. When a server
|
|
151
|
+
response changes the query parms, `contextFor` is called a second time for
|
|
152
|
+
that request, with the new parms, to load the page at them; see [refresh and patch](./page-files.md#refresh-and-patch). Write it to
|
|
153
|
+
be safe to call twice, and make a write visible to the second context by the
|
|
154
|
+
time `run` returns: a handle opened per request, with no transaction held
|
|
155
|
+
open across the two, is both.
|
|
156
|
+
|
|
151
157
|
Add a field for anything else a request needs — the authenticated user, a
|
|
152
158
|
request id, a feature flag set. Queries and requests read them from `ctx`; see
|
|
153
159
|
[page files](./page-files.md).
|