@loadbare/app 0.9.0 → 0.11.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 +3 -3
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +107 -90
- package/dist/build/assemble.js.map +1 -1
- package/dist/build/expand.d.ts +6 -1
- package/dist/build/expand.d.ts.map +1 -1
- package/dist/build/expand.js +112 -26
- package/dist/build/expand.js.map +1 -1
- package/dist/build/locations.d.ts +2 -3
- package/dist/build/locations.d.ts.map +1 -1
- package/dist/build/locations.js +2 -3
- package/dist/build/locations.js.map +1 -1
- package/dist/build/pages.d.ts +3 -4
- package/dist/build/pages.d.ts.map +1 -1
- package/dist/build/pages.js +3 -4
- package/dist/build/pages.js.map +1 -1
- package/dist/core/lb-constants.d.ts +27 -24
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +103 -168
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/core/lb-types.d.ts +64 -77
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js +40 -7
- package/dist/core/lb-types.js.map +1 -1
- package/dist/hub/lb-apply.d.ts +47 -37
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +195 -199
- package/dist/hub/lb-apply.js.map +1 -1
- package/dist/hub/lb-hub.browser.d.ts +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +410 -449
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/dist/server/lb-express.d.ts +5 -5
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +35 -66
- package/dist/server/lb-express.js.map +1 -1
- package/dist/server/lb-server.d.ts +77 -135
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +132 -79
- package/dist/server/lb-server.js.map +1 -1
- package/docs/TECHREF-1.0.md +908 -585
- package/docs/comparison.md +243 -185
- package/docs/prior-art.md +15 -14
- package/docs/reference/builder.md +9 -3
- package/docs/reference/chrome.md +107 -56
- package/docs/reference/custom-elements.md +291 -173
- package/docs/reference/data-binding.md +381 -374
- package/docs/reference/overview.md +12 -10
- package/docs/reference/page-files.md +164 -99
- package/docs/reference/server.md +2 -2
- package/docs/reference/widgets.md +104 -110
- package/docs/roadmap.md +32 -39
- package/docs/terms-of-art.md +57 -0
- package/docs/testing.md +97 -68
- package/docs/theory.md +92 -58
- package/docs/tutorials/010-pages-and-navigation.md +20 -12
- package/docs/tutorials/020-css.md +6 -3
- package/docs/tutorials/030-html-decomposition.md +9 -7
- package/docs/tutorials/040-displaying-data.md +30 -13
- package/docs/tutorials/{050-actions.md → 050-requests.md} +25 -15
- package/docs/tutorials/060-custom-element-code.md +17 -16
- package/docs/tutorials/065-conditional-rendering.md +34 -23
- package/docs/tutorials/070-displaying-a-list.md +29 -21
- package/docs/tutorials/072-inserting-into-a-list.md +24 -16
- package/docs/tutorials/074-deleting-from-a-list.md +9 -7
- package/docs/tutorials/076-updating-a-list-item.md +11 -10
- package/docs/tutorials/080-widget-requests.md +71 -43
- package/docs/tutorials/090-using-widget-libraries.md +22 -22
- package/docs/what-does-loadbare-extend.md +124 -0
- package/package.json +1 -1
- package/skills/loadbare-app/SKILL.md +201 -123
- package/skills/loadbare-app/references/TECHREF-1.0.md +908 -585
- package/skills/loadbare-app/references/builder.md +9 -3
- package/skills/loadbare-app/references/chrome.md +107 -56
- package/skills/loadbare-app/references/custom-elements.md +291 -173
- package/skills/loadbare-app/references/data-binding.md +381 -374
- package/skills/loadbare-app/references/overview.md +12 -10
- package/skills/loadbare-app/references/page-files.md +164 -99
- package/skills/loadbare-app/references/server.md +2 -2
- package/skills/loadbare-app/references/widgets.md +104 -110
- package/docs/analysis-accidental-complexity.md +0 -149
- package/docs/analysis-closed-set.md +0 -210
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { HubData, HubRequest, HubResponse, HubResult, Kind, Patch, RequestFields, Row } from "../core/lb-types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Whatever the application hands the engine for the duration of one request.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* declaration merging, once, anywhere in its own source:
|
|
5
|
+
* Loadbare declares it empty and never reads it. An application fills it in
|
|
6
|
+
* by declaration merging, once, anywhere in its own source:
|
|
7
7
|
*
|
|
8
8
|
* declare module "@loadbare/app/server" {
|
|
9
9
|
* interface HubContext {
|
|
@@ -21,192 +21,134 @@ import type { Patch, Row, HubData, HubResult } from "../core/lb-types.js";
|
|
|
21
21
|
export interface HubContext {
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* A declared query:
|
|
24
|
+
* A declared query: its kind, its key, and the run that answers it.
|
|
25
25
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* with
|
|
29
|
-
*
|
|
26
|
+
* Kind and key are properties of the name rather than of any one answer, so
|
|
27
|
+
* they are declared here and never inferred from what comes back. The engine
|
|
28
|
+
* sends both with every answer, so the markup repeats neither. Every query
|
|
29
|
+
* has a key; an aggregate row answers with a constant one.
|
|
30
30
|
*
|
|
31
|
-
* Write one with `row()` or `
|
|
31
|
+
* Write one with `row()` or `rows()` below; nothing else builds one.
|
|
32
32
|
*/
|
|
33
33
|
export interface Query {
|
|
34
|
-
readonly kind:
|
|
34
|
+
readonly kind: Kind;
|
|
35
|
+
readonly key: string;
|
|
35
36
|
readonly run: (ctx: HubContext) => HubResult | Promise<HubResult>;
|
|
36
37
|
}
|
|
37
|
-
/** A query that answers with one row. */
|
|
38
|
-
export declare function row(run: (ctx: HubContext) => Row | Promise<Row>): Query;
|
|
38
|
+
/** A query that answers with one row, identified by its `key` column. */
|
|
39
|
+
export declare function row(key: string, run: (ctx: HubContext) => Row | Promise<Row>): Query;
|
|
39
40
|
/**
|
|
40
|
-
* A query that answers with
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* There is no wrapper around the array, because the declaration already said
|
|
44
|
-
* this name answers with rows.
|
|
41
|
+
* A query that answers with all its rows, and therefore also their order.
|
|
42
|
+
* Rows land by their `key` column: a row whose key is not in the answer is
|
|
43
|
+
* gone.
|
|
45
44
|
*/
|
|
46
|
-
export declare function
|
|
45
|
+
export declare function rows(key: string, run: (ctx: HubContext) => Row[] | Promise<Row[]>): Query;
|
|
47
46
|
/**
|
|
48
|
-
* Only what changed, returned from a
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
47
|
+
* Only what changed, returned from a handler for a `rows` query. Rows named
|
|
48
|
+
* here are added or updated, keys in `drop` are removed, and everything
|
|
49
|
+
* unnamed is left alone — its contents, and its place in whatever order the
|
|
50
|
+
* page is keeping.
|
|
52
51
|
*
|
|
53
|
-
* `Array.isArray` is what tells a patch from
|
|
52
|
+
* `Array.isArray` is what tells a patch from all rows, so a patch needs no
|
|
54
53
|
* marker of its own and no column name is reserved to carry one.
|
|
55
54
|
*/
|
|
56
55
|
export declare function patch(change: {
|
|
57
56
|
rows?: Row[];
|
|
58
|
-
drop?:
|
|
57
|
+
drop?: unknown[];
|
|
59
58
|
}): Patch;
|
|
60
59
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* every other parm is left as it is. Name at least one.
|
|
60
|
+
* A new row for `lb-url`, returned from a handler when only the handler can
|
|
61
|
+
* know where the page belongs: the key of a row it inserted, or the absence
|
|
62
|
+
* of one it deleted.
|
|
65
63
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* is leaving. This is Post/Redirect/Get without the redirect. The hub writes the parms into the URL, replacing the history
|
|
70
|
-
* entry, and lands the load.
|
|
64
|
+
* A column other than `lb-path` is a query parm: set, or taken out when its
|
|
65
|
+
* value is empty. With `lb-path` naming another page, the page is entered
|
|
66
|
+
* with only the parms named here; otherwise every other parm is kept.
|
|
71
67
|
*
|
|
72
|
-
*
|
|
68
|
+
* The page then loads at the new URL in the same round trip, as a cold load
|
|
69
|
+
* of it would, so the refresh set is not run and whatever else the handler
|
|
70
|
+
* returned is dropped: both were answers for the URL the page is leaving.
|
|
71
|
+
* This is Post/Redirect/Get without the redirect.
|
|
73
72
|
*/
|
|
74
|
-
export declare function
|
|
75
|
-
/** The shape of a `<
|
|
73
|
+
export declare function url(columns: Record<string, string>): HubData;
|
|
74
|
+
/** The shape of a `<stub>.queries.ts` module. */
|
|
76
75
|
export type Queries = Record<string, Query>;
|
|
77
76
|
/**
|
|
78
|
-
*
|
|
79
|
-
* value a control may carry.
|
|
80
|
-
*
|
|
81
|
-
* The browser fills these from attributes it already has. It never names a
|
|
82
|
-
* function — only a name the page declared — which is what keeps this from
|
|
83
|
-
* being an RPC endpoint. A value may ride along because a `<select>` has one
|
|
84
|
-
* and there is nowhere else to put it; it is a string from a control, not an
|
|
85
|
-
* argument list.
|
|
86
|
-
*/
|
|
87
|
-
export interface Where {
|
|
88
|
-
list?: string;
|
|
89
|
-
row?: string;
|
|
90
|
-
key?: string;
|
|
91
|
-
cell?: string;
|
|
92
|
-
value?: string;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* What an action does, and which queries must re-run once it has.
|
|
77
|
+
* What a handler does, and which queries re-run once it has.
|
|
96
78
|
*
|
|
97
|
-
* `run` may also return
|
|
98
|
-
* ones. That is how a patch reaches the browser: a query answers
|
|
99
|
-
*
|
|
100
|
-
* what it changed and can say only that.
|
|
79
|
+
* `run` may also return answers of its own, which are laid over the
|
|
80
|
+
* refreshed ones. That is how a patch reaches the browser: a query answers
|
|
81
|
+
* for all its rows and cannot know why it re-ran, but the handler knows
|
|
82
|
+
* exactly what it changed and can say only that.
|
|
101
83
|
*/
|
|
102
|
-
export interface
|
|
103
|
-
run: (ctx: HubContext,
|
|
84
|
+
export interface Handler<R = RequestFields> {
|
|
85
|
+
run: (ctx: HubContext, request: R) => void | HubData | Promise<void | HubData>;
|
|
104
86
|
refresh: string[];
|
|
105
87
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
* refresh — but `where` carries only what that operation is typed to carry
|
|
109
|
-
* on the wire, rather than the general `Where`.
|
|
110
|
-
*/
|
|
111
|
-
export interface CrudOp<W> {
|
|
112
|
-
run: (ctx: HubContext, where: W) => void | HubData | Promise<void | HubData>;
|
|
113
|
-
refresh: string[];
|
|
114
|
-
}
|
|
115
|
-
export type RowDeleteOp = CrudOp<{
|
|
116
|
-
key: string;
|
|
117
|
-
}>;
|
|
118
|
-
export type RowInsertOp = CrudOp<{
|
|
88
|
+
export type RowInsertHandler = Handler<{
|
|
89
|
+
key?: string;
|
|
119
90
|
values: Record<string, string>;
|
|
120
91
|
}>;
|
|
121
|
-
/** `values` holds only the columns being set
|
|
122
|
-
export type
|
|
92
|
+
/** `values` holds only the columns being set, as an SQL UPDATE sets them. */
|
|
93
|
+
export type RowUpdateHandler = Handler<{
|
|
123
94
|
key: string;
|
|
124
95
|
values: Record<string, string>;
|
|
125
96
|
}>;
|
|
97
|
+
export type RowDeleteHandler = Handler<{
|
|
98
|
+
key: string;
|
|
99
|
+
}>;
|
|
126
100
|
/**
|
|
127
|
-
*
|
|
128
|
-
* `Requests.crud`. A
|
|
129
|
-
* cannot reach anything the page has not published
|
|
130
|
-
* action.
|
|
101
|
+
* What the requests Loadbare provides do to one query, keyed by its name in
|
|
102
|
+
* `Requests.crud`. A query with no entry here permits none of them: the wire
|
|
103
|
+
* cannot reach anything the page has not published.
|
|
131
104
|
*
|
|
132
|
-
* Every key
|
|
133
|
-
*
|
|
134
|
-
* are one vocabulary. All three are list operations: each needs a key, and a
|
|
135
|
-
* key exists only on a live row inside a list.
|
|
105
|
+
* Every key is the request name with the prefix stripped and the rest
|
|
106
|
+
* camel-cased: `lb-row-insert` runs `rowInsert`.
|
|
136
107
|
*/
|
|
137
108
|
export interface Crud {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
109
|
+
rowInsert?: RowInsertHandler;
|
|
110
|
+
rowUpdate?: RowUpdateHandler;
|
|
111
|
+
rowDelete?: RowDeleteHandler;
|
|
141
112
|
}
|
|
142
113
|
/**
|
|
143
|
-
* The shape of a `<
|
|
114
|
+
* The shape of a `<stub>.requests.ts` module.
|
|
144
115
|
*
|
|
145
|
-
* `onPageEnter` runs
|
|
146
|
-
*
|
|
147
|
-
*
|
|
116
|
+
* `onPageEnter` runs when the page loads, before its queries. It declares no
|
|
117
|
+
* refresh set: the page's queries all run afterward, so whatever it changed
|
|
118
|
+
* is already in the response.
|
|
148
119
|
*
|
|
149
|
-
* `
|
|
150
|
-
* declare is refused, so the wire cannot reach anything the page has
|
|
151
|
-
* published.
|
|
120
|
+
* `handlers` holds the page's declared requests, by name. A name the page
|
|
121
|
+
* did not declare is refused, so the wire cannot reach anything the page has
|
|
122
|
+
* not published.
|
|
152
123
|
*
|
|
153
|
-
* `crud`
|
|
154
|
-
* they operate on rather than by a declared name — there is nothing to name,
|
|
155
|
-
* since the row's own binding says what it is.
|
|
124
|
+
* `crud` holds what the requests Loadbare provides run, by query name.
|
|
156
125
|
*/
|
|
157
126
|
export interface Requests {
|
|
158
127
|
onPageEnter?: (ctx: HubContext) => void | Promise<void>;
|
|
159
|
-
|
|
128
|
+
handlers?: Record<string, Handler>;
|
|
160
129
|
crud?: Record<string, Crud>;
|
|
161
130
|
}
|
|
162
|
-
/** A page is three files sharing a
|
|
131
|
+
/** A page is three files sharing a stub; two of them are these. */
|
|
163
132
|
export interface Page {
|
|
164
133
|
queries: Queries;
|
|
165
134
|
requests: Requests;
|
|
166
135
|
}
|
|
167
136
|
/**
|
|
168
|
-
* The page registry. The builder
|
|
169
|
-
* `.
|
|
170
|
-
* "Generating the server-side page registry".
|
|
137
|
+
* The page registry. The builder writes it from every `.requests.ts` and
|
|
138
|
+
* `.queries.ts` it discovers — see docs/reference/builder.md.
|
|
171
139
|
*/
|
|
172
140
|
export type Pages = Record<string, Page>;
|
|
173
141
|
/**
|
|
174
|
-
* The engine.
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
* `dataForPage` with it, and sends the load with the row beside it.
|
|
179
|
-
* `hubRoutes` does exactly that.
|
|
142
|
+
* The engine. Both calls answer with response items. A request whose
|
|
143
|
+
* handler returned `url()` answers with the `lb-url` item alone: the caller
|
|
144
|
+
* builds a context at the new URL, calls `dataForPage` with it, and sends
|
|
145
|
+
* the load beside the item. `hubRoutes` does exactly that.
|
|
180
146
|
*/
|
|
181
147
|
export interface Hub {
|
|
182
|
-
/**
|
|
183
|
-
dataForPage(page: string, ctx: HubContext): Promise<
|
|
184
|
-
/**
|
|
185
|
-
|
|
186
|
-
* set declared with it.
|
|
187
|
-
*/
|
|
188
|
-
runAction(page: string, name: string, where: Where, ctx: HubContext): Promise<HubData>;
|
|
189
|
-
/** Drop one row: run the list's declared `rowDelete`, then its refresh set. */
|
|
190
|
-
runRowDelete(page: string, where: {
|
|
191
|
-
list: string;
|
|
192
|
-
key: string;
|
|
193
|
-
}, ctx: HubContext): Promise<HubData>;
|
|
194
|
-
/** Add one row: run the list's declared `rowInsert`, then its refresh set. */
|
|
195
|
-
runRowInsert(page: string, where: {
|
|
196
|
-
list: string;
|
|
197
|
-
values: Record<string, string>;
|
|
198
|
-
}, ctx: HubContext): Promise<HubData>;
|
|
199
|
-
/**
|
|
200
|
-
* Edit a row: run the list's declared `rowUpdate`, then its refresh set.
|
|
201
|
-
* `values` holds only the columns being set — one from a widget that
|
|
202
|
-
* commits a cell, several from a form — and a column absent from it is left
|
|
203
|
-
* as it is, as an SQL UPDATE leaves it.
|
|
204
|
-
*/
|
|
205
|
-
runRowUpdate(page: string, where: {
|
|
206
|
-
list: string;
|
|
207
|
-
key: string;
|
|
208
|
-
values: Record<string, string>;
|
|
209
|
-
}, ctx: HubContext): Promise<HubData>;
|
|
148
|
+
/** A page load: its `onPageEnter`, then all of its queries. */
|
|
149
|
+
dataForPage(page: string, ctx: HubContext): Promise<HubResponse>;
|
|
150
|
+
/** A request: run the handler its name picks, then the refresh set. */
|
|
151
|
+
runRequest(page: string, request: HubRequest, ctx: HubContext): Promise<HubResponse>;
|
|
210
152
|
}
|
|
211
153
|
/**
|
|
212
154
|
* Build the engine over a set of pages. The pages are fixed at startup; the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-server.d.ts","sourceRoot":"","sources":["../../server/lb-server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"lb-server.d.ts","sourceRoot":"","sources":["../../server/lb-server.ts"],"names":[],"mappings":"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"}
|
package/dist/server/lb-server.js
CHANGED
|
@@ -2,65 +2,78 @@
|
|
|
2
2
|
//
|
|
3
3
|
// This is the counterpart to core/lb-constants.ts: that file is the
|
|
4
4
|
// vocabulary the browser writes, this one is the vocabulary the application
|
|
5
|
-
// writes. The engine below is the whole of
|
|
6
|
-
// HTTP server, no router, and no data layer.
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
// writes. The engine below is the whole of Loadbare on the server. It ships
|
|
6
|
+
// no HTTP server, no router, and no data layer.
|
|
7
|
+
import { isRowRequest } from "../core/lb-types.js";
|
|
8
|
+
import { KIND_ROW, KIND_ROWS, LB_RESERVED_PREFIX, REQUEST_ROW_DELETE, REQUEST_ROW_INSERT, REQUEST_ROW_UPDATE, URL_COLUMN_PATH, URL_QUERY, } from "../core/lb-constants.js";
|
|
9
|
+
/** A query that answers with one row, identified by its `key` column. */
|
|
10
|
+
export function row(key, run) {
|
|
11
|
+
return { kind: KIND_ROW, key, run };
|
|
11
12
|
}
|
|
12
13
|
/**
|
|
13
|
-
* A query that answers with
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* There is no wrapper around the array, because the declaration already said
|
|
17
|
-
* this name answers with rows.
|
|
14
|
+
* A query that answers with all its rows, and therefore also their order.
|
|
15
|
+
* Rows land by their `key` column: a row whose key is not in the answer is
|
|
16
|
+
* gone.
|
|
18
17
|
*/
|
|
19
|
-
export function
|
|
20
|
-
return { kind:
|
|
18
|
+
export function rows(key, run) {
|
|
19
|
+
return { kind: KIND_ROWS, key, run };
|
|
21
20
|
}
|
|
22
21
|
/**
|
|
23
|
-
* Only what changed, returned from a
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
22
|
+
* Only what changed, returned from a handler for a `rows` query. Rows named
|
|
23
|
+
* here are added or updated, keys in `drop` are removed, and everything
|
|
24
|
+
* unnamed is left alone — its contents, and its place in whatever order the
|
|
25
|
+
* page is keeping.
|
|
27
26
|
*
|
|
28
|
-
* `Array.isArray` is what tells a patch from
|
|
27
|
+
* `Array.isArray` is what tells a patch from all rows, so a patch needs no
|
|
29
28
|
* marker of its own and no column name is reserved to carry one.
|
|
30
29
|
*/
|
|
31
30
|
export function patch(change) {
|
|
32
|
-
|
|
31
|
+
const made = { ...change };
|
|
32
|
+
patches.add(made);
|
|
33
|
+
return made;
|
|
33
34
|
}
|
|
34
35
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
* Every object `patch()` made. A patch and a row are both plain objects, so
|
|
37
|
+
* this is how the engine refuses a patch answering a `row` query.
|
|
38
|
+
*/
|
|
39
|
+
const patches = new WeakSet();
|
|
40
|
+
/**
|
|
41
|
+
* A new row for `lb-url`, returned from a handler when only the handler can
|
|
42
|
+
* know where the page belongs: the key of a row it inserted, or the absence
|
|
43
|
+
* of one it deleted.
|
|
39
44
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* is leaving. This is Post/Redirect/Get without the redirect. The hub writes the parms into the URL, replacing the history
|
|
44
|
-
* entry, and lands the load.
|
|
45
|
+
* A column other than `lb-path` is a query parm: set, or taken out when its
|
|
46
|
+
* value is empty. With `lb-path` naming another page, the page is entered
|
|
47
|
+
* with only the parms named here; otherwise every other parm is kept.
|
|
45
48
|
*
|
|
46
|
-
*
|
|
49
|
+
* The page then loads at the new URL in the same round trip, as a cold load
|
|
50
|
+
* of it would, so the refresh set is not run and whatever else the handler
|
|
51
|
+
* returned is dropped: both were answers for the URL the page is leaving.
|
|
52
|
+
* This is Post/Redirect/Get without the redirect.
|
|
47
53
|
*/
|
|
48
|
-
export function
|
|
49
|
-
return { [
|
|
54
|
+
export function url(columns) {
|
|
55
|
+
return { [URL_QUERY]: { ...columns } };
|
|
50
56
|
}
|
|
57
|
+
/** The camel-cased `Crud` key of a request Loadbare provides. */
|
|
58
|
+
const CRUD_KEYS = {
|
|
59
|
+
[REQUEST_ROW_INSERT]: "rowInsert",
|
|
60
|
+
[REQUEST_ROW_UPDATE]: "rowUpdate",
|
|
61
|
+
[REQUEST_ROW_DELETE]: "rowDelete",
|
|
62
|
+
};
|
|
51
63
|
/**
|
|
52
64
|
* Build the engine over a set of pages. The pages are fixed at startup; the
|
|
53
65
|
* context is not, and arrives with each call.
|
|
54
66
|
*/
|
|
55
67
|
export function createHub(pages) {
|
|
56
|
-
// Names beginning with the reserved prefix are Loadbare's —
|
|
57
|
-
//
|
|
68
|
+
// Names beginning with the reserved prefix are Loadbare's — its own query,
|
|
69
|
+
// the requests it provides — so an application cannot declare one.
|
|
58
70
|
// Refused at startup, because a name is a fact about the page and not
|
|
59
71
|
// about any request.
|
|
60
72
|
for (const [page, entry] of Object.entries(pages)) {
|
|
61
73
|
const declared = [
|
|
62
74
|
...Object.keys(entry.queries),
|
|
63
|
-
...Object.keys(entry.requests.
|
|
75
|
+
...Object.keys(entry.requests.handlers ?? {}),
|
|
76
|
+
...Object.keys(entry.requests.crud ?? {}),
|
|
64
77
|
];
|
|
65
78
|
for (const name of declared) {
|
|
66
79
|
if (name.startsWith(LB_RESERVED_PREFIX)) {
|
|
@@ -69,6 +82,44 @@ export function createHub(pages) {
|
|
|
69
82
|
}
|
|
70
83
|
}
|
|
71
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Answers into response items, adding the kind and key each query
|
|
87
|
+
* declared. An answer that disagrees with its declaration is a mistake in
|
|
88
|
+
* the application rather than a case to handle, and is left out: a page is
|
|
89
|
+
* better off missing one query than showing the wrong shape for it.
|
|
90
|
+
*/
|
|
91
|
+
function items(page, data) {
|
|
92
|
+
const out = [];
|
|
93
|
+
for (const [name, result] of Object.entries(data)) {
|
|
94
|
+
const query = pages[page]?.queries[name];
|
|
95
|
+
if (!query) {
|
|
96
|
+
console.warn(`loadbare: page '${page}' declares no query '${name}'`);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const item = {
|
|
100
|
+
query: name,
|
|
101
|
+
kind: query.kind,
|
|
102
|
+
key: query.key,
|
|
103
|
+
};
|
|
104
|
+
if (query.kind === KIND_ROW) {
|
|
105
|
+
if (Array.isArray(result) || patches.has(result)) {
|
|
106
|
+
console.warn(`loadbare: query '${name}' is declared row but answered with ` +
|
|
107
|
+
`${Array.isArray(result) ? "rows" : "a patch"}`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
item.row = result;
|
|
111
|
+
}
|
|
112
|
+
else if (Array.isArray(result)) {
|
|
113
|
+
item.rows = result;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
item.patch = result;
|
|
117
|
+
}
|
|
118
|
+
out.push(item);
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
/** Run queries by name, each answering with all its rows or its row. */
|
|
72
123
|
async function run(page, names, ctx) {
|
|
73
124
|
const data = {};
|
|
74
125
|
for (const name of names) {
|
|
@@ -78,10 +129,7 @@ export function createHub(pages) {
|
|
|
78
129
|
continue;
|
|
79
130
|
}
|
|
80
131
|
const result = await query.run(ctx);
|
|
81
|
-
|
|
82
|
-
// the query rather than a case to handle. Refused here, because a page
|
|
83
|
-
// is better off missing one name than showing the wrong shape for it.
|
|
84
|
-
if (Array.isArray(result) !== (query.kind === "list")) {
|
|
132
|
+
if (Array.isArray(result) !== (query.kind === KIND_ROWS)) {
|
|
85
133
|
console.warn(`loadbare: query '${name}' is declared ${query.kind} but answered ` +
|
|
86
134
|
`with ${Array.isArray(result) ? "rows" : "one row"}`);
|
|
87
135
|
continue;
|
|
@@ -91,73 +139,78 @@ export function createHub(pages) {
|
|
|
91
139
|
return data;
|
|
92
140
|
}
|
|
93
141
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* one that knows what actually changed.
|
|
142
|
+
* Run a handler, then its refresh set against the same context, laying
|
|
143
|
+
* what the handler itself returned over the refreshed queries — the
|
|
144
|
+
* narrower answer wins because it knows what actually changed.
|
|
98
145
|
*
|
|
99
|
-
*
|
|
100
|
-
* Loading the page
|
|
101
|
-
*
|
|
146
|
+
* A handler that returned `url()` answers with the `lb-url` item alone.
|
|
147
|
+
* Loading the page there needs a context built at the new URL, and
|
|
148
|
+
* building one is the caller's.
|
|
102
149
|
*/
|
|
103
|
-
async function settle(page,
|
|
104
|
-
if (!
|
|
150
|
+
async function settle(page, handler, request, ctx, notFound) {
|
|
151
|
+
if (!handler) {
|
|
105
152
|
console.warn(notFound);
|
|
106
|
-
return
|
|
153
|
+
return [];
|
|
107
154
|
}
|
|
108
|
-
const { [
|
|
109
|
-
if (
|
|
110
|
-
const refused = refusal(
|
|
155
|
+
const { [URL_QUERY]: moved, ...stated } = (await handler.run(ctx, request)) ?? {};
|
|
156
|
+
if (moved !== undefined) {
|
|
157
|
+
const refused = refusal(moved);
|
|
111
158
|
if (refused === undefined) {
|
|
112
|
-
return
|
|
159
|
+
return [
|
|
160
|
+
{
|
|
161
|
+
query: URL_QUERY,
|
|
162
|
+
kind: KIND_ROW,
|
|
163
|
+
key: URL_COLUMN_PATH,
|
|
164
|
+
row: moved,
|
|
165
|
+
},
|
|
166
|
+
];
|
|
113
167
|
}
|
|
114
|
-
// The
|
|
115
|
-
//
|
|
116
|
-
console.warn(`loadbare: page '${page}' returned ${
|
|
117
|
-
`ignoring it`);
|
|
168
|
+
// The handler has already run, so the page still hears about it, by
|
|
169
|
+
// its refresh set, as if no row for lb-url had been returned.
|
|
170
|
+
console.warn(`loadbare: page '${page}' returned ${URL_QUERY} ${refused}, ignoring it`);
|
|
118
171
|
}
|
|
119
|
-
return
|
|
172
|
+
return items(page, {
|
|
173
|
+
...(await run(page, handler.refresh, ctx)),
|
|
174
|
+
...stated,
|
|
175
|
+
});
|
|
120
176
|
}
|
|
121
177
|
return {
|
|
122
178
|
async dataForPage(page, ctx) {
|
|
123
179
|
const entry = pages[page];
|
|
124
180
|
if (!entry) {
|
|
125
181
|
console.warn(`loadbare: no page '${page}'`);
|
|
126
|
-
return
|
|
182
|
+
return [];
|
|
127
183
|
}
|
|
128
184
|
await entry.requests.onPageEnter?.(ctx);
|
|
129
|
-
return run(page, Object.keys(entry.queries), ctx);
|
|
185
|
+
return items(page, await run(page, Object.keys(entry.queries), ctx));
|
|
130
186
|
},
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return settle(page, pages[page]?.requests.crud?.[where.list]?.rowInsert, { values: where.values }, ctx, `loadbare: page '${page}' declares no rowInsert for '${where.list}'`);
|
|
139
|
-
},
|
|
140
|
-
runRowUpdate(page, where, ctx) {
|
|
141
|
-
return settle(page, pages[page]?.requests.crud?.[where.list]?.rowUpdate, { key: where.key, values: where.values }, ctx, `loadbare: page '${page}' declares no rowUpdate for '${where.list}'`);
|
|
187
|
+
runRequest(page, request, ctx) {
|
|
188
|
+
const { name, ...fields } = request;
|
|
189
|
+
if (isRowRequest(name)) {
|
|
190
|
+
const query = fields.query ?? "";
|
|
191
|
+
return settle(page, pages[page]?.requests.crud?.[query]?.[CRUD_KEYS[name]], fields, ctx, `loadbare: page '${page}' declares no ${CRUD_KEYS[name]} for '${query}'`);
|
|
192
|
+
}
|
|
193
|
+
return settle(page, pages[page]?.requests.handlers?.[name], fields, ctx, `loadbare: page '${page}' declares no request '${name}'`);
|
|
142
194
|
},
|
|
143
195
|
};
|
|
144
196
|
}
|
|
145
197
|
/**
|
|
146
|
-
* Why returned
|
|
198
|
+
* Why a returned row for `lb-url` cannot be used, or nothing when it can.
|
|
147
199
|
*
|
|
148
|
-
* A value is a string, as a control's is and a URL's is. At least one
|
|
149
|
-
* is named: a
|
|
150
|
-
* page at the same URL is not what this is for.
|
|
200
|
+
* A value is a string, as a control's is and a URL's is. At least one column
|
|
201
|
+
* is named: a row naming none changes nothing.
|
|
151
202
|
*/
|
|
152
|
-
function refusal(
|
|
153
|
-
if (typeof
|
|
203
|
+
function refusal(columns) {
|
|
204
|
+
if (typeof columns !== "object" ||
|
|
205
|
+
columns === null ||
|
|
206
|
+
Array.isArray(columns)) {
|
|
154
207
|
return "that is not a row";
|
|
155
208
|
}
|
|
156
|
-
const values = Object.values(
|
|
209
|
+
const values = Object.values(columns);
|
|
157
210
|
if (values.length === 0)
|
|
158
|
-
return "naming no
|
|
211
|
+
return "naming no column";
|
|
159
212
|
if (!values.every((v) => typeof v === "string")) {
|
|
160
|
-
return `holding a value that is not a string; write
|
|
213
|
+
return `holding a value that is not a string; write url({ name: "..." })`;
|
|
161
214
|
}
|
|
162
215
|
return undefined;
|
|
163
216
|
}
|