@loadbare/app 0.23.0 → 0.25.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.
Files changed (39) hide show
  1. package/dist/build/assemble.d.ts.map +1 -1
  2. package/dist/build/assemble.js +78 -1
  3. package/dist/build/assemble.js.map +1 -1
  4. package/dist/core/lb-constants.d.ts +8 -5
  5. package/dist/core/lb-constants.d.ts.map +1 -1
  6. package/dist/core/lb-constants.js +20 -11
  7. package/dist/core/lb-constants.js.map +1 -1
  8. package/dist/core/lb-types.d.ts +19 -3
  9. package/dist/core/lb-types.d.ts.map +1 -1
  10. package/dist/core/lb-types.js +35 -5
  11. package/dist/core/lb-types.js.map +1 -1
  12. package/dist/hub/lb-apply.d.ts +31 -22
  13. package/dist/hub/lb-apply.d.ts.map +1 -1
  14. package/dist/hub/lb-apply.js +186 -52
  15. package/dist/hub/lb-apply.js.map +1 -1
  16. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  17. package/dist/hub/lb-hub.browser.js +59 -28
  18. package/dist/hub/lb-hub.browser.js.map +1 -1
  19. package/dist/server/lb-server.d.ts +2 -2
  20. package/dist/server/lb-server.d.ts.map +1 -1
  21. package/dist/server/lb-server.js +29 -9
  22. package/dist/server/lb-server.js.map +1 -1
  23. package/docs/comparison.md +13 -11
  24. package/docs/miswarts.md +4 -3
  25. package/docs/reference/chrome.md +42 -0
  26. package/docs/reference/custom-elements.md +9 -4
  27. package/docs/reference/data-binding.md +143 -67
  28. package/docs/reference/names.md +11 -8
  29. package/docs/reference/page-files.md +11 -6
  30. package/docs/testing.md +3 -3
  31. package/package.json +1 -1
  32. package/skills/loadbare-app/SKILL.md +38 -15
  33. package/skills/loadbare-app/references/chrome.md +42 -0
  34. package/skills/loadbare-app/references/custom-elements.md +9 -4
  35. package/skills/loadbare-app/references/data-binding.md +143 -67
  36. package/skills/loadbare-app/references/miswarts.md +4 -3
  37. package/skills/loadbare-app/references/names.md +11 -8
  38. package/skills/loadbare-app/references/page-files.md +11 -6
  39. package/docs/temp-proposals.md +0 -251
@@ -89,7 +89,7 @@ export declare function patch(change: {
89
89
  * of one it deleted.
90
90
  *
91
91
  * A column other than `lb-path` is a query parm: set, or taken out when its
92
- * value is empty. With `lb-path` naming another page, the page is entered
92
+ * value is empty. An array of strings repeats the parm once per value. With `lb-path` naming another page, the page is entered
93
93
  * with only the parms named here; otherwise every other parm is kept.
94
94
  *
95
95
  * The page then loads at the new URL in the same round trip, as a cold load
@@ -97,7 +97,7 @@ export declare function patch(change: {
97
97
  * returned is dropped: both were answers for the URL the page is leaving.
98
98
  * This is Post/Redirect/Get without the redirect.
99
99
  */
100
- export declare function url(columns: Record<string, string>): HubData;
100
+ export declare function url(columns: Record<string, string | string[]>): HubData;
101
101
  /**
102
102
  * The user's choices for the page, returned from `onPageEnter` to restore
103
103
  * what the user last chose: an order, a filter. Each column is a column of
@@ -1 +1 @@
1
- {"version":3,"file":"lb-server.d.ts","sourceRoot":"","sources":["../../server/lb-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lb-server.d.ts","sourceRoot":"","sources":["../../server/lb-server.ts"],"names":[],"mappings":"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"}
@@ -52,7 +52,7 @@ const patches = new WeakSet();
52
52
  * of one it deleted.
53
53
  *
54
54
  * A column other than `lb-path` is a query parm: set, or taken out when its
55
- * value is empty. With `lb-path` naming another page, the page is entered
55
+ * value is empty. An array of strings repeats the parm once per value. With `lb-path` naming another page, the page is entered
56
56
  * with only the parms named here; otherwise every other parm is kept.
57
57
  *
58
58
  * The page then loads at the new URL in the same round trip, as a cold load
@@ -318,7 +318,15 @@ export function createHub(pages) {
318
318
  ];
319
319
  },
320
320
  runRequest(page, request, ctx) {
321
- const { name, ...fields } = request;
321
+ const { name, ...sent } = request;
322
+ // Only a column of lb-url holds several values, and the hub answers
323
+ // those itself: a column the server receives holds one.
324
+ if (sent.values !== undefined && !stringValues(sent.values)) {
325
+ console.warn(`loadbare: '${name}' carries values that are not one string per ` +
326
+ `column, ignoring it`);
327
+ return Promise.resolve([]);
328
+ }
329
+ const fields = sent;
322
330
  if (fields.rows !== undefined && !wellFormedRows(fields.rows)) {
323
331
  console.warn(`loadbare: '${name}' carries rows that are not keys and string ` +
324
332
  `values by query, ignoring it`);
@@ -348,6 +356,13 @@ export function createHub(pages) {
348
356
  },
349
357
  };
350
358
  }
359
+ /** Values as a server request carries them: one string per column. */
360
+ function stringValues(values) {
361
+ return (typeof values === "object" &&
362
+ values !== null &&
363
+ !Array.isArray(values) &&
364
+ Object.values(values).every((v) => typeof v === "string"));
365
+ }
351
366
  /** Gathered rows as the hub sends them: string keys and string values. */
352
367
  function wellFormedRows(rows) {
353
368
  if (typeof rows !== "object" || rows === null || Array.isArray(rows)) {
@@ -392,7 +407,7 @@ function layered(before, after) {
392
407
  function urlItem(page, moved) {
393
408
  if (moved === undefined)
394
409
  return undefined;
395
- const refused = unusableUrl(moved);
410
+ const refused = unusableUrl(moved, true);
396
411
  if (refused === undefined) {
397
412
  return {
398
413
  query: URL_QUERY,
@@ -411,7 +426,7 @@ function urlItem(page, moved) {
411
426
  function choicesItem(page, chose) {
412
427
  if (chose === undefined)
413
428
  return [];
414
- const refused = unusableUrl(chose);
429
+ const refused = unusableUrl(chose, false);
415
430
  if (refused !== undefined) {
416
431
  console.warn(`loadbare: page '${page}' returned ${CHOICES_QUERY} ${refused}, ` +
417
432
  `ignoring it`);
@@ -435,10 +450,11 @@ function leadsHere(page, parms, row) {
435
450
  * Why a returned row for `lb-url` or `lb-choices` cannot be used, or nothing
436
451
  * when it can.
437
452
  *
438
- * A value is a string, as a control's is and a URL's is. At least one column
439
- * is named: a row naming none changes nothing.
453
+ * A value is a string, as a control's is and a URL's is, or for `lb-url`,
454
+ * whose parms a URL may repeat, an array of strings. At least one column is
455
+ * named: a row naming none changes nothing.
440
456
  */
441
- function unusableUrl(columns) {
457
+ function unusableUrl(columns, several) {
442
458
  if (typeof columns !== "object" ||
443
459
  columns === null ||
444
460
  Array.isArray(columns)) {
@@ -447,8 +463,12 @@ function unusableUrl(columns) {
447
463
  const values = Object.values(columns);
448
464
  if (values.length === 0)
449
465
  return "naming no column";
450
- if (!values.every((v) => typeof v === "string")) {
451
- return `holding a value that is not a string; write each value as "..."`;
466
+ const usable = (v) => typeof v === "string" ||
467
+ (several && Array.isArray(v) && v.every((e) => typeof e === "string"));
468
+ if (!values.every(usable)) {
469
+ return several
470
+ ? `holding a value that is not a string or an array of strings`
471
+ : `holding a value that is not a string; write each value as "..."`;
452
472
  }
453
473
  return undefined;
454
474
  }
@@ -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 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/**\n * A query that answers with one row, identified by its `key` column, or with\n * `null` when there is none.\n */\nexport function row(\n key: string,\n run: (ctx: HubContext) => Row | null | Promise<Row | null>,\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 as object)) {\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 // No row reaches the browser as null, which JSON keeps and\n // undefined it would drop.\n item.row = (result ?? null) as Row | null;\n } else {\n if (result === null || result === undefined) {\n console.warn(\n `loadbare: query '${name}' is declared rows but answered with nothing`,\n );\n continue;\n }\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\" : result == null ? \"nothing\" : \"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 as object)) {\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"]}
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/**\n * A query that answers with one row, identified by its `key` column, or with\n * `null` when there is none.\n */\nexport function row(\n key: string,\n run: (ctx: HubContext) => Row | null | Promise<Row | null>,\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. An array of strings repeats the parm once per value. 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 | 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 as object)) {\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 // No row reaches the browser as null, which JSON keeps and\n // undefined it would drop.\n item.row = (result ?? null) as Row | null;\n } else {\n if (result === null || result === undefined) {\n console.warn(\n `loadbare: query '${name}' is declared rows but answered with nothing`,\n );\n continue;\n }\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\" : result == null ? \"nothing\" : \"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, ...sent } = request;\n // Only a column of lb-url holds several values, and the hub answers\n // those itself: a column the server receives holds one.\n if (sent.values !== undefined && !stringValues(sent.values)) {\n console.warn(\n `loadbare: '${name}' carries values that are not one string per ` +\n `column, ignoring it`,\n );\n return Promise.resolve([]);\n }\n const fields = sent as RequestFields;\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/** Values as a server request carries them: one string per column. */\nfunction stringValues(values: unknown): values is Record<string, string> {\n return (\n typeof values === \"object\" &&\n values !== null &&\n !Array.isArray(values) &&\n Object.values(values).every((v) => typeof v === \"string\")\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 as object)) {\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, true);\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, false);\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, or for `lb-url`,\n * whose parms a URL may repeat, an array of strings. At least one column is\n * named: a row naming none changes nothing.\n */\nfunction unusableUrl(columns: unknown, several: boolean): 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 const usable = (v: unknown) =>\n typeof v === \"string\" ||\n (several && Array.isArray(v) && v.every((e) => typeof e === \"string\"));\n if (!values.every(usable)) {\n return several\n ? `holding a value that is not a string or an array of strings`\n : `holding a value that is not a string; write each value as \"...\"`;\n }\n return undefined;\n}\n"]}
@@ -263,12 +263,12 @@ At run time the hub performs a closed set of operations:
263
263
  | A `row` that is `null` | Land its columns as null and remove `lb-key-value`; under a row template, the row leaves |
264
264
  | A new key through a row template | Clone the row template, stamp `lb-row-live` and `lb-key-value`, fill it, insert it |
265
265
  | All rows | Place every row by the query's order, or the user's; remove rows whose keys did not arrive |
266
- | An answer to a request from inside the list | Leave the rows already showing where they stand, stamp `lb-row-unsorted` on each out of order |
266
+ | An answer to a request, under `lb-manual` | Leave the rows already showing where they stand, stamp `lb-row-unplaced` on each out of place |
267
267
  | A patch | Upsert the rows named, remove the keys named, leave other rows in place |
268
268
  | A group template | Build a group where the order breaks, keep it while its run goes on, remove it with its last row |
269
269
  | An aggregate (`lb-count`, `lb-sum` and the rest) | Set the element from the rows showing in its group, or in the whole list |
270
270
  | A row changes, moves or leaves | Stamp `lb-row-created`, `lb-row-changed`, `lb-row-moved`, `lb-row-requested` or `lb-row-leaving`; remove a leaving row once its animations finish |
271
- | After rows land | Stamp `lb-query-row-count`, `lb-query-unsorted-count` and `lb-query-order` |
271
+ | After rows land | Stamp `lb-query-row-count`, `lb-query-unplaced-count` and `lb-query-order` |
272
272
  | The user makes a choice | Land `lb-choices`, place and filter the rows already held again, tell the server |
273
273
  | A row the user's filter does not pass | Set `hidden` on it |
274
274
  | An `lb-show` column | Move the element into, or out of, a `<template lb-show>` |
@@ -566,10 +566,12 @@ request the page declares.
566
566
  `commandfor` and one of three commands: `--lb-edit` fills the form from
567
567
  the row the button sits in, as the hub holds it, with no round trip;
568
568
  `--lb-new` starts it empty for an insert; `--lb-release` lets it go.
569
+ `--lb-new` aimed at a list nested in a form adds a row not yet entered,
570
+ which the form's submit carries with no key.
569
571
  Inside a `<dialog>`, pointing opens the dialog and letting go closes it.
570
- - An edit from inside a list leaves that list's rows where they stand. A
571
- fourth command, `--lb-sort`, aimed at the list, places them by the order
572
- with no round trip.
572
+ - A list carrying `lb-manual` leaves its rows where they stand, by order,
573
+ filter or both, for an answer to any request. A fourth command,
574
+ `--lb-place`, aimed at the list, places them with no round trip.
573
575
  - `<input type="checkbox" lb-select>` in a row template selects its row. An
574
576
  update or delete for the query that names no one row carries the selected
575
577
  keys as `keys`, and the server runs `rowUpdate` or `rowDelete` once per
@@ -758,7 +760,7 @@ Checked at build time, as errors:
758
760
  the browser's lifecycle callbacks
759
761
  - a chrome without exactly one `<lb-hub>` and one empty `<main>` inside it,
760
762
  and a page carrying either
761
- - an `lb-` attribute in markup that is not one of the seventeen a developer
763
+ - an `lb-` attribute in markup that is not one of the eighteen a developer
762
764
  writes; the rest are stamps, and script never assigns them
763
765
  - an `lb-request` value beginning with `lb-` that is not one of the three
764
766
  requests Loadbare provides
@@ -827,8 +829,8 @@ Nothing is scoped, renamed, added or removed.
827
829
  styled with `[lb-query-row-count="0"]`.
828
830
  - Every change the hub makes to a list is stamped: `lb-row-created`,
829
831
  `lb-row-changed`, `lb-row-moved`, `lb-row-requested`, `lb-row-leaving` and
830
- `lb-group-leaving`, and a row an edit left out of order carries
831
- `lb-row-unsorted`. A leaving row is removed once its animations finish,
832
+ `lb-group-leaving`, and a row an answer left out of place carries
833
+ `lb-row-unplaced`. A leaving row is removed once its animations finish,
832
834
  so an application animates rows with CSS alone.
833
835
 
834
836
  ### Elsewhere
@@ -988,11 +990,11 @@ cross-reference.
988
990
 
989
991
  | Owned | Count | Names |
990
992
  | ------------------------------------ | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
991
- | `lb-*` attributes a developer writes | 17 | `lb-query`, `lb-column`, `lb-show`, `lb-group`, `lb-count`, `lb-sum`, `lb-avg`, `lb-min`, `lb-max`, `lb-order`, `lb-filter`, `lb-request`, `lb-select`, `lb-url-link`, `lb-url-parm`, `lb-url-push`, `lb-url-unknown` |
992
- | `lb-*` stamps | 28 | `lb-column-value`, `lb-key-value`, `lb-row-live`, `lb-query-row-count`, `lb-query-unsorted-count`, `lb-query-order`, `lb-group-live`, `lb-group-column`, `lb-group-value`, `lb-row-created`, `lb-row-changed`, `lb-row-moved`, `lb-row-requested`, `lb-row-unsorted`, `lb-row-leaving`, `lb-group-leaving`, `lb-row-editing`, `lb-row-selected`, `lb-request-pending`, `lb-request-error`, `lb-request-refused`, `lb-page`, `lb-page-title`, and on `<lb-hub>` `lb-load-state`, `lb-server-state`, `lb-error`, `lb-pending`, `lb-as-of` |
993
+ | `lb-*` attributes a developer writes | 18 | `lb-query`, `lb-column`, `lb-show`, `lb-group`, `lb-count`, `lb-sum`, `lb-avg`, `lb-min`, `lb-max`, `lb-order`, `lb-filter`, `lb-manual`, `lb-request`, `lb-select`, `lb-url-link`, `lb-url-parm`, `lb-url-push`, `lb-url-unknown` |
994
+ | `lb-*` stamps | 28 | `lb-column-value`, `lb-key-value`, `lb-row-live`, `lb-query-row-count`, `lb-query-unplaced-count`, `lb-query-order`, `lb-group-live`, `lb-group-column`, `lb-group-value`, `lb-row-created`, `lb-row-changed`, `lb-row-moved`, `lb-row-requested`, `lb-row-unplaced`, `lb-row-leaving`, `lb-group-leaving`, `lb-row-editing`, `lb-row-selected`, `lb-request-pending`, `lb-request-error`, `lb-request-refused`, `lb-page`, `lb-page-title`, and on `<lb-hub>` `lb-load-state`, `lb-server-state`, `lb-error`, `lb-pending`, `lb-as-of` |
993
995
  | `lb-*` build-time attributes | 2 | `lb-exp-slot`, `lb-exp-template` |
994
996
  | Requests Loadbare provides | 3 | `lb-row-insert`, `lb-row-update`, `lb-row-delete` |
995
- | Commands for `commandfor` | 4 | `--lb-edit`, `--lb-new`, `--lb-release`, `--lb-sort` |
997
+ | Commands for `commandfor` | 4 | `--lb-edit`, `--lb-new`, `--lb-release`, `--lb-place` |
996
998
  | `lb*` methods on custom elements | 4 | `lbAttached`, `lbRowsLanded`, `lbRequestDone`, `lbDetached` |
997
999
  | Attribute namespace for expansion | 1 | `exp-*` |
998
1000
  | Reserved tags | 1 | `<lb-hub>` |
package/docs/miswarts.md CHANGED
@@ -47,9 +47,10 @@ template, and only the `name`.
47
47
 
48
48
  ## Checkboxes and radio groups always gather a value
49
49
 
50
- **What.** An unchecked checkbox gathers `"false"`, a checked one
51
- `"true"`, and its `value` attribute means nothing to the hub. A radio
52
- group with none checked gathers `""`.
50
+ **What.** A checkbox without a `value` attribute is a flag: unchecked it
51
+ gathers `"false"`, checked `"true"`. A radio group with none checked
52
+ gathers `""`. A checkbox carrying `value` is one of a set holding a query
53
+ parm, and gathers as HTML has it.
53
54
 
54
55
  **Rule broken.** Where HTML already has an opinion, Loadbare/app
55
56
  follows it. HTML sends a checkbox's `value` when it
@@ -238,6 +238,48 @@ and after Back alike
238
238
  Loadbare assigns no parm a meaning, and a column of `lb-url` whose name
239
239
  begins with `lb-` is never a query parm.
240
240
 
241
+ ### Several values
242
+
243
+ A URL may repeat a parm, `/statements?band=3&band=5`, as an HTML form does
244
+ for a `<select multiple>` or for checkboxes sharing a name. That column of
245
+ `lb-url` holds every value, in order, as an array of strings. A parm the URL
246
+ carries once is a string, as it always is. Two controls hold several values:
247
+
248
+ ```html
249
+ <div lb-query="lb-url">
250
+ <select multiple lb-column="band" lb-request="lb-row-update">
251
+ <option value="3">Cash</option>
252
+ <option value="5">Investments</option>
253
+ </select>
254
+ </div>
255
+
256
+ <form lb-query="lb-url" lb-request="lb-row-update">
257
+ <label><input type="checkbox" lb-column="band" value="3" /> Cash</label>
258
+ <label><input type="checkbox" lb-column="band" value="5" /> Investments</label>
259
+ <button>Show</button>
260
+ </form>
261
+ ```
262
+
263
+ - A `<select multiple>` selects each option whose `value` is among the
264
+ column's, and writes the values of the options selected.
265
+ - A checkbox carrying `value` is one of a set naming the column. It is
266
+ checked when its `value` is among the column's, and the set writes the
267
+ values of the ones checked. A checkbox without `value` is a flag; see
268
+ [Checkboxes](./data-binding.md#checkboxes).
269
+ - Writing no value takes the parm out of the URL.
270
+ - Either control carries the values as a JSON array in `lb-column-value`.
271
+ - A control that holds one value, and any other element, shows the first,
272
+ as `URLSearchParams.get` reads it. A form-associated custom element
273
+ receives the value as it is, a string or an array, and may give an array
274
+ back; see [Being a control](./custom-elements.md#being-a-control).
275
+ - A handler's `url()` writes several values as an array of strings, and
276
+ `contextFor` reads them with `parms.getAll`.
277
+
278
+ Only a column of `lb-url` holds several values. Every other column holds
279
+ one, so the builder refuses a control holding several anywhere but inside
280
+ `lb-query="lb-url"`, and the server ignores a request that carries several
281
+ for a column of its own.
282
+
241
283
  A query parm is user input. A query still takes no argument from the
242
284
  browser, and a parm arrives the way the session cookie does, as part of the
243
285
  request the context is built from, but a user who types `?acct=99999`
@@ -268,7 +268,7 @@ Each direction has one mechanism for each kind of message:
268
268
 
269
269
  | Direction | What | How | Today |
270
270
  |----------------|--------------------------------------------|-----------------------------|-------|
271
- | Hub to element | State that lasts | An attribute the hub stamps | `lb-column-value`, `lb-key-value`, `lb-query-row-count`, `lb-query-unsorted-count`, `lb-query-order`, `lb-group-*`, `lb-row-*`, `lb-request-pending`, `lb-request-error`, `lb-request-refused` |
271
+ | Hub to element | State that lasts | An attribute the hub stamps | `lb-column-value`, `lb-key-value`, `lb-query-row-count`, `lb-query-unplaced-count`, `lb-query-order`, `lb-group-*`, `lb-row-*`, `lb-request-pending`, `lb-request-error`, `lb-request-refused` |
272
272
  | Hub to element | State the platform already names | A property | A control's `value` |
273
273
  | Hub to element | What the hub did | An optional `lb` method | `lbAttached`, `lbRowsLanded`, `lbRequestDone`, `lbDetached` |
274
274
  | Hub to element | A moment an element started | A bubbling event | `lb-request-done` |
@@ -404,6 +404,11 @@ customElements.define("star-rating", StarRating);
404
404
  Dispatch `change` with `bubbles: true`, as a native control's does, when the
405
405
  user commits an edit. Setting `value` from the hub fires nothing.
406
406
 
407
+ Inside `lb-query="lb-url"`, a control may hold several values. The hub sets
408
+ its `value` to a string, or to an array of strings when the URL repeats the
409
+ parm, and gathers an array back when `value` gives one; see
410
+ [Several values](./chrome.md#several-values).
411
+
407
412
  A form owns a form-associated custom element the way it owns an `<input>`,
408
413
  including through the HTML `form` attribute, so a form gathers it on submit.
409
414
  Write `formResetCallback` to restore the default value: the hub resets a form
@@ -627,7 +632,7 @@ had there hears the event, and no custom element the hub has removed hears
627
632
  ### Holding rows
628
633
 
629
634
  The hub lands every row template itself, places every row by the query's
630
- order (see [Rows the user is working in](./data-binding.md#rows-the-user-is-working-in)
635
+ order (see [Placing by hand](./data-binding.md#placing-by-hand)
631
636
  for when it does not), and builds every group the markup's group templates describe; see
632
637
  [Order](./data-binding.md#order) and [Groups](./data-binding.md#groups). A
633
638
  plain element carrying `lb-query` with a row template inside it needs no
@@ -656,12 +661,12 @@ Everything else is the hub's:
656
661
  |-------------|----------------------------------------------------------|
657
662
  | Cloning | Clones the row template once per new key |
658
663
  | Matching | Fills the live row already showing that key, when its values changed |
659
- | Placing | Places every live row by the user's order or the query's, and leaves them where they stand for an edit from inside the list |
664
+ | Placing | Places every live row by the user's order or the query's, and leaves them where they stand for an answer under `lb-manual` |
660
665
  | Filtering | Hides a row the user's filter does not pass, and a group whose rows are all hidden |
661
666
  | Grouping | Builds a group where the order breaks, keeps it while its run goes on, and removes it with its last row |
662
667
  | Removing | Marks the live rows the response says are gone as leaving, and removes them |
663
668
  | Stamping | Stamps each live row with `lb-row-live`, `lb-key-value`, and what happened to it |
664
- | Counting | Stamps `lb-query-row-count` with the number of live rows showing, `lb-query-unsorted-count` with the number out of order, and `lb-query-order` with the order placing them |
669
+ | Counting | Stamps `lb-query-row-count` with the number of live rows showing, `lb-query-unplaced-count` with the number out of place, and `lb-query-order` with the order placing them |
665
670
 
666
671
  `lb-options.browser.ts` and `lb-table.browser.ts` in
667
672
  [`@loadbare/widgets`](./widgets.md) are two uses of `lbRowsLanded`: one