@loadbare/app 0.16.0 → 0.18.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 +10 -14
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +15 -1
- package/dist/build/assemble.js.map +1 -1
- package/dist/core/lb-constants.d.ts +6 -2
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +38 -8
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/core/lb-types.d.ts +57 -20
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js +122 -11
- package/dist/core/lb-types.js.map +1 -1
- package/dist/hub/lb-apply.d.ts +38 -26
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +655 -471
- package/dist/hub/lb-apply.js.map +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +77 -59
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/dist/hub/lb-lifecycle.d.ts +1 -2
- package/dist/hub/lb-lifecycle.d.ts.map +1 -1
- package/dist/hub/lb-lifecycle.js +9 -3
- package/dist/hub/lb-lifecycle.js.map +1 -1
- package/dist/server/lb-server.d.ts +29 -12
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +79 -19
- package/dist/server/lb-server.js.map +1 -1
- package/docs/TECHREF-1.0.md +167 -53
- package/docs/comparison.md +9 -8
- package/docs/reference/chrome.md +8 -5
- package/docs/reference/custom-elements.md +25 -7
- package/docs/reference/data-binding.md +125 -22
- package/docs/reference/page-files.md +56 -11
- package/docs/reference/widgets.md +16 -1
- package/docs/roadmap.md +8 -0
- package/docs/testing.md +6 -2
- package/docs/theory-old.md +839 -0
- package/docs/theory.md +113 -839
- package/package.json +1 -1
- package/skills/loadbare-app/SKILL.md +32 -20
- package/skills/loadbare-app/references/TECHREF-1.0.md +167 -53
- package/skills/loadbare-app/references/chrome.md +8 -5
- package/skills/loadbare-app/references/custom-elements.md +25 -7
- package/skills/loadbare-app/references/data-binding.md +125 -22
- package/skills/loadbare-app/references/page-files.md +56 -11
- package/skills/loadbare-app/references/widgets.md +16 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-lifecycle.d.ts","sourceRoot":"","sources":["../../hub/lb-lifecycle.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAExE,6DAA6D;AAC7D,eAAO,MAAM,MAAM,sBAA2B,CAAC;AAE/C,uCAAuC;AACvC,eAAO,MAAM,OAAO,yCAAmD,CAAC;AAmDxE;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,OAAO,GAAG,gBAAgB,EAChC,GAAG,EAAE,QAAQ,GACZ,IAAI,CASN;AAED,4EAA4E;AAC5E,wBAAgB,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAE1C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,IAAI,
|
|
1
|
+
{"version":3,"file":"lb-lifecycle.d.ts","sourceRoot":"","sources":["../../hub/lb-lifecycle.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAExE,6DAA6D;AAC7D,eAAO,MAAM,MAAM,sBAA2B,CAAC;AAE/C,uCAAuC;AACvC,eAAO,MAAM,OAAO,yCAAmD,CAAC;AAmDxE;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,OAAO,GAAG,gBAAgB,EAChC,GAAG,EAAE,QAAQ,GACZ,IAAI,CASN;AAED,4EAA4E;AAC5E,wBAAgB,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAE1C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAoBnC;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,GAAG,IAAI,CAQ9D;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAIzE"}
|
package/dist/hub/lb-lifecycle.js
CHANGED
|
@@ -97,7 +97,14 @@ export function placed(root) {
|
|
|
97
97
|
export function attachPlaced() {
|
|
98
98
|
const roots = placedSince;
|
|
99
99
|
placedSince = [];
|
|
100
|
-
const
|
|
100
|
+
const all = new Set(roots);
|
|
101
|
+
const outer = roots.filter((root) => {
|
|
102
|
+
for (let up = root.parentElement; up; up = up.parentElement) {
|
|
103
|
+
if (all.has(up))
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
});
|
|
101
108
|
for (const root of outer) {
|
|
102
109
|
if (root.closest(LEAVING))
|
|
103
110
|
continue;
|
|
@@ -113,8 +120,7 @@ export function attachPlaced() {
|
|
|
113
120
|
}
|
|
114
121
|
/**
|
|
115
122
|
* Detach every attached custom element in nodes the hub has removed, inner
|
|
116
|
-
* before outer. An element the hub never attached
|
|
117
|
-
* row leaves behind, hears nothing.
|
|
123
|
+
* before outer. An element the hub never attached hears nothing.
|
|
118
124
|
*/
|
|
119
125
|
export function detachRemoved(nodes) {
|
|
120
126
|
const found = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-lifecycle.js","sourceRoot":"","sources":["../../hub/lb-lifecycle.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,SAAS,GACV,MAAM,yBAAyB,CAAC;AAGjC,6DAA6D;AAC7D,MAAM,CAAC,MAAM,MAAM,GAAG,YAAY,SAAS,GAAG,CAAC;AAE/C,uCAAuC;AACvC,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,gBAAgB,OAAO,kBAAkB,GAAG,CAAC;AAExE,0EAA0E;AAC1E,MAAM,YAAY,GAAG,CAAC,CAAC;AAEvB,sEAAsE;AACtE,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAW,CAAC;AAExC,4DAA4D;AAC5D,IAAI,WAAW,GAAc,EAAE,CAAC;AAEhC,kDAAkD;AAClD,SAAS,QAAQ,CAAC,EAAW;IAC3B,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9C,MAAM,QAAQ,GAAI,UAAyD;SACxE,cAAc,CAAC;IAClB,MAAM,OAAO,GAAG,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC;IAC5C,OAAO,OAAO,KAAK,SAAS,IAAI,EAAE,YAAY,OAAO,CAAC;AACxD,CAAC;AAED;;;GAGG;AACH,SAAS,QAAQ,CAAC,IAAgC,EAAE,KAAgB;IAClE,IAAI,IAAI,CAAC,QAAQ,KAAK,YAAY,IAAI,QAAQ,CAAC,IAAe,CAAC,EAAE,CAAC;QAChE,KAAK,CAAC,IAAI,CAAC,IAAe,CAAC,CAAC;IAC9B,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClC,QAAQ,CACN,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAE,KAA6B,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EACtE,KAAK,CACN,CAAC;IACJ,CAAC;AACH,CAAC;AAED,4DAA4D;AAC5D,SAAS,IAAI,CACX,EAAW,EACX,MAAS,EACT,GAAG,IAA8C;IAEjD,MAAM,EAAE,GAAI,EAA6B,CAAC,MAAM,CACL,CAAC;IAC5C,IAAI,CAAC;QACH,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC,SAAS,KAAK,MAAM,QAAQ,EAAE,GAAG,CAAC,CAAC;IAClE,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa,CAC3B,IAAgC,EAChC,GAAa;IAEb,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC;QACnD,MAAM,OAAO,GAAI,MAA8B,CAAC,OAAO,CAAC;QACxD,MAAM,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;QACzC,IAAI,CAAC,MAAM;YAAE,SAAS;QACtB,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC1C,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACzB,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,MAAM,CAAC,IAAa;IAClC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,KAAK,GAAG,WAAW,CAAC;IAC1B,WAAW,GAAG,EAAE,CAAC;IACjB,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"lb-lifecycle.js","sourceRoot":"","sources":["../../hub/lb-lifecycle.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference lib=\"dom\" />\n\n/**\n * The hub's lifecycle — see docs/reference/custom-elements.md, When its code\n * runs.\n *\n * A custom element hears what the hub did to it, in the hub's terms, rather\n * than the browser's insertions. The hub knows every moment it places an\n * element and every moment it removes one, so it says so: `lbAttached` once\n * the element is placed and filled, `lbDetached` once it is gone. A move, or\n * `lb-show` parking an element and bringing it back, is neither, and the\n * element hears nothing.\n *\n * Placing is noted as it happens and told at the end of the landing that\n * follows, so an element is filled before it hears it, and an outer element\n * hears it before the ones inside it.\n *\n * Every custom element the hub places has upgraded before it is filled: a\n * live row and a group are imported into the document, and an absent branch\n * is imported again as it arrives — see upgradeAbsent().\n */\n\nimport {\n ATTR_GROUP_LEAVING,\n ATTR_ROW_LEAVING,\n ATTR_SHOW,\n} from \"../core/lb-constants.js\";\nimport type { HubLifecycle, HubRequestDone } from \"../core/lb-types.js\";\n\n/** The template holding an absent branch — see ATTR_SHOW. */\nexport const PARKED = `template[${ATTR_SHOW}]`;\n\n/** A row or a group on its way out. */\nexport const LEAVING = `[${ATTR_ROW_LEAVING}], [${ATTR_GROUP_LEAVING}]`;\n\n/** An Element's nodeType. Tier 3 has no `Node` global to read it from. */\nconst ELEMENT_NODE = 1;\n\n/** Every custom element the hub has attached and not yet detached. */\nconst attached = new WeakSet<Element>();\n\n/** What the hub has placed since the last landing ended. */\nlet placedSince: Element[] = [];\n\n/** A custom element whose class has run on it. */\nfunction upgraded(el: Element): boolean {\n if (!el.localName.includes(\"-\")) return false;\n const registry = (globalThis as { customElements?: CustomElementRegistry })\n .customElements;\n const defined = registry?.get(el.localName);\n return defined !== undefined && el instanceof defined;\n}\n\n/**\n * Every upgraded custom element at and under `root`, in tree order, outer\n * before inner. An absent branch is searched where its template stands.\n */\nfunction customIn(root: Element | DocumentFragment, found: Element[]): void {\n if (root.nodeType === ELEMENT_NODE && upgraded(root as Element)) {\n found.push(root as Element);\n }\n for (const child of root.children) {\n customIn(\n child.matches(PARKED) ? (child as HTMLTemplateElement).content : child,\n found,\n );\n }\n}\n\n/** Call one of the hub's methods, if the element has it. */\nfunction tell<K extends keyof HubLifecycle>(\n el: Element,\n method: K,\n ...args: Parameters<NonNullable<HubLifecycle[K]>>\n): void {\n const fn = (el as Element & HubLifecycle)[method] as\n ((...a: typeof args) => void) | undefined;\n try {\n fn?.apply(el, args);\n } catch (err) {\n console.error(`lb-hub: <${el.localName}> ${method} threw`, err);\n }\n}\n\n/**\n * Make every absent branch under `root` from elements that have upgraded.\n *\n * The builder ships each `lb-show` element inside its template, and a\n * template's content belongs to a document with no definitions, so nothing\n * shipped there has upgraded. Importing the branch into `doc` upgrades it as\n * it is made, and it goes back into its template already the class it will\n * be when it shows: the hub sets a control's `value` while it is away, and\n * calls its methods, as on any other.\n *\n * Called on what the hub places before anything lands on it, while every\n * branch is as the builder shipped it.\n */\nexport function upgradeAbsent(\n root: Element | DocumentFragment,\n doc: Document,\n): void {\n for (const parked of root.querySelectorAll(PARKED)) {\n const content = (parked as HTMLTemplateElement).content;\n const branch = content.firstElementChild;\n if (!branch) continue;\n const made = doc.importNode(branch, true);\n branch.replaceWith(made);\n upgradeAbsent(made, doc);\n }\n}\n\n/** Note that the hub has placed `root`, to attach at the end of landing. */\nexport function placed(root: Element): void {\n placedSince.push(root);\n}\n\n/**\n * Attach every custom element in what the hub has placed since the last\n * landing ended. A root inside another is reached from the outer one, so an\n * outer element is attached before the ones inside it. An element on its way\n * out is passed over.\n */\nexport function attachPlaced(): void {\n const roots = placedSince;\n placedSince = [];\n const all = new Set(roots);\n const outer = roots.filter((root) => {\n for (let up = root.parentElement; up; up = up.parentElement) {\n if (all.has(up)) return false;\n }\n return true;\n });\n for (const root of outer) {\n if (root.closest(LEAVING)) continue;\n const found: Element[] = [];\n customIn(root, found);\n for (const el of found) {\n if (attached.has(el) || el.closest(LEAVING)) continue;\n attached.add(el);\n tell(el, \"lbAttached\");\n }\n }\n}\n\n/**\n * Detach every attached custom element in nodes the hub has removed, inner\n * before outer. An element the hub never attached hears nothing.\n */\nexport function detachRemoved(nodes: Iterable<ChildNode>): void {\n const found: Element[] = [];\n for (const node of nodes) {\n if (node.nodeType === ELEMENT_NODE) customIn(node as Element, found);\n }\n for (const el of found.reverse()) {\n if (attached.delete(el)) tell(el, \"lbDetached\");\n }\n}\n\n/**\n * Tell the attached custom elements from `origin` outward how its request\n * turned out, innermost first.\n */\nexport function requestDone(origin: Element, detail: HubRequestDone): void {\n for (let el: Element | null = origin; el; el = el.parentElement) {\n if (attached.has(el)) tell(el, \"lbRequestDone\", detail, origin);\n }\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HubData, HubRequest, HubResponse, HubResult, Kind, GatheredRow, HubRefusal, Patch, RequestFields, Row } from "../core/lb-types.js";
|
|
1
|
+
import type { ColumnTypes, HubData, HubRequest, HubResponse, HubResult, Kind, GatheredRow, HubRefusal, 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
|
*
|
|
@@ -36,26 +36,28 @@ export interface Query {
|
|
|
36
36
|
readonly run: (ctx: HubContext) => HubResult | Promise<HubResult>;
|
|
37
37
|
/** A `rows` query's order, as `rows()` declared it. */
|
|
38
38
|
readonly order?: string;
|
|
39
|
-
/**
|
|
40
|
-
readonly
|
|
39
|
+
/** A `rows` query's column types, as `rows()` declared them. */
|
|
40
|
+
readonly types?: ColumnTypes;
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
43
|
* What a `rows` query declares about its order.
|
|
44
44
|
*
|
|
45
45
|
* `order` is the order the hub places its rows by: columns separated by
|
|
46
46
|
* commas, each descending when it begins with `-`, as in
|
|
47
|
-
* `category_order,-opened`.
|
|
48
|
-
*
|
|
49
|
-
*
|
|
47
|
+
* `category_order,-opened`. An order the user chose, held in `lb-choices`,
|
|
48
|
+
* replaces it. With neither, rows show in the order the query answered them
|
|
49
|
+
* in.
|
|
50
50
|
*
|
|
51
|
-
* `
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
51
|
+
* `types` names a type for any of its columns, and the hub sorts, filters
|
|
52
|
+
* and takes `lb-min` and `lb-max` of that column read as its type rather
|
|
53
|
+
* than by the JSON type its values arrive as: `{ amount: "number" }` for a
|
|
54
|
+
* Postgres `numeric` that arrives as a string. A column it does not name
|
|
55
|
+
* compares by its JSON type. The order and the types go to the browser
|
|
56
|
+
* once, with the answer that loads the page.
|
|
55
57
|
*/
|
|
56
58
|
export interface RowsOptions {
|
|
57
59
|
order?: string;
|
|
58
|
-
|
|
60
|
+
types?: ColumnTypes;
|
|
59
61
|
}
|
|
60
62
|
/** A query that answers with one row, identified by its `key` column. */
|
|
61
63
|
export declare function row(key: string, run: (ctx: HubContext) => Row | Promise<Row>): Query;
|
|
@@ -93,6 +95,15 @@ export declare function patch(change: {
|
|
|
93
95
|
* This is Post/Redirect/Get without the redirect.
|
|
94
96
|
*/
|
|
95
97
|
export declare function url(columns: Record<string, string>): HubData;
|
|
98
|
+
/**
|
|
99
|
+
* The user's choices for the page, returned from `onPageEnter` to restore
|
|
100
|
+
* what the user last chose: an order, a filter. Each column is a column of
|
|
101
|
+
* `lb-choices`, and the hub places the page's rows by them as they land.
|
|
102
|
+
* Return it beside `url()` with the spread both are written for:
|
|
103
|
+
*
|
|
104
|
+
* return { ...url({ acct: "23" }), ...choices({ sort: "-amount" }) };
|
|
105
|
+
*/
|
|
106
|
+
export declare function choices(columns: Record<string, string>): HubData;
|
|
96
107
|
/**
|
|
97
108
|
* A handler's refusal: thrown, and answered to the browser with the words
|
|
98
109
|
* it carries. Every other error a handler throws reaches the browser as a
|
|
@@ -171,7 +182,12 @@ export interface Crud {
|
|
|
171
182
|
* page to other query parms or another page before anything shows, as an
|
|
172
183
|
* application restoring what the user last had on screen does. The page
|
|
173
184
|
* then loads there in the same round trip, and `onPageEnter` running for
|
|
174
|
-
* that load is not followed again.
|
|
185
|
+
* that load is not followed again. It may return `choices()`, with `url()`
|
|
186
|
+
* or alone, to restore the user's choices for the page.
|
|
187
|
+
*
|
|
188
|
+
* `onChoices` runs each time the user makes a choice, with every choice
|
|
189
|
+
* made on the page, so an application can keep them for `onPageEnter` to
|
|
190
|
+
* restore. It answers with nothing, by convention.
|
|
175
191
|
*
|
|
176
192
|
* `handlers` holds the page's declared requests, by name. A name the page
|
|
177
193
|
* did not declare is refused, so the wire cannot reach anything the page has
|
|
@@ -181,6 +197,7 @@ export interface Crud {
|
|
|
181
197
|
*/
|
|
182
198
|
export interface Requests {
|
|
183
199
|
onPageEnter?: (ctx: HubContext) => void | HubData | Promise<void | HubData>;
|
|
200
|
+
onChoices?: (ctx: HubContext, choices: Record<string, string>) => void | HubData | Promise<void | HubData>;
|
|
184
201
|
handlers?: Record<string, Handler>;
|
|
185
202
|
crud?: Record<string, Crud>;
|
|
186
203
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-server.d.ts","sourceRoot":"","sources":["../../server/lb-server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,OAAO,EACP,UAAU,EACV,WAAW,EACX,SAAS,EACT,IAAI,EACJ,WAAW,EACX,UAAU,EACV,KAAK,EACL,aAAa,EAEb,GAAG,EACJ,MAAM,qBAAqB,CAAC;
|
|
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
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// writes. The engine below is the whole of Loadbare on the server. It ships
|
|
6
6
|
// no HTTP server, no router, and no data layer.
|
|
7
7
|
import { isRowRequest, nextLocation, parseOrder } 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";
|
|
8
|
+
import { CHOICES_QUERY, COLUMN_TYPES, 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
9
|
/** A query that answers with one row, identified by its `key` column. */
|
|
10
10
|
export function row(key, run) {
|
|
11
11
|
return { kind: KIND_ROW, key, run };
|
|
@@ -21,7 +21,7 @@ export function rows(key, run, options = {}) {
|
|
|
21
21
|
key,
|
|
22
22
|
run,
|
|
23
23
|
...(options.order !== undefined && { order: options.order }),
|
|
24
|
-
...(options.
|
|
24
|
+
...(options.types !== undefined && { types: options.types }),
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
@@ -60,6 +60,17 @@ const patches = new WeakSet();
|
|
|
60
60
|
export function url(columns) {
|
|
61
61
|
return { [URL_QUERY]: { ...columns } };
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* The user's choices for the page, returned from `onPageEnter` to restore
|
|
65
|
+
* what the user last chose: an order, a filter. Each column is a column of
|
|
66
|
+
* `lb-choices`, and the hub places the page's rows by them as they land.
|
|
67
|
+
* Return it beside `url()` with the spread both are written for:
|
|
68
|
+
*
|
|
69
|
+
* return { ...url({ acct: "23" }), ...choices({ sort: "-amount" }) };
|
|
70
|
+
*/
|
|
71
|
+
export function choices(columns) {
|
|
72
|
+
return { [CHOICES_QUERY]: { ...columns } };
|
|
73
|
+
}
|
|
63
74
|
/**
|
|
64
75
|
* A handler's refusal: thrown, and answered to the browser with the words
|
|
65
76
|
* it carries. Every other error a handler throws reaches the browser as a
|
|
@@ -118,16 +129,23 @@ export function createHub(pages) {
|
|
|
118
129
|
`beginning with '${LB_RESERVED_PREFIX}' are reserved`);
|
|
119
130
|
}
|
|
120
131
|
}
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
// handler may leave the query to say so.
|
|
132
|
+
// A crud handler always knows which rows it changed: an update or a
|
|
133
|
+
// delete names its row by key, and an insert has the new row's key once
|
|
134
|
+
// it has written it. So each can answer with a patch, and the hub places
|
|
135
|
+
// what the patch names by the query's order. Refreshing the whole set
|
|
136
|
+
// instead sends every row to say one thing. There is no case where that
|
|
137
|
+
// is the answer, so it is refused rather than warned about.
|
|
128
138
|
// An order is a fact about the query, so a malformed one is refused at
|
|
129
139
|
// startup, as a reserved name is.
|
|
140
|
+
// So is a column type the hub does not know.
|
|
130
141
|
for (const [name, query] of Object.entries(entry.queries)) {
|
|
142
|
+
for (const [column, type] of Object.entries(query.types ?? {})) {
|
|
143
|
+
if (!COLUMN_TYPES.includes(type)) {
|
|
144
|
+
throw new Error(`loadbare: page '${page}' query '${name}' declares column ` +
|
|
145
|
+
`'${column}' as '${type}'; a column type is one of ` +
|
|
146
|
+
`${COLUMN_TYPES.join(", ")}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
131
149
|
if (query.order === undefined)
|
|
132
150
|
continue;
|
|
133
151
|
try {
|
|
@@ -140,7 +158,7 @@ export function createHub(pages) {
|
|
|
140
158
|
for (const [name, crud] of Object.entries(entry.requests.crud ?? {})) {
|
|
141
159
|
if (entry.queries[name]?.kind !== KIND_ROWS)
|
|
142
160
|
continue;
|
|
143
|
-
for (const request of ["rowUpdate", "rowDelete"]) {
|
|
161
|
+
for (const request of ["rowInsert", "rowUpdate", "rowDelete"]) {
|
|
144
162
|
if (crud[request]?.refresh.includes(name)) {
|
|
145
163
|
throw new Error(`loadbare: page '${page}' refreshes '${name}' after its own ` +
|
|
146
164
|
`${request}; return the changed rows in a patch instead`);
|
|
@@ -153,8 +171,11 @@ export function createHub(pages) {
|
|
|
153
171
|
* declared. An answer that disagrees with its declaration is a mistake in
|
|
154
172
|
* the application rather than a case to handle, and is left out: a page is
|
|
155
173
|
* better off missing one query than showing the wrong shape for it.
|
|
174
|
+
*
|
|
175
|
+
* A page load also adds each `rows` query's order and column types, which
|
|
176
|
+
* the hub keeps for the answers after it.
|
|
156
177
|
*/
|
|
157
|
-
function items(page, data) {
|
|
178
|
+
function items(page, data, load = false) {
|
|
158
179
|
const out = [];
|
|
159
180
|
for (const [name, result] of Object.entries(data)) {
|
|
160
181
|
const query = pages[page]?.queries[name];
|
|
@@ -180,10 +201,10 @@ export function createHub(pages) {
|
|
|
180
201
|
item.rows = result;
|
|
181
202
|
else
|
|
182
203
|
item.patch = result;
|
|
183
|
-
if (query.order !== undefined)
|
|
204
|
+
if (load && query.order !== undefined)
|
|
184
205
|
item.order = query.order;
|
|
185
|
-
if (query.
|
|
186
|
-
item.
|
|
206
|
+
if (load && query.types !== undefined)
|
|
207
|
+
item.types = query.types;
|
|
187
208
|
}
|
|
188
209
|
out.push(item);
|
|
189
210
|
}
|
|
@@ -273,15 +294,19 @@ export function createHub(pages) {
|
|
|
273
294
|
console.warn(`loadbare: no page '${page}'`);
|
|
274
295
|
return [];
|
|
275
296
|
}
|
|
276
|
-
const { [URL_QUERY]: moved, ...stated } = (await entry.requests.onPageEnter?.(ctx)) ?? {};
|
|
297
|
+
const { [URL_QUERY]: moved, [CHOICES_QUERY]: chose, ...stated } = (await entry.requests.onPageEnter?.(ctx)) ?? {};
|
|
277
298
|
if (Object.keys(stated).length > 0) {
|
|
278
299
|
console.warn(`loadbare: page '${page}' onPageEnter returned answers; it returns ` +
|
|
279
|
-
`url() or nothing, ignoring them`);
|
|
300
|
+
`url(), choices() or nothing, ignoring them`);
|
|
280
301
|
}
|
|
281
302
|
const item = follow ? urlItem(page, moved) : undefined;
|
|
282
303
|
if (item && !(parms && leadsHere(page, parms, item.row)))
|
|
283
304
|
return [item];
|
|
284
|
-
|
|
305
|
+
// The choices go first, so the rows that read them land by them.
|
|
306
|
+
return [
|
|
307
|
+
...choicesItem(page, chose),
|
|
308
|
+
...items(page, await run(page, Object.keys(entry.queries), ctx), true),
|
|
309
|
+
];
|
|
285
310
|
},
|
|
286
311
|
runRequest(page, request, ctx) {
|
|
287
312
|
const { name, ...fields } = request;
|
|
@@ -290,6 +315,18 @@ export function createHub(pages) {
|
|
|
290
315
|
`values by query, ignoring it`);
|
|
291
316
|
return Promise.resolve([]);
|
|
292
317
|
}
|
|
318
|
+
// The user's choices, told to the page. One with no `onChoices` is
|
|
319
|
+
// not keeping them, and is not told.
|
|
320
|
+
if (fields.query === CHOICES_QUERY) {
|
|
321
|
+
const onChoices = pages[page]?.requests.onChoices;
|
|
322
|
+
if (name !== REQUEST_ROW_UPDATE || !onChoices) {
|
|
323
|
+
return Promise.resolve([]);
|
|
324
|
+
}
|
|
325
|
+
return settle(page, {
|
|
326
|
+
run: (c, r) => onChoices(c, r.values ?? {}),
|
|
327
|
+
refresh: [],
|
|
328
|
+
}, fields, ctx, "");
|
|
329
|
+
}
|
|
293
330
|
if (fields.keys !== undefined &&
|
|
294
331
|
(name === REQUEST_ROW_UPDATE || name === REQUEST_ROW_DELETE)) {
|
|
295
332
|
return eachKey(page, name, fields, ctx);
|
|
@@ -358,13 +395,36 @@ function urlItem(page, moved) {
|
|
|
358
395
|
console.warn(`loadbare: page '${page}' returned ${URL_QUERY} ${refused}, ignoring it`);
|
|
359
396
|
return undefined;
|
|
360
397
|
}
|
|
398
|
+
/**
|
|
399
|
+
* The `lb-choices` item for the choices `onPageEnter` returned, or nothing
|
|
400
|
+
* when it returned none or ones that cannot be used, which are reported.
|
|
401
|
+
*/
|
|
402
|
+
function choicesItem(page, chose) {
|
|
403
|
+
if (chose === undefined)
|
|
404
|
+
return [];
|
|
405
|
+
const refused = unusableUrl(chose);
|
|
406
|
+
if (refused !== undefined) {
|
|
407
|
+
console.warn(`loadbare: page '${page}' returned ${CHOICES_QUERY} ${refused}, ` +
|
|
408
|
+
`ignoring it`);
|
|
409
|
+
return [];
|
|
410
|
+
}
|
|
411
|
+
return [
|
|
412
|
+
{
|
|
413
|
+
query: CHOICES_QUERY,
|
|
414
|
+
kind: KIND_ROW,
|
|
415
|
+
key: URL_COLUMN_PATH,
|
|
416
|
+
row: chose,
|
|
417
|
+
},
|
|
418
|
+
];
|
|
419
|
+
}
|
|
361
420
|
/** Whether a row for `lb-url` leads to the page and parms already in hand. */
|
|
362
421
|
function leadsHere(page, parms, row) {
|
|
363
422
|
const next = nextLocation(page, parms, row);
|
|
364
423
|
return next.page === page && next.parms.toString() === parms.toString();
|
|
365
424
|
}
|
|
366
425
|
/**
|
|
367
|
-
* Why a returned row for `lb-url` cannot be used, or nothing
|
|
426
|
+
* Why a returned row for `lb-url` or `lb-choices` cannot be used, or nothing
|
|
427
|
+
* when it can.
|
|
368
428
|
*
|
|
369
429
|
* A value is a string, as a control's is and a URL's is. At least one column
|
|
370
430
|
* is named: a row naming none changes nothing.
|
|
@@ -379,7 +439,7 @@ function unusableUrl(columns) {
|
|
|
379
439
|
if (values.length === 0)
|
|
380
440
|
return "naming no column";
|
|
381
441
|
if (!values.every((v) => typeof v === "string")) {
|
|
382
|
-
return `holding a value that is not a string; write
|
|
442
|
+
return `holding a value that is not a string; write each value as "..."`;
|
|
383
443
|
}
|
|
384
444
|
return undefined;
|
|
385
445
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-server.js","sourceRoot":"","sources":["../../server/lb-server.ts"],"names":[],"mappings":"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","sourcesContent":["// The server-side contract and the engine that runs it.\n//\n// This is the counterpart to core/lb-constants.ts: that file is the\n// vocabulary the browser writes, this one is the vocabulary the application\n// writes. The engine below is the whole of Loadbare on the server. It ships\n// no HTTP server, no router, and no data layer.\n\nimport type {\n HubData,\n HubRequest,\n HubResponse,\n HubResult,\n Kind,\n GatheredRow,\n HubRefusal,\n Patch,\n RequestFields,\n ResponseItem,\n Row,\n} from \"../core/lb-types.js\";\nimport { isRowRequest, nextLocation, parseOrder } from \"../core/lb-types.js\";\nimport {\n KIND_ROW,\n KIND_ROWS,\n LB_RESERVED_PREFIX,\n REQUEST_ROW_DELETE,\n REQUEST_ROW_INSERT,\n REQUEST_ROW_UPDATE,\n URL_COLUMN_PATH,\n URL_QUERY,\n} from \"../core/lb-constants.js\";\n\n/**\n * Whatever the application hands the engine for the duration of one request.\n *\n * Loadbare declares it empty and never reads it. An application fills it in\n * by declaration merging, once, anywhere in its own source:\n *\n * declare module \"@loadbare/app/server\" {\n * interface HubContext {\n * db: Db;\n * }\n * }\n *\n * That is why no type on this page takes a type parameter. The context is a\n * request-scoped handle — an authenticated database connection is the\n * expected case — so it is passed per call rather than held by the engine.\n * The page's query parms are the other expected member: a query that reads\n * one off the context takes no argument, and re-runs under `refresh` like\n * any other.\n */\nexport interface HubContext {}\n\n/**\n * A declared query: its kind, its key, and the run that answers it.\n *\n * Kind and key are properties of the name rather than of any one answer, so\n * they are declared here and never inferred from what comes back. The engine\n * sends both with every answer, so the markup repeats neither. Every query\n * has a key; an aggregate row answers with a constant one.\n *\n * Write one with `row()` or `rows()` below; nothing else builds one.\n */\nexport interface Query {\n readonly kind: Kind;\n readonly key: string;\n readonly run: (ctx: HubContext) => HubResult | Promise<HubResult>;\n /** A `rows` query's order, as `rows()` declared it. */\n readonly order?: string;\n /** Whether a `rows` query reads the order parm and sorts by it itself. */\n readonly serverSortedByUrl?: boolean;\n}\n\n/**\n * What a `rows` query declares about its order.\n *\n * `order` is the order the hub places its rows by: columns separated by\n * commas, each descending when it begins with `-`, as in\n * `category_order,-opened`. The user's order, carried in the query parm\n * `lb-order-<query>`, replaces it. With neither, rows show in the order the\n * query answered them in.\n *\n * `serverSortedByUrl` says the query reads that parm and sorts by it itself,\n * which a query that pages or limits its rows must: a change of the user's\n * order then runs the query again. Without it, the hub sorts the rows it\n * already has, with no round trip.\n */\nexport interface RowsOptions {\n order?: string;\n serverSortedByUrl?: boolean;\n}\n\n/** A query that answers with one row, identified by its `key` column. */\nexport function row(\n key: string,\n run: (ctx: HubContext) => Row | Promise<Row>,\n): Query {\n return { kind: KIND_ROW, key, run };\n}\n\n/**\n * A query that answers with all its rows. Rows land by their `key` column: a\n * row whose key is not in the answer is gone. The hub places them by the\n * order in `options`, or in the order they arrived when there is none.\n */\nexport function rows(\n key: string,\n run: (ctx: HubContext) => Row[] | Promise<Row[]>,\n options: RowsOptions = {},\n): Query {\n return {\n kind: KIND_ROWS,\n key,\n run,\n ...(options.order !== undefined && { order: options.order }),\n ...(options.serverSortedByUrl && { serverSortedByUrl: true }),\n };\n}\n\n/**\n * Only what changed, returned from a handler for a `rows` query. Rows named\n * here are added or updated, keys in `drop` are removed, and everything\n * unnamed is left alone — its contents, and its place in whatever order the\n * page is keeping.\n *\n * `Array.isArray` is what tells a patch from all rows, so a patch needs no\n * marker of its own and no column name is reserved to carry one.\n */\nexport function patch(change: { rows?: Row[]; drop?: unknown[] }): Patch {\n const made = { ...change };\n patches.add(made);\n return made;\n}\n\n/**\n * Every object `patch()` made. A patch and a row are both plain objects, so\n * this is how the engine refuses a patch answering a `row` query.\n */\nconst patches = new WeakSet<object>();\n\n/**\n * A new row for `lb-url`, returned from a handler when only the handler can\n * know where the page belongs: the key of a row it inserted, or the absence\n * of one it deleted.\n *\n * A column other than `lb-path` is a query parm: set, or taken out when its\n * value is empty. With `lb-path` naming another page, the page is entered\n * with only the parms named here; otherwise every other parm is kept.\n *\n * The page then loads at the new URL in the same round trip, as a cold load\n * of it would, so the refresh set is not run and whatever else the handler\n * returned is dropped: both were answers for the URL the page is leaving.\n * This is Post/Redirect/Get without the redirect.\n */\nexport function url(columns: Record<string, string>): HubData {\n return { [URL_QUERY]: { ...columns } };\n}\n\n/**\n * A handler's refusal: thrown, and answered to the browser with the words\n * it carries. Every other error a handler throws reaches the browser as a\n * failure with nothing in it.\n */\nexport class Refusal extends Error {\n readonly refusal: HubRefusal;\n\n constructor(refusal: HubRefusal) {\n super(refusal.message ?? \"refused\");\n this.name = \"Refusal\";\n this.refusal = refusal;\n }\n}\n\n/**\n * Refuse a request, in words written for the user. Throw what it returns:\n *\n * throw refusal(\"That series already has paychecks entered.\");\n * throw refusal({ amount: \"Must be more than zero\" });\n * throw refusal(\"Two fields need attention.\", { amount: \"…\" });\n *\n * A message alone is for the request as a whole; columns name the controls\n * the request gathered, and each message is shown on its control. Thrown\n * rather than returned, so a refusal can come from anywhere the handler\n * calls, and whatever the application has open rolls back as for any\n * throw.\n */\nexport function refusal(\n message: string | Record<string, string>,\n columns?: Record<string, string>,\n): Refusal {\n return typeof message === \"string\"\n ? new Refusal({ message, ...(columns && { columns }) })\n : new Refusal({ columns: message });\n}\n\n/** The shape of a `<stub>.queries.ts` module. */\nexport type Queries = Record<string, Query>;\n\n/**\n * What a handler does, and which queries re-run once it has.\n *\n * `run` may also return answers of its own, which are laid over the\n * refreshed ones. That is how a patch reaches the browser: a query answers\n * for all its rows and cannot know why it re-ran, but the handler knows\n * exactly what it changed and can say only that.\n */\nexport interface Handler<R = RequestFields> {\n run: (\n ctx: HubContext,\n request: R,\n ) => void | HubData | Promise<void | HubData>;\n refresh: string[];\n}\n\n/**\n * `rows` holds the rows of the queries nested in the form the request came\n * from, by query name, each with its own values: a master's details,\n * written with it.\n */\nexport type RowInsertHandler = Handler<{\n key?: string;\n values: Record<string, string>;\n rows?: Record<string, GatheredRow[]>;\n}>;\n/** `values` holds only the columns being set, as an SQL UPDATE sets them. */\nexport type RowUpdateHandler = Handler<{\n key: string;\n values: Record<string, string>;\n rows?: Record<string, GatheredRow[]>;\n}>;\nexport type RowDeleteHandler = Handler<{ key: string }>;\n\n/**\n * What the requests Loadbare provides do to one query, keyed by its name in\n * `Requests.crud`. A query with no entry here permits none of them: the wire\n * cannot reach anything the page has not published.\n *\n * Every key is the request name with the prefix stripped and the rest\n * camel-cased: `lb-row-insert` runs `rowInsert`.\n */\nexport interface Crud {\n rowInsert?: RowInsertHandler;\n rowUpdate?: RowUpdateHandler;\n rowDelete?: RowDeleteHandler;\n}\n\n/**\n * The shape of a `<stub>.requests.ts` module.\n *\n * `onPageEnter` runs when the page loads, before its queries. It declares no\n * refresh set: the page's queries all run afterward, so whatever it changed\n * is already in the response. It may return `url()` instead, to move the\n * page to other query parms or another page before anything shows, as an\n * application restoring what the user last had on screen does. The page\n * then loads there in the same round trip, and `onPageEnter` running for\n * that load is not followed again.\n *\n * `handlers` holds the page's declared requests, by name. A name the page\n * did not declare is refused, so the wire cannot reach anything the page has\n * not published.\n *\n * `crud` holds what the requests Loadbare provides run, by query name.\n */\nexport interface Requests {\n onPageEnter?: (ctx: HubContext) => void | HubData | Promise<void | HubData>;\n handlers?: Record<string, Handler>;\n crud?: Record<string, Crud>;\n}\n\n/** A page is three files sharing a stub; two of them are these. */\nexport interface Page {\n queries: Queries;\n requests: Requests;\n}\n\n/**\n * The page registry. The builder writes it from every `.requests.ts` and\n * `.queries.ts` it discovers — see docs/reference/builder.md.\n */\nexport type Pages = Record<string, Page>;\n\n/**\n * How a page load treats a `url()` its `onPageEnter` returned.\n *\n * With `follow` false it is ignored and the queries run, as they must for a\n * load that is itself at a URL something else moved to. With `parms`, the\n * query parms the page is being loaded at, one that leads to the page and\n * parms already in hand is ignored the same way, so the page loads once.\n */\nexport interface PageLoadOptions {\n follow?: boolean;\n parms?: URLSearchParams;\n}\n\n/**\n * The engine. Both calls answer with response items. A request whose\n * handler returned `url()`, and a page load whose `onPageEnter` did, answer\n * with the `lb-url` item alone: the caller builds a context at the new URL,\n * calls `dataForPage` with it and `follow` false, and sends the load beside the\n * item. `hubRoutes` does exactly that.\n */\nexport interface Hub {\n /** A page load: its `onPageEnter`, then all of its queries. */\n dataForPage(\n page: string,\n ctx: HubContext,\n options?: PageLoadOptions,\n ): Promise<HubResponse>;\n\n /** A request: run the handler its name picks, then the refresh set. */\n runRequest(\n page: string,\n request: HubRequest,\n ctx: HubContext,\n ): Promise<HubResponse>;\n}\n\n/** The camel-cased `Crud` key of a request Loadbare provides. */\nconst CRUD_KEYS: Record<string, keyof Crud> = {\n [REQUEST_ROW_INSERT]: \"rowInsert\",\n [REQUEST_ROW_UPDATE]: \"rowUpdate\",\n [REQUEST_ROW_DELETE]: \"rowDelete\",\n};\n\n/**\n * Build the engine over a set of pages. The pages are fixed at startup; the\n * context is not, and arrives with each call.\n */\nexport function createHub(pages: Pages): Hub {\n // Names beginning with the reserved prefix are Loadbare's — its own query,\n // the requests it provides — so an application cannot declare one.\n // Refused at startup, because a name is a fact about the page and not\n // about any request.\n for (const [page, entry] of Object.entries(pages)) {\n const declared = [\n ...Object.keys(entry.queries),\n ...Object.keys(entry.requests.handlers ?? {}),\n ...Object.keys(entry.requests.crud ?? {}),\n ];\n for (const name of declared) {\n if (name.startsWith(LB_RESERVED_PREFIX)) {\n throw new Error(\n `loadbare: page '${page}' declares '${name}', but names ` +\n `beginning with '${LB_RESERVED_PREFIX}' are reserved`,\n );\n }\n }\n\n // An update or a delete names its row by key, so its handler always\n // knows which rows changed and can answer with a patch, and the hub\n // places what the patch names by the query's order. Refreshing the whole\n // set instead sends every row to say one thing. There is no case where\n // that is the answer to either, so it is refused rather than warned\n // about. An insert is not: its key exists only once the row does, and a\n // handler may leave the query to say so.\n // An order is a fact about the query, so a malformed one is refused at\n // startup, as a reserved name is.\n for (const [name, query] of Object.entries(entry.queries)) {\n if (query.order === undefined) continue;\n try {\n parseOrder(query.order);\n } catch (err) {\n throw new Error(\n `loadbare: page '${page}' query '${name}': ${(err as Error).message}`,\n );\n }\n }\n\n for (const [name, crud] of Object.entries(entry.requests.crud ?? {})) {\n if (entry.queries[name]?.kind !== KIND_ROWS) continue;\n for (const request of [\"rowUpdate\", \"rowDelete\"] as const) {\n if (crud[request]?.refresh.includes(name)) {\n throw new Error(\n `loadbare: page '${page}' refreshes '${name}' after its own ` +\n `${request}; return the changed rows in a patch instead`,\n );\n }\n }\n }\n }\n\n /**\n * Answers into response items, adding the kind and key each query\n * declared. An answer that disagrees with its declaration is a mistake in\n * the application rather than a case to handle, and is left out: a page is\n * better off missing one query than showing the wrong shape for it.\n */\n function items(page: string, data: HubData): HubResponse {\n const out: ResponseItem[] = [];\n for (const [name, result] of Object.entries(data)) {\n const query = pages[page]?.queries[name];\n if (!query) {\n console.warn(`loadbare: page '${page}' declares no query '${name}'`);\n continue;\n }\n const item: ResponseItem = {\n query: name,\n kind: query.kind,\n key: query.key,\n };\n if (query.kind === KIND_ROW) {\n if (Array.isArray(result) || patches.has(result)) {\n console.warn(\n `loadbare: query '${name}' is declared row but answered with ` +\n `${Array.isArray(result) ? \"rows\" : \"a patch\"}`,\n );\n continue;\n }\n item.row = result as Row;\n } else {\n if (Array.isArray(result)) item.rows = result;\n else item.patch = result as Patch;\n if (query.order !== undefined) item.order = query.order;\n if (query.serverSortedByUrl) item.serverSortedByUrl = true;\n }\n out.push(item);\n }\n return out;\n }\n\n /** Run queries by name, each answering with all its rows or its row. */\n async function run(\n page: string,\n names: string[],\n ctx: HubContext,\n ): Promise<HubData> {\n const data: HubData = {};\n for (const name of names) {\n const query = pages[page]?.queries[name];\n if (!query) {\n console.warn(`loadbare: page '${page}' declares no query '${name}'`);\n continue;\n }\n const result = await query.run(ctx);\n if (Array.isArray(result) !== (query.kind === KIND_ROWS)) {\n console.warn(\n `loadbare: query '${name}' is declared ${query.kind} but answered ` +\n `with ${Array.isArray(result) ? \"rows\" : \"one row\"}`,\n );\n continue;\n }\n data[name] = result;\n }\n return data;\n }\n\n /**\n * Run a handler, then its refresh set against the same context, laying\n * what the handler itself returned over the refreshed queries — the\n * narrower answer wins because it knows what actually changed.\n *\n * A handler that returned `url()` answers with the `lb-url` item alone.\n * Loading the page there needs a context built at the new URL, and\n * building one is the caller's.\n */\n async function settle(\n page: string,\n handler: Handler<never> | undefined,\n request: RequestFields,\n ctx: HubContext,\n notFound: string,\n ): Promise<HubResponse> {\n if (!handler) {\n console.warn(notFound);\n return [];\n }\n const { [URL_QUERY]: moved, ...stated } =\n (await handler.run(ctx, request as never)) ?? {};\n const item = urlItem(page, moved);\n if (item) return [item];\n return items(page, {\n ...(await run(page, handler.refresh, ctx)),\n ...stated,\n });\n }\n\n /**\n * An update or a delete for several rows: the page's `crud` entry runs once\n * per key, as it would for each row alone, so an application writes\n * nothing more to allow it. What each run returns is laid together, a\n * patch onto a patch, and the refresh set runs once.\n *\n * A run that returns `url()` is reported and its row ignored: a URL names\n * one view, and several rows cannot each choose it.\n */\n async function eachKey(\n page: string,\n name: string,\n fields: RequestFields,\n ctx: HubContext,\n ): Promise<HubResponse> {\n const { keys, ...one } = fields;\n const query = fields.query ?? \"\";\n const handler = pages[page]?.requests.crud?.[query]?.[CRUD_KEYS[name]!] as\n | Handler<never>\n | undefined;\n if (!handler) {\n console.warn(\n `loadbare: page '${page}' declares no ${CRUD_KEYS[name]} for '${query}'`,\n );\n return [];\n }\n if (!Array.isArray(keys) || keys.some((key) => typeof key !== \"string\")) {\n console.warn(`loadbare: '${name}' carries keys that are not strings`);\n return [];\n }\n let stated: HubData = {};\n for (const key of keys) {\n const { [URL_QUERY]: moved, ...answer } =\n (await handler.run(ctx, { ...one, key } as never)) ?? {};\n if (moved !== undefined) {\n console.warn(\n `loadbare: ${CRUD_KEYS[name]} for '${query}' returned url() for ` +\n `one of several rows, ignoring it`,\n );\n }\n stated = layered(stated, answer);\n }\n return items(page, {\n ...(await run(page, handler.refresh, ctx)),\n ...stated,\n });\n }\n\n return {\n async dataForPage(page, ctx, { follow = true, parms } = {}) {\n const entry = pages[page];\n if (!entry) {\n console.warn(`loadbare: no page '${page}'`);\n return [];\n }\n const { [URL_QUERY]: moved, ...stated } =\n (await entry.requests.onPageEnter?.(ctx)) ?? {};\n if (Object.keys(stated).length > 0) {\n console.warn(\n `loadbare: page '${page}' onPageEnter returned answers; it returns ` +\n `url() or nothing, ignoring them`,\n );\n }\n const item = follow ? urlItem(page, moved) : undefined;\n if (item && !(parms && leadsHere(page, parms, item.row!))) return [item];\n return items(page, await run(page, Object.keys(entry.queries), ctx));\n },\n\n runRequest(page, request, ctx) {\n const { name, ...fields } = request;\n if (fields.rows !== undefined && !wellFormedRows(fields.rows)) {\n console.warn(\n `loadbare: '${name}' carries rows that are not keys and string ` +\n `values by query, ignoring it`,\n );\n return Promise.resolve([]);\n }\n if (\n fields.keys !== undefined &&\n (name === REQUEST_ROW_UPDATE || name === REQUEST_ROW_DELETE)\n ) {\n return eachKey(page, name, fields, ctx);\n }\n if (isRowRequest(name)) {\n const query = fields.query ?? \"\";\n return settle(\n page,\n pages[page]?.requests.crud?.[query]?.[CRUD_KEYS[name]!] as\n Handler<never> | undefined,\n fields,\n ctx,\n `loadbare: page '${page}' declares no ${CRUD_KEYS[name]} for '${query}'`,\n );\n }\n return settle(\n page,\n pages[page]?.requests.handlers?.[name] as Handler<never> | undefined,\n fields,\n ctx,\n `loadbare: page '${page}' declares no request '${name}'`,\n );\n },\n };\n}\n\n/** Gathered rows as the hub sends them: string keys and string values. */\nfunction wellFormedRows(rows: unknown): boolean {\n if (typeof rows !== \"object\" || rows === null || Array.isArray(rows)) {\n return false;\n }\n return Object.values(rows).every(\n (set) =>\n Array.isArray(set) &&\n set.every(\n (row) =>\n typeof row === \"object\" &&\n row !== null &&\n typeof row.key === \"string\" &&\n typeof row.values === \"object\" &&\n row.values !== null &&\n Object.values(row.values).every((v) => typeof v === \"string\"),\n ),\n );\n}\n\n/**\n * Two answers as one: a patch laid onto a patch for the same query keeps\n * what both name, and any other answer replaces the one before it.\n */\nfunction layered(before: HubData, after: HubData): HubData {\n const out: HubData = { ...before };\n for (const [name, answer] of Object.entries(after)) {\n const was = out[name];\n if (was && patches.has(was) && patches.has(answer)) {\n const a = was as Patch;\n const b = answer as Patch;\n out[name] = patch({\n rows: [...(a.rows ?? []), ...(b.rows ?? [])],\n drop: [...(a.drop ?? []), ...(b.drop ?? [])],\n });\n } else {\n out[name] = answer;\n }\n }\n return out;\n}\n\n/**\n * The `lb-url` item for a row a handler or `onPageEnter` returned, or\n * nothing when it returned none or one that cannot be used. Whatever\n * returned it has already run, so a row that cannot be used is reported and\n * the call goes on as if none had been returned.\n */\nfunction urlItem(page: string, moved: unknown): ResponseItem | undefined {\n if (moved === undefined) return undefined;\n const refused = unusableUrl(moved);\n if (refused === undefined) {\n return {\n query: URL_QUERY,\n kind: KIND_ROW,\n key: URL_COLUMN_PATH,\n row: moved as Row,\n };\n }\n console.warn(\n `loadbare: page '${page}' returned ${URL_QUERY} ${refused}, ignoring it`,\n );\n return undefined;\n}\n\n/** Whether a row for `lb-url` leads to the page and parms already in hand. */\nfunction leadsHere(page: string, parms: URLSearchParams, row: Row): boolean {\n const next = nextLocation(page, parms, row);\n return next.page === page && next.parms.toString() === parms.toString();\n}\n\n/**\n * Why a returned row for `lb-url` cannot be used, or nothing when it can.\n *\n * A value is a string, as a control's is and a URL's is. At least one column\n * is named: a row naming none changes nothing.\n */\nfunction unusableUrl(columns: unknown): string | undefined {\n if (\n typeof columns !== \"object\" ||\n columns === null ||\n Array.isArray(columns)\n ) {\n return \"that is not a row\";\n }\n const values = Object.values(columns);\n if (values.length === 0) return \"naming no column\";\n if (!values.every((v) => typeof v === \"string\")) {\n return `holding a value that is not a string; write url({ name: \"...\" })`;\n }\n return undefined;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"lb-server.js","sourceRoot":"","sources":["../../server/lb-server.ts"],"names":[],"mappings":"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","sourcesContent":["// The server-side contract and the engine that runs it.\n//\n// This is the counterpart to core/lb-constants.ts: that file is the\n// vocabulary the browser writes, this one is the vocabulary the application\n// writes. The engine below is the whole of Loadbare on the server. It ships\n// no HTTP server, no router, and no data layer.\n\nimport type {\n ColumnTypes,\n HubData,\n HubRequest,\n HubResponse,\n HubResult,\n Kind,\n GatheredRow,\n HubRefusal,\n Patch,\n RequestFields,\n ResponseItem,\n Row,\n} from \"../core/lb-types.js\";\nimport { isRowRequest, nextLocation, parseOrder } from \"../core/lb-types.js\";\nimport {\n CHOICES_QUERY,\n COLUMN_TYPES,\n KIND_ROW,\n KIND_ROWS,\n LB_RESERVED_PREFIX,\n REQUEST_ROW_DELETE,\n REQUEST_ROW_INSERT,\n REQUEST_ROW_UPDATE,\n URL_COLUMN_PATH,\n URL_QUERY,\n} from \"../core/lb-constants.js\";\n\n/**\n * Whatever the application hands the engine for the duration of one request.\n *\n * Loadbare declares it empty and never reads it. An application fills it in\n * by declaration merging, once, anywhere in its own source:\n *\n * declare module \"@loadbare/app/server\" {\n * interface HubContext {\n * db: Db;\n * }\n * }\n *\n * That is why no type on this page takes a type parameter. The context is a\n * request-scoped handle — an authenticated database connection is the\n * expected case — so it is passed per call rather than held by the engine.\n * The page's query parms are the other expected member: a query that reads\n * one off the context takes no argument, and re-runs under `refresh` like\n * any other.\n */\nexport interface HubContext {}\n\n/**\n * A declared query: its kind, its key, and the run that answers it.\n *\n * Kind and key are properties of the name rather than of any one answer, so\n * they are declared here and never inferred from what comes back. The engine\n * sends both with every answer, so the markup repeats neither. Every query\n * has a key; an aggregate row answers with a constant one.\n *\n * Write one with `row()` or `rows()` below; nothing else builds one.\n */\nexport interface Query {\n readonly kind: Kind;\n readonly key: string;\n readonly run: (ctx: HubContext) => HubResult | Promise<HubResult>;\n /** A `rows` query's order, as `rows()` declared it. */\n readonly order?: string;\n /** A `rows` query's column types, as `rows()` declared them. */\n readonly types?: ColumnTypes;\n}\n\n/**\n * What a `rows` query declares about its order.\n *\n * `order` is the order the hub places its rows by: columns separated by\n * commas, each descending when it begins with `-`, as in\n * `category_order,-opened`. An order the user chose, held in `lb-choices`,\n * replaces it. With neither, rows show in the order the query answered them\n * in.\n *\n * `types` names a type for any of its columns, and the hub sorts, filters\n * and takes `lb-min` and `lb-max` of that column read as its type rather\n * than by the JSON type its values arrive as: `{ amount: \"number\" }` for a\n * Postgres `numeric` that arrives as a string. A column it does not name\n * compares by its JSON type. The order and the types go to the browser\n * once, with the answer that loads the page.\n */\nexport interface RowsOptions {\n order?: string;\n types?: ColumnTypes;\n}\n\n/** A query that answers with one row, identified by its `key` column. */\nexport function row(\n key: string,\n run: (ctx: HubContext) => Row | Promise<Row>,\n): Query {\n return { kind: KIND_ROW, key, run };\n}\n\n/**\n * A query that answers with all its rows. Rows land by their `key` column: a\n * row whose key is not in the answer is gone. The hub places them by the\n * order in `options`, or in the order they arrived when there is none.\n */\nexport function rows(\n key: string,\n run: (ctx: HubContext) => Row[] | Promise<Row[]>,\n options: RowsOptions = {},\n): Query {\n return {\n kind: KIND_ROWS,\n key,\n run,\n ...(options.order !== undefined && { order: options.order }),\n ...(options.types !== undefined && { types: options.types }),\n };\n}\n\n/**\n * Only what changed, returned from a handler for a `rows` query. Rows named\n * here are added or updated, keys in `drop` are removed, and everything\n * unnamed is left alone — its contents, and its place in whatever order the\n * page is keeping.\n *\n * `Array.isArray` is what tells a patch from all rows, so a patch needs no\n * marker of its own and no column name is reserved to carry one.\n */\nexport function patch(change: { rows?: Row[]; drop?: unknown[] }): Patch {\n const made = { ...change };\n patches.add(made);\n return made;\n}\n\n/**\n * Every object `patch()` made. A patch and a row are both plain objects, so\n * this is how the engine refuses a patch answering a `row` query.\n */\nconst patches = new WeakSet<object>();\n\n/**\n * A new row for `lb-url`, returned from a handler when only the handler can\n * know where the page belongs: the key of a row it inserted, or the absence\n * of one it deleted.\n *\n * A column other than `lb-path` is a query parm: set, or taken out when its\n * value is empty. With `lb-path` naming another page, the page is entered\n * with only the parms named here; otherwise every other parm is kept.\n *\n * The page then loads at the new URL in the same round trip, as a cold load\n * of it would, so the refresh set is not run and whatever else the handler\n * returned is dropped: both were answers for the URL the page is leaving.\n * This is Post/Redirect/Get without the redirect.\n */\nexport function url(columns: Record<string, string>): HubData {\n return { [URL_QUERY]: { ...columns } };\n}\n\n/**\n * The user's choices for the page, returned from `onPageEnter` to restore\n * what the user last chose: an order, a filter. Each column is a column of\n * `lb-choices`, and the hub places the page's rows by them as they land.\n * Return it beside `url()` with the spread both are written for:\n *\n * return { ...url({ acct: \"23\" }), ...choices({ sort: \"-amount\" }) };\n */\nexport function choices(columns: Record<string, string>): HubData {\n return { [CHOICES_QUERY]: { ...columns } };\n}\n\n/**\n * A handler's refusal: thrown, and answered to the browser with the words\n * it carries. Every other error a handler throws reaches the browser as a\n * failure with nothing in it.\n */\nexport class Refusal extends Error {\n readonly refusal: HubRefusal;\n\n constructor(refusal: HubRefusal) {\n super(refusal.message ?? \"refused\");\n this.name = \"Refusal\";\n this.refusal = refusal;\n }\n}\n\n/**\n * Refuse a request, in words written for the user. Throw what it returns:\n *\n * throw refusal(\"That series already has paychecks entered.\");\n * throw refusal({ amount: \"Must be more than zero\" });\n * throw refusal(\"Two fields need attention.\", { amount: \"…\" });\n *\n * A message alone is for the request as a whole; columns name the controls\n * the request gathered, and each message is shown on its control. Thrown\n * rather than returned, so a refusal can come from anywhere the handler\n * calls, and whatever the application has open rolls back as for any\n * throw.\n */\nexport function refusal(\n message: string | Record<string, string>,\n columns?: Record<string, string>,\n): Refusal {\n return typeof message === \"string\"\n ? new Refusal({ message, ...(columns && { columns }) })\n : new Refusal({ columns: message });\n}\n\n/** The shape of a `<stub>.queries.ts` module. */\nexport type Queries = Record<string, Query>;\n\n/**\n * What a handler does, and which queries re-run once it has.\n *\n * `run` may also return answers of its own, which are laid over the\n * refreshed ones. That is how a patch reaches the browser: a query answers\n * for all its rows and cannot know why it re-ran, but the handler knows\n * exactly what it changed and can say only that.\n */\nexport interface Handler<R = RequestFields> {\n run: (\n ctx: HubContext,\n request: R,\n ) => void | HubData | Promise<void | HubData>;\n refresh: string[];\n}\n\n/**\n * `rows` holds the rows of the queries nested in the form the request came\n * from, by query name, each with its own values: a master's details,\n * written with it.\n */\nexport type RowInsertHandler = Handler<{\n key?: string;\n values: Record<string, string>;\n rows?: Record<string, GatheredRow[]>;\n}>;\n/** `values` holds only the columns being set, as an SQL UPDATE sets them. */\nexport type RowUpdateHandler = Handler<{\n key: string;\n values: Record<string, string>;\n rows?: Record<string, GatheredRow[]>;\n}>;\nexport type RowDeleteHandler = Handler<{ key: string }>;\n\n/**\n * What the requests Loadbare provides do to one query, keyed by its name in\n * `Requests.crud`. A query with no entry here permits none of them: the wire\n * cannot reach anything the page has not published.\n *\n * Every key is the request name with the prefix stripped and the rest\n * camel-cased: `lb-row-insert` runs `rowInsert`.\n */\nexport interface Crud {\n rowInsert?: RowInsertHandler;\n rowUpdate?: RowUpdateHandler;\n rowDelete?: RowDeleteHandler;\n}\n\n/**\n * The shape of a `<stub>.requests.ts` module.\n *\n * `onPageEnter` runs when the page loads, before its queries. It declares no\n * refresh set: the page's queries all run afterward, so whatever it changed\n * is already in the response. It may return `url()` instead, to move the\n * page to other query parms or another page before anything shows, as an\n * application restoring what the user last had on screen does. The page\n * then loads there in the same round trip, and `onPageEnter` running for\n * that load is not followed again. It may return `choices()`, with `url()`\n * or alone, to restore the user's choices for the page.\n *\n * `onChoices` runs each time the user makes a choice, with every choice\n * made on the page, so an application can keep them for `onPageEnter` to\n * restore. It answers with nothing, by convention.\n *\n * `handlers` holds the page's declared requests, by name. A name the page\n * did not declare is refused, so the wire cannot reach anything the page has\n * not published.\n *\n * `crud` holds what the requests Loadbare provides run, by query name.\n */\nexport interface Requests {\n onPageEnter?: (ctx: HubContext) => void | HubData | Promise<void | HubData>;\n onChoices?: (\n ctx: HubContext,\n choices: Record<string, string>,\n ) => void | HubData | Promise<void | HubData>;\n handlers?: Record<string, Handler>;\n crud?: Record<string, Crud>;\n}\n\n/** A page is three files sharing a stub; two of them are these. */\nexport interface Page {\n queries: Queries;\n requests: Requests;\n}\n\n/**\n * The page registry. The builder writes it from every `.requests.ts` and\n * `.queries.ts` it discovers — see docs/reference/builder.md.\n */\nexport type Pages = Record<string, Page>;\n\n/**\n * How a page load treats a `url()` its `onPageEnter` returned.\n *\n * With `follow` false it is ignored and the queries run, as they must for a\n * load that is itself at a URL something else moved to. With `parms`, the\n * query parms the page is being loaded at, one that leads to the page and\n * parms already in hand is ignored the same way, so the page loads once.\n */\nexport interface PageLoadOptions {\n follow?: boolean;\n parms?: URLSearchParams;\n}\n\n/**\n * The engine. Both calls answer with response items. A request whose\n * handler returned `url()`, and a page load whose `onPageEnter` did, answer\n * with the `lb-url` item alone: the caller builds a context at the new URL,\n * calls `dataForPage` with it and `follow` false, and sends the load beside the\n * item. `hubRoutes` does exactly that.\n */\nexport interface Hub {\n /** A page load: its `onPageEnter`, then all of its queries. */\n dataForPage(\n page: string,\n ctx: HubContext,\n options?: PageLoadOptions,\n ): Promise<HubResponse>;\n\n /** A request: run the handler its name picks, then the refresh set. */\n runRequest(\n page: string,\n request: HubRequest,\n ctx: HubContext,\n ): Promise<HubResponse>;\n}\n\n/** The camel-cased `Crud` key of a request Loadbare provides. */\nconst CRUD_KEYS: Record<string, keyof Crud> = {\n [REQUEST_ROW_INSERT]: \"rowInsert\",\n [REQUEST_ROW_UPDATE]: \"rowUpdate\",\n [REQUEST_ROW_DELETE]: \"rowDelete\",\n};\n\n/**\n * Build the engine over a set of pages. The pages are fixed at startup; the\n * context is not, and arrives with each call.\n */\nexport function createHub(pages: Pages): Hub {\n // Names beginning with the reserved prefix are Loadbare's — its own query,\n // the requests it provides — so an application cannot declare one.\n // Refused at startup, because a name is a fact about the page and not\n // about any request.\n for (const [page, entry] of Object.entries(pages)) {\n const declared = [\n ...Object.keys(entry.queries),\n ...Object.keys(entry.requests.handlers ?? {}),\n ...Object.keys(entry.requests.crud ?? {}),\n ];\n for (const name of declared) {\n if (name.startsWith(LB_RESERVED_PREFIX)) {\n throw new Error(\n `loadbare: page '${page}' declares '${name}', but names ` +\n `beginning with '${LB_RESERVED_PREFIX}' are reserved`,\n );\n }\n }\n\n // A crud handler always knows which rows it changed: an update or a\n // delete names its row by key, and an insert has the new row's key once\n // it has written it. So each can answer with a patch, and the hub places\n // what the patch names by the query's order. Refreshing the whole set\n // instead sends every row to say one thing. There is no case where that\n // is the answer, so it is refused rather than warned about.\n // An order is a fact about the query, so a malformed one is refused at\n // startup, as a reserved name is.\n // So is a column type the hub does not know.\n for (const [name, query] of Object.entries(entry.queries)) {\n for (const [column, type] of Object.entries(query.types ?? {})) {\n if (!(COLUMN_TYPES as readonly string[]).includes(type)) {\n throw new Error(\n `loadbare: page '${page}' query '${name}' declares column ` +\n `'${column}' as '${type}'; a column type is one of ` +\n `${COLUMN_TYPES.join(\", \")}`,\n );\n }\n }\n if (query.order === undefined) continue;\n try {\n parseOrder(query.order);\n } catch (err) {\n throw new Error(\n `loadbare: page '${page}' query '${name}': ${(err as Error).message}`,\n );\n }\n }\n\n for (const [name, crud] of Object.entries(entry.requests.crud ?? {})) {\n if (entry.queries[name]?.kind !== KIND_ROWS) continue;\n for (const request of [\"rowInsert\", \"rowUpdate\", \"rowDelete\"] as const) {\n if (crud[request]?.refresh.includes(name)) {\n throw new Error(\n `loadbare: page '${page}' refreshes '${name}' after its own ` +\n `${request}; return the changed rows in a patch instead`,\n );\n }\n }\n }\n }\n\n /**\n * Answers into response items, adding the kind and key each query\n * declared. An answer that disagrees with its declaration is a mistake in\n * the application rather than a case to handle, and is left out: a page is\n * better off missing one query than showing the wrong shape for it.\n *\n * A page load also adds each `rows` query's order and column types, which\n * the hub keeps for the answers after it.\n */\n function items(page: string, data: HubData, load = false): HubResponse {\n const out: ResponseItem[] = [];\n for (const [name, result] of Object.entries(data)) {\n const query = pages[page]?.queries[name];\n if (!query) {\n console.warn(`loadbare: page '${page}' declares no query '${name}'`);\n continue;\n }\n const item: ResponseItem = {\n query: name,\n kind: query.kind,\n key: query.key,\n };\n if (query.kind === KIND_ROW) {\n if (Array.isArray(result) || patches.has(result)) {\n console.warn(\n `loadbare: query '${name}' is declared row but answered with ` +\n `${Array.isArray(result) ? \"rows\" : \"a patch\"}`,\n );\n continue;\n }\n item.row = result as Row;\n } else {\n if (Array.isArray(result)) item.rows = result;\n else item.patch = result as Patch;\n if (load && query.order !== undefined) item.order = query.order;\n if (load && query.types !== undefined) item.types = query.types;\n }\n out.push(item);\n }\n return out;\n }\n\n /** Run queries by name, each answering with all its rows or its row. */\n async function run(\n page: string,\n names: string[],\n ctx: HubContext,\n ): Promise<HubData> {\n const data: HubData = {};\n for (const name of names) {\n const query = pages[page]?.queries[name];\n if (!query) {\n console.warn(`loadbare: page '${page}' declares no query '${name}'`);\n continue;\n }\n const result = await query.run(ctx);\n if (Array.isArray(result) !== (query.kind === KIND_ROWS)) {\n console.warn(\n `loadbare: query '${name}' is declared ${query.kind} but answered ` +\n `with ${Array.isArray(result) ? \"rows\" : \"one row\"}`,\n );\n continue;\n }\n data[name] = result;\n }\n return data;\n }\n\n /**\n * Run a handler, then its refresh set against the same context, laying\n * what the handler itself returned over the refreshed queries — the\n * narrower answer wins because it knows what actually changed.\n *\n * A handler that returned `url()` answers with the `lb-url` item alone.\n * Loading the page there needs a context built at the new URL, and\n * building one is the caller's.\n */\n async function settle(\n page: string,\n handler: Handler<never> | undefined,\n request: RequestFields,\n ctx: HubContext,\n notFound: string,\n ): Promise<HubResponse> {\n if (!handler) {\n console.warn(notFound);\n return [];\n }\n const { [URL_QUERY]: moved, ...stated } =\n (await handler.run(ctx, request as never)) ?? {};\n const item = urlItem(page, moved);\n if (item) return [item];\n return items(page, {\n ...(await run(page, handler.refresh, ctx)),\n ...stated,\n });\n }\n\n /**\n * An update or a delete for several rows: the page's `crud` entry runs once\n * per key, as it would for each row alone, so an application writes\n * nothing more to allow it. What each run returns is laid together, a\n * patch onto a patch, and the refresh set runs once.\n *\n * A run that returns `url()` is reported and its row ignored: a URL names\n * one view, and several rows cannot each choose it.\n */\n async function eachKey(\n page: string,\n name: string,\n fields: RequestFields,\n ctx: HubContext,\n ): Promise<HubResponse> {\n const { keys, ...one } = fields;\n const query = fields.query ?? \"\";\n const handler = pages[page]?.requests.crud?.[query]?.[CRUD_KEYS[name]!] as\n | Handler<never>\n | undefined;\n if (!handler) {\n console.warn(\n `loadbare: page '${page}' declares no ${CRUD_KEYS[name]} for '${query}'`,\n );\n return [];\n }\n if (!Array.isArray(keys) || keys.some((key) => typeof key !== \"string\")) {\n console.warn(`loadbare: '${name}' carries keys that are not strings`);\n return [];\n }\n let stated: HubData = {};\n for (const key of keys) {\n const { [URL_QUERY]: moved, ...answer } =\n (await handler.run(ctx, { ...one, key } as never)) ?? {};\n if (moved !== undefined) {\n console.warn(\n `loadbare: ${CRUD_KEYS[name]} for '${query}' returned url() for ` +\n `one of several rows, ignoring it`,\n );\n }\n stated = layered(stated, answer);\n }\n return items(page, {\n ...(await run(page, handler.refresh, ctx)),\n ...stated,\n });\n }\n\n return {\n async dataForPage(page, ctx, { follow = true, parms } = {}) {\n const entry = pages[page];\n if (!entry) {\n console.warn(`loadbare: no page '${page}'`);\n return [];\n }\n const {\n [URL_QUERY]: moved,\n [CHOICES_QUERY]: chose,\n ...stated\n } = (await entry.requests.onPageEnter?.(ctx)) ?? {};\n if (Object.keys(stated).length > 0) {\n console.warn(\n `loadbare: page '${page}' onPageEnter returned answers; it returns ` +\n `url(), choices() or nothing, ignoring them`,\n );\n }\n const item = follow ? urlItem(page, moved) : undefined;\n if (item && !(parms && leadsHere(page, parms, item.row!))) return [item];\n // The choices go first, so the rows that read them land by them.\n return [\n ...choicesItem(page, chose),\n ...items(page, await run(page, Object.keys(entry.queries), ctx), true),\n ];\n },\n\n runRequest(page, request, ctx) {\n const { name, ...fields } = request;\n if (fields.rows !== undefined && !wellFormedRows(fields.rows)) {\n console.warn(\n `loadbare: '${name}' carries rows that are not keys and string ` +\n `values by query, ignoring it`,\n );\n return Promise.resolve([]);\n }\n // The user's choices, told to the page. One with no `onChoices` is\n // not keeping them, and is not told.\n if (fields.query === CHOICES_QUERY) {\n const onChoices = pages[page]?.requests.onChoices;\n if (name !== REQUEST_ROW_UPDATE || !onChoices) {\n return Promise.resolve([]);\n }\n return settle(\n page,\n {\n run: (c: HubContext, r: RequestFields) =>\n onChoices(c, r.values ?? {}),\n refresh: [],\n } as Handler<never>,\n fields,\n ctx,\n \"\",\n );\n }\n if (\n fields.keys !== undefined &&\n (name === REQUEST_ROW_UPDATE || name === REQUEST_ROW_DELETE)\n ) {\n return eachKey(page, name, fields, ctx);\n }\n if (isRowRequest(name)) {\n const query = fields.query ?? \"\";\n return settle(\n page,\n pages[page]?.requests.crud?.[query]?.[CRUD_KEYS[name]!] as\n Handler<never> | undefined,\n fields,\n ctx,\n `loadbare: page '${page}' declares no ${CRUD_KEYS[name]} for '${query}'`,\n );\n }\n return settle(\n page,\n pages[page]?.requests.handlers?.[name] as Handler<never> | undefined,\n fields,\n ctx,\n `loadbare: page '${page}' declares no request '${name}'`,\n );\n },\n };\n}\n\n/** Gathered rows as the hub sends them: string keys and string values. */\nfunction wellFormedRows(rows: unknown): boolean {\n if (typeof rows !== \"object\" || rows === null || Array.isArray(rows)) {\n return false;\n }\n return Object.values(rows).every(\n (set) =>\n Array.isArray(set) &&\n set.every(\n (row) =>\n typeof row === \"object\" &&\n row !== null &&\n typeof row.key === \"string\" &&\n typeof row.values === \"object\" &&\n row.values !== null &&\n Object.values(row.values).every((v) => typeof v === \"string\"),\n ),\n );\n}\n\n/**\n * Two answers as one: a patch laid onto a patch for the same query keeps\n * what both name, and any other answer replaces the one before it.\n */\nfunction layered(before: HubData, after: HubData): HubData {\n const out: HubData = { ...before };\n for (const [name, answer] of Object.entries(after)) {\n const was = out[name];\n if (was && patches.has(was) && patches.has(answer)) {\n const a = was as Patch;\n const b = answer as Patch;\n out[name] = patch({\n rows: [...(a.rows ?? []), ...(b.rows ?? [])],\n drop: [...(a.drop ?? []), ...(b.drop ?? [])],\n });\n } else {\n out[name] = answer;\n }\n }\n return out;\n}\n\n/**\n * The `lb-url` item for a row a handler or `onPageEnter` returned, or\n * nothing when it returned none or one that cannot be used. Whatever\n * returned it has already run, so a row that cannot be used is reported and\n * the call goes on as if none had been returned.\n */\nfunction urlItem(page: string, moved: unknown): ResponseItem | undefined {\n if (moved === undefined) return undefined;\n const refused = unusableUrl(moved);\n if (refused === undefined) {\n return {\n query: URL_QUERY,\n kind: KIND_ROW,\n key: URL_COLUMN_PATH,\n row: moved as Row,\n };\n }\n console.warn(\n `loadbare: page '${page}' returned ${URL_QUERY} ${refused}, ignoring it`,\n );\n return undefined;\n}\n\n/**\n * The `lb-choices` item for the choices `onPageEnter` returned, or nothing\n * when it returned none or ones that cannot be used, which are reported.\n */\nfunction choicesItem(page: string, chose: unknown): ResponseItem[] {\n if (chose === undefined) return [];\n const refused = unusableUrl(chose);\n if (refused !== undefined) {\n console.warn(\n `loadbare: page '${page}' returned ${CHOICES_QUERY} ${refused}, ` +\n `ignoring it`,\n );\n return [];\n }\n return [\n {\n query: CHOICES_QUERY,\n kind: KIND_ROW,\n key: URL_COLUMN_PATH,\n row: chose as Row,\n },\n ];\n}\n\n/** Whether a row for `lb-url` leads to the page and parms already in hand. */\nfunction leadsHere(page: string, parms: URLSearchParams, row: Row): boolean {\n const next = nextLocation(page, parms, row);\n return next.page === page && next.parms.toString() === parms.toString();\n}\n\n/**\n * Why a returned row for `lb-url` or `lb-choices` cannot be used, or nothing\n * when it can.\n *\n * A value is a string, as a control's is and a URL's is. At least one column\n * is named: a row naming none changes nothing.\n */\nfunction unusableUrl(columns: unknown): string | undefined {\n if (\n typeof columns !== \"object\" ||\n columns === null ||\n Array.isArray(columns)\n ) {\n return \"that is not a row\";\n }\n const values = Object.values(columns);\n if (values.length === 0) return \"naming no column\";\n if (!values.every((v) => typeof v === \"string\")) {\n return `holding a value that is not a string; write each value as \"...\"`;\n }\n return undefined;\n}\n"]}
|