@loadbare/app 0.25.0 → 0.27.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 (56) hide show
  1. package/dist/build/assemble.d.ts +1 -2
  2. package/dist/build/assemble.d.ts.map +1 -1
  3. package/dist/build/assemble.js +31 -37
  4. package/dist/build/assemble.js.map +1 -1
  5. package/dist/build/cli.d.ts +4 -10
  6. package/dist/build/cli.d.ts.map +1 -1
  7. package/dist/build/cli.js +11 -33
  8. package/dist/build/cli.js.map +1 -1
  9. package/dist/build/declared.js +1 -1
  10. package/dist/build/declared.js.map +1 -1
  11. package/dist/core/lb-constants.d.ts +15 -13
  12. package/dist/core/lb-constants.d.ts.map +1 -1
  13. package/dist/core/lb-constants.js +47 -51
  14. package/dist/core/lb-constants.js.map +1 -1
  15. package/dist/core/lb-types.d.ts +12 -11
  16. package/dist/core/lb-types.d.ts.map +1 -1
  17. package/dist/core/lb-types.js +7 -0
  18. package/dist/core/lb-types.js.map +1 -1
  19. package/dist/hub/lb-apply.d.ts +10 -34
  20. package/dist/hub/lb-apply.d.ts.map +1 -1
  21. package/dist/hub/lb-apply.js +92 -151
  22. package/dist/hub/lb-apply.js.map +1 -1
  23. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  24. package/dist/hub/lb-hub.browser.js +115 -127
  25. package/dist/hub/lb-hub.browser.js.map +1 -1
  26. package/dist/hub/lb-lifecycle.d.ts.map +1 -1
  27. package/dist/hub/lb-lifecycle.js +4 -11
  28. package/dist/hub/lb-lifecycle.js.map +1 -1
  29. package/dist/server/lb-express.d.ts.map +1 -1
  30. package/dist/server/lb-express.js +3 -3
  31. package/dist/server/lb-express.js.map +1 -1
  32. package/dist/server/lb-server.d.ts +35 -19
  33. package/dist/server/lb-server.d.ts.map +1 -1
  34. package/dist/server/lb-server.js +141 -88
  35. package/dist/server/lb-server.js.map +1 -1
  36. package/docs/binding-cases.md +150 -0
  37. package/docs/comparison.md +34 -38
  38. package/docs/opinions.md +2 -2
  39. package/docs/reference/chrome.md +8 -10
  40. package/docs/reference/css.md +9 -10
  41. package/docs/reference/custom-elements.md +11 -8
  42. package/docs/reference/data-binding.md +93 -134
  43. package/docs/reference/names.md +21 -22
  44. package/docs/reference/page-files.md +24 -26
  45. package/docs/reference/server.md +3 -4
  46. package/docs/roadmap.md +2 -2
  47. package/docs/testing.md +5 -6
  48. package/package.json +1 -1
  49. package/skills/loadbare-app/SKILL.md +55 -48
  50. package/skills/loadbare-app/references/chrome.md +8 -10
  51. package/skills/loadbare-app/references/css.md +9 -10
  52. package/skills/loadbare-app/references/custom-elements.md +11 -8
  53. package/skills/loadbare-app/references/data-binding.md +93 -134
  54. package/skills/loadbare-app/references/names.md +21 -22
  55. package/skills/loadbare-app/references/page-files.md +24 -26
  56. package/skills/loadbare-app/references/server.md +3 -4
@@ -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. 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"]}
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, oneRow, parseOrder } from \"../core/lb-types.js\";\nimport {\n COLUMN_TYPES,\n FILTERS_COLUMN_QUERY,\n FILTERS_QUERY,\n KIND_ROW,\n KIND_ROWS,\n LB_RESERVED_PREFIX,\n REQUEST_READ,\n REQUEST_ROW_DELETE,\n REQUEST_ROW_INSERT,\n REQUEST_ROW_UPDATE,\n SORT_COLUMN_ORDER,\n SORT_COLUMN_QUERY,\n SORT_QUERY,\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/** What answers a `row` query. */\ntype RowRun = (ctx: HubContext) => HubResult | Promise<HubResult>;\n/** What answers a `rows` query, given its resolved order. */\ntype RowsRun = (ctx: HubContext, order: string) => HubResult | Promise<HubResult>;\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: RowRun | RowsRun;\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-sort`,\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 and\n * filters 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 *\n * `run` receives the resolved order — the user's, from `lb-sort`, or the\n * one `options` declares — and orders its SQL by it, so the rows it answers\n * arrive in the order the page shows them in.\n */\nexport function rows(\n key: string,\n run: (ctx: HubContext, order: string) => 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 resolved order for each `rows` query, returned from `onPageEnter` to\n * restore what the user last chose. Each key is a query name and its value\n * the order the query runs its rows in, as `rows()` declares one.\n */\nexport function sort(orders: Record<string, string>): HubData {\n return {\n [SORT_QUERY]: Object.entries(orders).map(([query, order]) => ({\n [SORT_COLUMN_QUERY]: query,\n [SORT_COLUMN_ORDER]: order,\n })),\n };\n}\n\n/**\n * The user's filters for the page, returned from `onPageEnter` to restore\n * them. Each key is a query name and its value the filter values for that\n * query's rows, by column.\n */\nexport function filters(\n rows: Record<string, Record<string, string>>,\n): HubData {\n return {\n [FILTERS_QUERY]: Object.entries(rows).map(([query, values]) => ({\n [FILTERS_COLUMN_QUERY]: query,\n ...values,\n })),\n };\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 `sort()`, `filters()`,\n * with `url()` or alone, to restore the user's choices for the page.\n *\n * `onChoices` runs each time the user changes the sort or a filter, with\n * the query and its new sort or filters, so an application can keep them\n * for `onPageEnter` to 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 change: {\n query: string;\n sort?: string;\n filters?: Record<string, string>;\n },\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.\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 // Every answer is rows: a `row` query's is its row, or none.\n item.rows = result == null ? [] : [result as Row];\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.types !== undefined) item.types = query.types;\n }\n out.push(item);\n }\n return out;\n }\n\n /**\n * Run queries by name, each answering with all its rows or its row. A\n * `rows` query receives the resolved order for its name from `sorts`, or\n * its declared default, and the order each ran with is returned beside the\n * data for the caller to write to `lb-sort`.\n */\n async function run(\n page: string,\n names: string[],\n ctx: HubContext,\n sorts?: Map<string, string>,\n ): Promise<{ data: HubData; orders: Map<string, string> }> {\n const data: HubData = {};\n const orders = new Map<string, string>();\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 order = sorts?.get(name) ?? query.order ?? \"\";\n const result =\n query.kind === KIND_ROWS\n ? await (query.run as RowsRun)(ctx, order)\n : await (query.run as RowRun)(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 if (query.kind === KIND_ROWS && order !== \"\") orders.set(name, order);\n }\n return { data, orders };\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)).data,\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 [SORT_QUERY]: sorts,\n [FILTERS_QUERY]: filters,\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(), sort(), filters() or nothing, ignoring them`,\n );\n }\n const item = follow ? urlItem(page, moved) : undefined;\n if (item && !(parms && leadsHere(page, parms, oneRow(item)!))) return [item];\n const { data, orders } = await run(\n page,\n Object.keys(entry.queries),\n ctx,\n sortsToMap(sorts),\n );\n // The sort and filters go first, so the rows that read them land by\n // them.\n return [\n ...sortItem(orders),\n ...filtersItem(page, filters),\n ...items(page, data, true),\n ];\n },\n\n async runRequest(page, request, ctx) {\n const { name, ...sent } = request;\n // The hub's read: the queries named, at the parms the request was sent\n // at, answered as any query is.\n if (name === REQUEST_READ) {\n const declared = pages[page]?.queries ?? {};\n const names = (Array.isArray(sent.queries) ? sent.queries : []).filter(\n (q): q is string => typeof q === \"string\" && q in declared,\n );\n return run(page, names, ctx).then(({ data }) => items(page, data));\n }\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 // A sort or filter the user changed, told to the page. One with no\n // `onChoices` is not keeping them, and is not told.\n if (fields.query === SORT_QUERY || fields.query === FILTERS_QUERY) {\n const onChoices = pages[page]?.requests.onChoices;\n if (name !== REQUEST_ROW_UPDATE || !onChoices) {\n return Promise.resolve([]);\n }\n const query = fields.key ?? \"\";\n const sort =\n fields.query === SORT_QUERY\n ? fields.values?.[SORT_COLUMN_ORDER]\n : undefined;\n const filters =\n fields.query === FILTERS_QUERY ? fields.values : undefined;\n await onChoices(ctx, {\n query,\n ...(sort !== undefined && { sort }),\n ...(filters !== undefined && { filters }),\n });\n // The browser re-sorts or re-filters the rows it holds; the server\n // only persists the change for the next page.\n return [];\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 rows: [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/** The sorts `onPageEnter` returned, as a map of query name to order. */\nfunction sortsToMap(sorts: unknown): Map<string, string> {\n const map = new Map<string, string>();\n if (!Array.isArray(sorts)) return map;\n for (const row of sorts) {\n if (typeof row !== \"object\" || row === null) continue;\n const r = row as Record<string, unknown>;\n if (\n typeof r[SORT_COLUMN_QUERY] === \"string\" &&\n typeof r[SORT_COLUMN_ORDER] === \"string\"\n ) {\n map.set(r[SORT_COLUMN_QUERY], r[SORT_COLUMN_ORDER]);\n }\n }\n return map;\n}\n\n/**\n * The `lb-sort` item for the orders each `rows` query ran with, or nothing\n * when none ran with an order.\n */\nfunction sortItem(orders: Map<string, string>): ResponseItem[] {\n if (orders.size === 0) return [];\n return [\n {\n query: SORT_QUERY,\n kind: KIND_ROWS,\n key: SORT_COLUMN_QUERY,\n rows: [...orders.entries()].map(([query, order]) => ({\n [SORT_COLUMN_QUERY]: query,\n [SORT_COLUMN_ORDER]: order,\n })),\n },\n ];\n}\n\n/**\n * The `lb-filters` item for the filters `onPageEnter` returned, or nothing\n * when it returned none. Each row names its query and its filter values, by\n * column.\n */\nfunction filtersItem(page: string, filters: unknown): ResponseItem[] {\n if (filters === undefined) return [];\n if (!Array.isArray(filters)) {\n console.warn(\n `loadbare: page '${page}' returned ${FILTERS_QUERY} that is not a ` +\n `list, ignoring it`,\n );\n return [];\n }\n const rows: Row[] = [];\n for (const row of filters) {\n if (typeof row !== \"object\" || row === null) continue;\n const r = row as Record<string, unknown>;\n if (typeof r[FILTERS_COLUMN_QUERY] !== \"string\") continue;\n const values = Object.fromEntries(\n Object.entries(r).filter(\n ([key, value]) => key !== FILTERS_COLUMN_QUERY && typeof value === \"string\",\n ),\n ) as Record<string, string>;\n rows.push({ [FILTERS_COLUMN_QUERY]: r[FILTERS_COLUMN_QUERY], ...values });\n }\n if (rows.length === 0) return [];\n return [\n {\n query: FILTERS_QUERY,\n kind: KIND_ROWS,\n key: FILTERS_COLUMN_QUERY,\n rows,\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` 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, 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"]}
@@ -0,0 +1,150 @@
1
+ # Binding cases
2
+
3
+ Concept Zero: All data is in rows, rows belong to named queries.
4
+ - Everything is a row. A row is a javascript object where
5
+ the values are always scalar (Loadbare does not understand object
6
+ in object)
7
+ - A named query has zero, one or more rows in its results.
8
+ - A query has an owner, either the application or the hub.
9
+
10
+ Browser Navigation goes through the hub-owned `lb-url` query, which
11
+ is read/write in the browser and on the server.
12
+
13
+ Queries are bound to HTML by the shape of the HTML. An element names
14
+ a query with `lb-query="query-name"`. If that element does not contain
15
+ an HTML `<template>`, then it is assumed its children represent only one row.
16
+ If it contains the template, it can display any number of rows, where
17
+ the template is the representation of a row within the query results.
18
+
19
+ The hub "lands" values onto elements by their declared `lb-column`
20
+ attribute.
21
+
22
+ ## CRUD Cases
23
+
24
+ Application owned queries are processed on the server and sent to the
25
+ browser, where the hub "lands" the values by the `lb-column` attribute
26
+ on elements.
27
+
28
+ The `lb-action` attribute allows update, insert and delete of single
29
+ rows.
30
+
31
+ Because list elements just contain individual rows, no special vocabulary
32
+ is required to enable CRUD on the single row of a list - each element
33
+ in the list acts like a standalone row.
34
+
35
+ The vocabulary is expanded to allow update, insert and delete of
36
+ multiple rows on list elements.
37
+
38
+ ## UI Cases
39
+
40
+ Within the UI we speak of "interaction state", which represents whether
41
+ a row being edited by the user is new or existing.
42
+
43
+ The cases build in three layers, and each layer adds one idea. The notes
44
+ under each layer say where today's markup departs from it. Navigation, the
45
+ hub's other rows, and a query owned in the browser sit outside the layers.
46
+ Today a query parm the URL repeats is one column holding an array, where
47
+ every other value is scalar.
48
+
49
+ A verb is aimed at a target: the bound row or the list.
50
+ Insert, update and delete are verbs, and so is any request the application
51
+ names, such as marking an item complete; the owner decides what a named
52
+ verb does. The owner answers with every row the write changed, in any
53
+ query it owns, and may move the URL. A write can therefore add, change or
54
+ drop rows of other queries, recalculate a whole list, or move a row from
55
+ one list to another.
56
+
57
+ An element that is a row with no binding takes its target from the verb:
58
+ an insert makes a new row.
59
+
60
+ ### Layer 1: one row
61
+
62
+ An element that is a row, most often a form, is bound to a new row or to a
63
+ row on record. A new row inserts, a row on record updates, and either can
64
+ be deleted; deleting a new row discards it.
65
+
66
+ | Case | What the user does |
67
+ |--------------------|---------------------------------------------------------------------|
68
+ | Show | sees the row |
69
+ | Create one | fills an empty row and saves it |
70
+ | Edit one | changes a row on record and saves it |
71
+ | Delete one | deletes the row |
72
+ | Abandon | leaves the row unsaved, reverted or discarded |
73
+ | Create in sequence | saves a new row, and the next starts, carrying values |
74
+ | Choose | sets a column to a row of another query, or creates one and sets it |
75
+
76
+ Interaction state for one row: whether it is new or on record, and whether
77
+ the user has changed it.
78
+
79
+ A new row may start with values: carried forward from the row saved before
80
+ it, taken from what the page shows, or copied from another row. The
81
+ mechanism is not decided.
82
+
83
+ A picker is a list whose rows are the choices for one column of a row. It
84
+ is a control of layer 1 rather than a list the user works on. Choosing sets
85
+ the column to the chosen row's key. Creating a row in the picker's query is
86
+ Create one, and the new row's key is then chosen.
87
+
88
+ Today:
89
+ - Create one. A form for a new record is bound to the saved row by moving
90
+ the URL to its key, rather than by binding the form.
91
+ - Abandon. Nothing reverts a row on record that the user has changed in
92
+ place. A new row in a list is discarded by a delete that the hub answers
93
+ with no round trip, a request acting as a command.
94
+ - Create in sequence. The ghost row is an insert form laid out as a table
95
+ row, outside the list. Nothing carries values forward. A server can give
96
+ a new record starting values by answering its query with them, with an
97
+ empty key.
98
+ - Choose. Creating a row and choosing it is the widget `lb-insert-dialog`'s
99
+ work, in its own script.
100
+
101
+ ### Layer 2: a list
102
+
103
+ Every row of a list is a layer 1 row. The user works on it in place, or in
104
+ a form bound to it; which one is a choice of HTML, and the binding is the
105
+ same. A list adds only what many rows make possible.
106
+
107
+ | Case | What the list adds |
108
+ |---------------------------|----------------------------------------------|
109
+ | Show | order and filter |
110
+ | Any layer 1 case | on one of its rows |
111
+ | Several changes, one save | rows added, changed, removed, saved together |
112
+
113
+ Interaction state for a list adds which rows are
114
+ marked for deletion at the next save.
115
+
116
+ Today:
117
+ - Several changes, one save. Only new rows can wait for a save, and only in
118
+ a list inside a form's own query, which is layer 3. Nothing saves changes
119
+ to rows on record together, or marks rows for deletion at the next save.
120
+
121
+ ### Layer 3: a row with a list inside it
122
+
123
+ A layer 1 row whose element holds a layer 2 list, saved together: a master
124
+ and its details. The list inside follows the outer row's binding, so a
125
+ form bound to another row shows that row's details.
126
+
127
+ | Case | What the user does |
128
+ |----------------|---------------------------------------------|
129
+ | Saved together | changes a row and the list inside it, saves |
130
+
131
+ A master and its details as two queries saved separately is a layer 1 row
132
+ beside a layer 2 list, and not layer 3.
133
+
134
+ Today:
135
+ - The list inside follows the outer row's binding by a round trip: Edit and
136
+ New read the list's query again, with the bound key as a parm.
137
+ - A new master is keyed `""` by either of two means: its query answering an
138
+ empty key, or New aimed at the form.
139
+
140
+ ## Decisions
141
+
142
+ - One shape. The wire and the hub carry every answer as rows, zero or
143
+ more. The server's `row()` declares a query that answers with at most
144
+ one row; the declaration is checked, and it gives the query's elements
145
+ their default binding.
146
+ - The hub owns interaction state, and keeps it where the platform has a
147
+ place for it: a checkbox's `checked`, a control's value, a dialog's
148
+ `open`, an element's stamps. A query the hub owns holds only what the
149
+ platform has no place for: the URL row, the user's choices, and the hub's
150
+ status. A form's binding is its `lb-key-value` and its filled controls.
@@ -202,17 +202,16 @@ observable, and no client cache that answers a request.
202
202
  the path, the page label and the query parms.
203
203
  - An element hidden by `lb-show` exists inside a `<template>` standing where
204
204
  it stood.
205
- - A form pointed at a row carries that row's key as `lb-key-value`, and a
206
- row ticked by its `lb-select` checkbox carries `lb-row-selected`.
205
+ - A form pointed at a row carries that row's key as `lb-key-value`.
207
206
  - The hub keeps each query's last answer until the page changes, only to
208
207
  fill an element that names the query and arrives after it, such as a
209
208
  picker in a new live row, to fill a form pointed at one of its rows, and
210
209
  to place rows again when the user makes a choice. Nothing else is read
211
210
  from it in place of asking the server.
212
211
  - The hub keeps a little state of its own: the name of the current page,
213
- the user's choices for it as the row of its query `lb-choices`, the live
214
- row each form is pointed at, the refusals on show, and, until a request
215
- completes, the form it gathered from.
212
+ the user's order and filters for it as the rows of `lb-sort` and
213
+ `lb-filters`, the live row each form is pointed at, the refusals on show,
214
+ and, until a request completes, the form it gathered from.
216
215
 
217
216
  The server is the source of truth for everything else. A query takes no
218
217
  argument from the browser; its only input is `ctx`, which the application
@@ -255,7 +254,7 @@ At run time the hub performs a closed set of operations:
255
254
 
256
255
  | Trigger | Operation |
257
256
  | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
258
- | Navigation | Clone the page's `<template>` into `<main>`, land `lb-url`, land `lb-choices` empty |
257
+ | Navigation | Clone the page's `<template>` into `<main>`, land `lb-url`, land `lb-sort` and `lb-filters` empty |
259
258
  | A value for a control | Set `value`, and stamp `lb-column-value` |
260
259
  | A value for another native element | Set `textContent`, and stamp `lb-column-value` |
261
260
  | A value for a custom element that is not a control | Stamp `lb-column-value` |
@@ -266,14 +265,13 @@ At run time the hub performs a closed set of operations:
266
265
  | 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
266
  | A patch | Upsert the rows named, remove the keys named, leave other rows in place |
268
267
  | A group template | Build a group where the order breaks, keep it while its run goes on, remove it with its last row |
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 |
268
+ | An aggregate (`lb-aggregate`) | Set the element from the rows showing in its group, or in the whole list |
270
269
  | 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
270
  | After rows land | Stamp `lb-query-row-count`, `lb-query-unplaced-count` and `lb-query-order` |
272
- | The user makes a choice | Land `lb-choices`, place and filter the rows already held again, tell the server |
271
+ | The user makes a sort or a filter | Land `lb-sort` and `lb-filters`, place and filter the rows already held again, tell the server |
273
272
  | A row the user's filter does not pass | Set `hidden` on it |
274
273
  | An `lb-show` column | Move the element into, or out of, a `<template lb-show>` |
275
274
  | A `commandfor` button with `--lb-edit` or `--lb-new` | Fill the form from the held row, or empty it; stamp `lb-key-value` and `lb-row-editing`; open its `<dialog>` |
276
- | An `lb-select` checkbox is ticked | Stamp `lb-row-selected` on its row |
277
275
  | A request starts or ends | Set or remove `lb-request-pending` and `aria-busy`; set `lb-request-error` on failure, `lb-request-refused` and control validity on a refusal |
278
276
  | An insert from a form succeeds | Reset the form |
279
277
  | A request from a pointed form succeeds | Let the form go: empty it, drop its key, close its `<dialog>` |
@@ -324,7 +322,7 @@ There are two kinds of placeholder, and they run at different times.
324
322
  a whole attribute value or a whole text node. The tag supplies it with an
325
323
  `exp-name` attribute. An unsupplied placeholder with no default drops the
326
324
  attribute. The value becomes permanent in the shipped HTML.
327
- - Run time. Data lands through `lb-column`, the aggregate attributes,
325
+ - Run time. Data lands through `lb-column`, `lb-aggregate`,
328
326
  `lb-query` with a row or group `<template>`, and `lb-show`. There is no
329
327
  run-time interpolation into attribute values or text.
330
328
 
@@ -572,10 +570,6 @@ request the page declares.
572
570
  - A list carrying `lb-manual` leaves its rows where they stand, by order,
573
571
  filter or both, for an answer to any request. A fourth command,
574
572
  `--lb-place`, aimed at the list, places them with no round trip.
575
- - `<input type="checkbox" lb-select>` in a row template selects its row. An
576
- update or delete for the query that names no one row carries the selected
577
- keys as `keys`, and the server runs `rowUpdate` or `rowDelete` once per
578
- key.
579
573
 
580
574
  On the server, each entry has `run` and `refresh`. `run` does the work and
581
575
  may return results, such as a `patch()`, or a new row for the URL with
@@ -685,7 +679,7 @@ for the mechanisms by project.
685
679
  `<dialog lb-url-unknown>` if the chrome has one.
686
680
  - The hub serves its own query, `lb-url`, of kind `row` and keyed by
687
681
  `lb-path`. Its columns are `lb-path`, `lb-page-label` (the page file's
688
- `<title>`, or its stub), `lb-page-unknown`, and one column per query parm,
682
+ `<title>`, or its stub), and one column per query parm,
689
683
  bound like any server row.
690
684
  - A control inside `lb-query="lb-url"` carrying `lb-request="lb-row-update"`
691
685
  writes a query parm. The hub answers that request itself: it replaces the
@@ -696,8 +690,8 @@ for the mechanisms by project.
696
690
  - The query string rides on every round trip, and the server hands its parms
697
691
  to `contextFor`.
698
692
  - The user's order and filters for rows already on the page are not query
699
- parms. They are choices, the row of the hub's query `lb-choices`, and
700
- hold until the page changes.
693
+ parms. They are the rows of the hub's queries `lb-sort` and `lb-filters`,
694
+ and hold until the page changes.
701
695
  - The application must be served from the origin root. The hub reaches
702
696
  `/lb` by absolute path, and the chrome links `/client.js` and `/app.css`.
703
697
  - The hub sets `document.title` from `lb-page-label`. On entering a page,
@@ -725,10 +719,10 @@ answers 200 for an unknown path, as Loadbare/app does.
725
719
  ### Loadbare/app
726
720
 
727
721
  ```
728
- loadbare-app-build [--src src] [--out dist] [--watch] [--minify]
722
+ loadbare-app-build [--src src] [--out dist] [--minify]
729
723
  ```
730
724
 
731
- - Four flags. No configuration file.
725
+ - Three flags. No configuration file.
732
726
  - Files are classified by name: `chrome.html`, `*.page.html`,
733
727
  `*.queries.ts`, `*.requests.ts`, `imports.ts`, `*.css`, `<tag>.html`,
734
728
  `<tag>.browser.ts`.
@@ -740,8 +734,10 @@ loadbare-app-build [--src src] [--out dist] [--watch] [--minify]
740
734
  widget script whose tag is never used is never imported.
741
735
  - Outputs: `app.html`, `client.js`, `app.css` when there is a stylesheet,
742
736
  `pages.ts` when there is page data, and `client-entry.ts`.
743
- - `--watch` rebuilds on changes to `.html`, `.ts` and `.css` files. There is
744
- no dev server, no hot module replacement and no browser reload.
737
+ - The builder runs once. `@loadbare/app/dev` is the dev server: it
738
+ rebuilds on changes to `.html`, `.ts` and `.css` files and restarts the
739
+ application's server when a server-side file changed. There is no hot
740
+ module replacement and no browser reload.
745
741
  - `app.html` is formatted with Prettier and never minified.
746
742
 
747
743
  Checked at build time, as errors:
@@ -760,7 +756,7 @@ Checked at build time, as errors:
760
756
  the browser's lifecycle callbacks
761
757
  - a chrome without exactly one `<lb-hub>` and one empty `<main>` inside it,
762
758
  and a page carrying either
763
- - an `lb-` attribute in markup that is not one of the eighteen a developer
759
+ - an `lb-` attribute in markup that is not one of the ten a developer
764
760
  writes; the rest are stamps, and script never assigns them
765
761
  - an `lb-request` value beginning with `lb-` that is not one of the three
766
762
  requests Loadbare provides
@@ -770,18 +766,18 @@ Checked at build time, as errors:
770
766
  `<lb-hub>`
771
767
  - `lb-show` on a `<template>`, on a row template's root, with no `lb-query`
772
768
  around it, or written `!` or `!!column`
773
- - `lb-group` on anything but a `<template>` holding a `<template>`, and an
774
- aggregate on a `<template>` or naming no column
775
- - `lb-order` or `lb-filter` on an element not carrying `lb-query`, or naming
769
+ - `lb-group` on anything but a `<template>` holding a `<template>`, and
770
+ `lb-aggregate` on a `<template>`, naming no function it knows, or naming
771
+ no column for any but `count`
772
+ - `lb-filter` on an element not carrying `lb-query`, or naming
776
773
  no column
777
- - `lb-select` on anything but a checkbox in a row template, or beside
778
- `lb-column` or `lb-request`
779
774
  - a `command` beginning `--lb-` that is not one of the three, and one of the
780
775
  three with no `commandfor`
781
776
  - an `lb-query` naming a query the page does not declare, an `lb-request`
782
777
  naming a request it does not declare under `handlers`, and one of the
783
778
  three requests for a query whose `crud` entry lacks it. The chrome names
784
- only `lb-url` and `lb-choices`. The builder reads the declarations by
779
+ only `lb-url`, `lb-sort`, `lb-filters` and `lb-hub-status`. The builder
780
+ reads the declarations by
785
781
  loading the page modules, so their import-time code runs during the build.
786
782
  - a `.queries.ts` or `.requests.ts` with no matching page
787
783
 
@@ -824,7 +820,7 @@ Nothing is scoped, renamed, added or removed.
824
820
  widgets.
825
821
  - The hub stamps attributes a stylesheet can select on: `lb-column-value`,
826
822
  `lb-key-value`, `lb-row-live`, `lb-query-row-count`, the group stamps,
827
- `lb-row-editing`, `lb-row-selected`, `lb-request-pending`,
823
+ `lb-row-editing`, `lb-request-pending`,
828
824
  `lb-request-error` and `lb-request-refused`. An empty `rows` query is
829
825
  styled with `[lb-query-row-count="0"]`.
830
826
  - Every change the hub makes to a list is stamped: `lb-row-created`,
@@ -853,7 +849,7 @@ Nothing is scoped, renamed, added or removed.
853
849
  Loadbare/app ships no server. It ships:
854
850
 
855
851
  - `createHub(pages)`, an engine with no HTTP code, in `@loadbare/app/server`,
856
- beside `row`, `rows`, `patch`, `url`, `choices` and `refusal`
852
+ beside `row`, `rows`, `patch`, `url`, `sort`, `filters` and `refusal`
857
853
  - `hubRoutes(hub, contextFor, options)`, an Express 5 router, in
858
854
  `@loadbare/app/express`. Express is an optional peer dependency.
859
855
  - `pages.ts`, generated by the builder, which imports every page's queries
@@ -990,8 +986,8 @@ cross-reference.
990
986
 
991
987
  | Owned | Count | Names |
992
988
  | ------------------------------------ | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
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` |
989
+ | `lb-*` attributes a developer writes | 12 | `lb-query`, `lb-column`, `lb-show`, `lb-group`, `lb-aggregate`, `lb-filter`, `lb-manual`, `lb-request`, `lb-url-link`, `lb-url-parm`, `lb-url-push`, `lb-url-unknown` |
990
+ | `lb-*` stamps | 27 | `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-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` |
995
991
  | `lb-*` build-time attributes | 2 | `lb-exp-slot`, `lb-exp-template` |
996
992
  | Requests Loadbare provides | 3 | `lb-row-insert`, `lb-row-update`, `lb-row-delete` |
997
993
  | Commands for `commandfor` | 4 | `--lb-edit`, `--lb-new`, `--lb-release`, `--lb-place` |
@@ -999,11 +995,11 @@ cross-reference.
999
995
  | Attribute namespace for expansion | 1 | `exp-*` |
1000
996
  | Reserved tags | 1 | `<lb-hub>` |
1001
997
  | Reserved events | 2 | `lb-request`, `lb-request-done` |
1002
- | Queries the hub serves | 3 | `lb-url`, with columns `lb-path`, `lb-page-label`, `lb-page-unknown`; `lb-choices`, keyed by `lb-path`; `lb-hub`, with columns `lb-load-state`, `lb-server-state`, `lb-error`, `lb-pending`, `lb-as-of` |
1003
- | Builder flags | 4 | `--src`, `--out`, `--watch`, `--minify` |
998
+ | Queries the hub serves | 4 | `lb-url`, with columns `lb-path`, `lb-page-label`; `lb-sort` and `lb-filters`, keyed by the query's name; `lb-hub-status`, with columns `lb-load-state`, `lb-server-state`, `lb-error`, `lb-pending`, `lb-as-of` |
999
+ | Builder flags | 3 | `--src`, `--out`, `--minify` |
1004
1000
  | Reserved file names and patterns | 8 | `chrome.html`, `imports.ts`, `*.page.html`, `*.queries.ts`, `*.requests.ts`, `<tag>.html`, `<tag>.browser.ts`, `*.css` |
1005
1001
  | Reserved `package.json` keys | 1 | `loadbare.widgets` |
1006
- | Server functions | 8 | `row`, `rows`, `patch`, `url`, `choices`, `refusal`, `createHub`, `hubRoutes` |
1002
+ | Server functions | 9 | `row`, `rows`, `patch`, `url`, `sort`, `filters`, `refusal`, `createHub`, `hubRoutes` |
1007
1003
  | Browser exports | 2 | `applyRow`, `applyResponse` |
1008
1004
 
1009
1005
  Theory states that the whole vocabulary fits in one technical reference,
@@ -1152,7 +1148,7 @@ Each is current behavior or an open item named in the
1152
1148
  | File inputs | Not landed and not gathered. |
1153
1149
  | Field validation | No per-keystroke mechanism. A handler's refusal lands on each control through the browser's own validity; validation placement is an open question. |
1154
1150
  | Concurrent writers | No version or conflict check. Open question. |
1155
- | Chrome-level queries | The chrome names only `lb-url` and `lb-choices`; an application query there is a build error. On the roadmap. |
1151
+ | Chrome-level queries | The chrome names only `lb-url`, `lb-sort`, `lb-filters` and `lb-hub-status`; an application query there is a build error. On the roadmap. |
1156
1152
  | A widget receiving a whole row | Not available. On the roadmap. |
1157
1153
  | Compound foreign keys | A picker offers the same choices on every row; a choice restricted by another column of its row cannot be expressed. Roadmap. |
1158
1154
  | Configurable request deadline | Fixed at ten seconds. |
@@ -1412,8 +1408,8 @@ Each equivalent is approximate.
1412
1408
  | `lb-exp-slot`, `lb-exp-template` | Default slot and named slots; `ng-content` with `select` |
1413
1409
  | `lbRowsLanded` | An effect after a list renders |
1414
1410
  | `lbAttached`, `lbDetached` | Mount and unmount; `onMounted` and `onUnmounted` (Vue); `onMount` and `onDestroy` (Svelte) |
1415
- | `order`, `lb-group` | `ORDER BY` with report-writer control breaks (`BREAK ON`, group bands) |
1416
- | `lb-choices`, `lb-order`, `lb-filter` | A data grid's sort and filter model |
1411
+ | `order`, `lb-group` | `ORDER BY` with report-writer control breaks (`BREAK ON`, group bands) |
1412
+ | `lb-sort`, `lb-filters`, `lb-filter` | A data grid's sort and filter model |
1417
1413
  | `ctx` / `HubContext` | `event.locals` (SvelteKit); loader `context` (React Router 7); request-scoped dependency injection |
1418
1414
  | `imports.ts` | Installing and registering a component library |
1419
1415
  | `pages.ts` | Generated route manifest |
package/docs/opinions.md CHANGED
@@ -109,8 +109,8 @@ every link and every query parm follows this rule. See
109
109
 
110
110
  What the framework itself keeps is presented as rows. The URL is the
111
111
  query `lb-url`, of kind `row`, and lands like any server query. User
112
- preferences and choices on a page are sent as `lb-row-update` for `lb-choices`,
113
- and can land anywhere on the page.
112
+ preferences and choices on a page are sent as `lb-row-update` for `lb-sort`
113
+ and `lb-filters`, and can land anywhere on the page.
114
114
 
115
115
  Most frameworks keep their own state in a store with its own interface.
116
116
  In Loadbare/app, a developer shows the current page's label or a