@loadbare/app 0.9.0 → 0.10.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 (80) hide show
  1. package/README.md +3 -3
  2. package/dist/build/assemble.d.ts.map +1 -1
  3. package/dist/build/assemble.js +74 -66
  4. package/dist/build/assemble.js.map +1 -1
  5. package/dist/build/expand.d.ts.map +1 -1
  6. package/dist/build/expand.js +20 -19
  7. package/dist/build/expand.js.map +1 -1
  8. package/dist/build/locations.d.ts +2 -3
  9. package/dist/build/locations.d.ts.map +1 -1
  10. package/dist/build/locations.js +2 -3
  11. package/dist/build/locations.js.map +1 -1
  12. package/dist/build/pages.d.ts +3 -4
  13. package/dist/build/pages.d.ts.map +1 -1
  14. package/dist/build/pages.js +3 -4
  15. package/dist/build/pages.js.map +1 -1
  16. package/dist/core/lb-constants.d.ts +25 -24
  17. package/dist/core/lb-constants.d.ts.map +1 -1
  18. package/dist/core/lb-constants.js +95 -168
  19. package/dist/core/lb-constants.js.map +1 -1
  20. package/dist/core/lb-types.d.ts +64 -77
  21. package/dist/core/lb-types.d.ts.map +1 -1
  22. package/dist/core/lb-types.js +40 -7
  23. package/dist/core/lb-types.js.map +1 -1
  24. package/dist/hub/lb-apply.d.ts +47 -37
  25. package/dist/hub/lb-apply.d.ts.map +1 -1
  26. package/dist/hub/lb-apply.js +174 -193
  27. package/dist/hub/lb-apply.js.map +1 -1
  28. package/dist/hub/lb-hub.browser.d.ts +1 -1
  29. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  30. package/dist/hub/lb-hub.browser.js +411 -449
  31. package/dist/hub/lb-hub.browser.js.map +1 -1
  32. package/dist/server/lb-express.d.ts +5 -5
  33. package/dist/server/lb-express.d.ts.map +1 -1
  34. package/dist/server/lb-express.js +35 -66
  35. package/dist/server/lb-express.js.map +1 -1
  36. package/dist/server/lb-server.d.ts +77 -135
  37. package/dist/server/lb-server.d.ts.map +1 -1
  38. package/dist/server/lb-server.js +132 -79
  39. package/dist/server/lb-server.js.map +1 -1
  40. package/docs/TECHREF-1.0.md +861 -587
  41. package/docs/comparison.md +222 -185
  42. package/docs/prior-art.md +15 -14
  43. package/docs/reference/builder.md +9 -3
  44. package/docs/reference/chrome.md +107 -56
  45. package/docs/reference/custom-elements.md +199 -173
  46. package/docs/reference/data-binding.md +374 -374
  47. package/docs/reference/overview.md +12 -10
  48. package/docs/reference/page-files.md +135 -99
  49. package/docs/reference/server.md +2 -2
  50. package/docs/reference/widgets.md +104 -110
  51. package/docs/roadmap.md +32 -39
  52. package/docs/terms-of-art.md +57 -0
  53. package/docs/testing.md +97 -68
  54. package/docs/theory.md +92 -58
  55. package/docs/tutorials/010-pages-and-navigation.md +20 -12
  56. package/docs/tutorials/020-css.md +6 -3
  57. package/docs/tutorials/030-html-decomposition.md +9 -7
  58. package/docs/tutorials/040-displaying-data.md +30 -13
  59. package/docs/tutorials/{050-actions.md → 050-requests.md} +25 -15
  60. package/docs/tutorials/060-custom-element-code.md +17 -16
  61. package/docs/tutorials/065-conditional-rendering.md +34 -23
  62. package/docs/tutorials/070-displaying-a-list.md +29 -21
  63. package/docs/tutorials/072-inserting-into-a-list.md +24 -16
  64. package/docs/tutorials/074-deleting-from-a-list.md +9 -7
  65. package/docs/tutorials/076-updating-a-list-item.md +11 -10
  66. package/docs/tutorials/080-widget-requests.md +71 -43
  67. package/docs/tutorials/090-using-widget-libraries.md +22 -22
  68. package/package.json +1 -1
  69. package/skills/loadbare-app/SKILL.md +178 -122
  70. package/skills/loadbare-app/references/TECHREF-1.0.md +861 -587
  71. package/skills/loadbare-app/references/builder.md +9 -3
  72. package/skills/loadbare-app/references/chrome.md +107 -56
  73. package/skills/loadbare-app/references/custom-elements.md +199 -173
  74. package/skills/loadbare-app/references/data-binding.md +374 -374
  75. package/skills/loadbare-app/references/overview.md +12 -10
  76. package/skills/loadbare-app/references/page-files.md +135 -99
  77. package/skills/loadbare-app/references/server.md +2 -2
  78. package/skills/loadbare-app/references/widgets.md +104 -110
  79. package/docs/analysis-accidental-complexity.md +0 -149
  80. package/docs/analysis-closed-set.md +0 -210
@@ -1 +1 @@
1
- {"version":3,"file":"lb-hub.browser.js","sourceRoot":"","sources":["../../hub/lb-hub.browser.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport {\n ACTION_ROW_DELETE,\n ACTION_ROW_INSERT,\n ACTION_ROW_UPDATE,\n ATTR_ACTION,\n ATTR_CELL,\n ATTR_ERROR,\n ATTR_KEY_VALUE,\n ATTR_LIST,\n ATTR_NAV_LINK,\n ATTR_PAGE,\n ATTR_PENDING,\n ATTR_QUERY_PARM,\n ATTR_QUERY_PARM_PUSH,\n ATTR_ROW,\n ATTR_UNKNOWN_PAGE,\n ATTR_VALUE,\n LB_ACTIONS,\n LB_RESERVED_PREFIX,\n HUB_TAG_NAME,\n NAV_CELL_LABEL,\n NAV_CELL_URI,\n NAV_ROW,\n QUERY_PARMS_ROW,\n LB_EVENT_NAME,\n LB_ENDPOINT,\n LB_REQUEST_TIMEOUT_MS,\n} from \"../core/lb-constants.js\";\nimport {\n withQueryParms,\n type HubData,\n type HubRequest,\n} from \"../core/lb-types.js\";\nimport {\n applyData,\n applyQueryParms,\n isValueControl,\n within,\n} from \"./lb-apply.js\";\n\nexport { applyData, applyRow } from \"./lb-apply.js\";\n\n/** What each operation takes from scope, and what it cannot be sent without. */\nconst OPERATIONS: Record<\n string,\n { fills: readonly string[]; requires: readonly string[] }\n> = {\n [ACTION_ROW_INSERT]: { fills: [\"list\"], requires: [\"list\", \"values\"] },\n [ACTION_ROW_DELETE]: { fills: [\"list\", \"key\"], requires: [\"list\", \"key\"] },\n [ACTION_ROW_UPDATE]: {\n fills: [\"list\", \"key\"],\n requires: [\"list\", \"key\", \"values\"],\n },\n};\n\n/**\n * Complete a request from where its element sits — see docs/TECHREF-1.0.md,\n * \"Requests\". Every request passes through here, whoever built it, so a\n * widget states what it did and never where it is.\n *\n * The scope field is `list` or `row`, whichever attribute scoped the nearest\n * ancestor, so a declared action fired inside a single-row scope says so and\n * the wire never needs a noun for the addressable thing. A field the request\n * already carries is the dispatcher's to decide and is left as it is.\n *\n * False when the request cannot be sent, having said why.\n */\nfunction scope(request: HubRequest | undefined, el: Element): boolean {\n if (!request?.action) {\n console.warn(`lb-hub: event without an action, ignoring`, request);\n return false;\n }\n const { action } = request;\n const reserved = action.startsWith(LB_RESERVED_PREFIX);\n // The reserved prefix is the whole of the discriminant, so a reserved\n // name that is not one of the three operations is a typo rather than\n // something to forward. Refused here, where the widget can see why: a\n // server would only report that it declares no such action.\n if (reserved && !(LB_ACTIONS as readonly string[]).includes(action)) {\n console.error(\n `lb-hub: '${action}' begins with '${LB_RESERVED_PREFIX}' ` +\n `but names no operation, ignoring`,\n );\n return false;\n }\n\n // A control that writes a query parm has said what it does with its value.\n // An action beside that would send the choice twice, once as a request and\n // once as a page load, so the request is the one refused.\n if (el.hasAttribute(ATTR_QUERY_PARM)) {\n console.error(\n `lb-hub: <${el.localName}> carries ${ATTR_QUERY_PARM} and sends ` +\n `'${action}' as well, not sent; a query parm change reloads the page`,\n el,\n );\n return false;\n }\n\n const fields = request as unknown as Record<string, unknown>;\n // The element's own lb-list or lb-row is what it displays. Where it sits\n // is its ancestors', the way a control belongs to the form around it.\n const parent = el.parentElement;\n const enclosing = parent?.closest(`[${ATTR_LIST}], [${ATTR_ROW}]`);\n const row = parent?.closest(`[${ATTR_KEY_VALUE}]`);\n const inScope: Record<string, string | null | undefined> = {\n list: enclosing?.getAttribute(ATTR_LIST),\n row: enclosing?.hasAttribute(ATTR_LIST)\n ? null\n : enclosing?.getAttribute(ATTR_ROW),\n // Only a row of that scope: a key from outside it names a row of some\n // other list.\n key:\n row && enclosing?.contains(row) ? row.getAttribute(ATTR_KEY_VALUE) : null,\n cell: el.getAttribute(ATTR_CELL),\n };\n const fill = (name: string) => {\n const value = inScope[name];\n if (fields[name] === undefined && value !== null && value !== undefined) {\n fields[name] = value;\n }\n };\n\n const operation = OPERATIONS[action];\n if (!operation) {\n // A declared action names one scope or the other, never both.\n if (fields.list === undefined && fields.row === undefined) {\n fill(\"list\");\n fill(\"row\");\n }\n fill(\"key\");\n fill(\"cell\");\n return true;\n }\n\n for (const name of operation.fills) fill(name);\n // An element carrying lb-cell is a record of one cell, the way a control\n // has a value and a form has values: the map holds that cell and nothing\n // beside it. Its value is the one the dispatcher sent, or else what its\n // control holds.\n //\n // Anything else gathers from the record it belongs to, the way a button\n // submits its form owner: the nearest form, table row or live row around\n // it, the element itself included. A <tr> counts because it is the one\n // place a form cannot go around a row's controls. What else sits beside\n // the element never decides it. The record is the scope's, so it is never\n // the scope itself nor outside it; with none, there is nothing to say which\n // cells are meant. Outside every scope the list is what is missing, and\n // that is what gets reported.\n if (fields.values === undefined && operation.requires.includes(\"values\")) {\n const cell = el.getAttribute(ATTR_CELL);\n if (cell !== null) {\n const control = controlOf(el);\n const value =\n typeof fields.value === \"string\" ? fields.value : control?.value;\n if (value !== undefined) {\n fields.values = { [cell]: value };\n if (action === ACTION_ROW_INSERT && control?.value === value) {\n gathered.set(request, [{ cell: el, control, value }]);\n }\n }\n } else if (fields.value !== undefined) {\n console.error(\n `lb-hub: '${action}' from <${el.localName}> carries a value but no ` +\n `${ATTR_CELL} to say which column it is, not sent`,\n el,\n );\n return false;\n } else {\n const owner = el.closest(`form, tr, [${ATTR_KEY_VALUE}]`);\n const ours =\n !enclosing || (owner !== enclosing && enclosing.contains(owner));\n if (owner && ours) {\n const { values, read } = gatherValues(owner);\n if (read.length > 0) {\n fields.values = values;\n if (action === ACTION_ROW_INSERT) gathered.set(request, read);\n }\n } else if (enclosing) {\n console.error(\n `lb-hub: '${action}' from <${el.localName}> is in no form, table ` +\n `row or live row to gather values from, not sent; put its cells ` +\n `in a <form>`,\n el,\n );\n return false;\n }\n }\n }\n // An operation carries its values and nothing else: the value they were\n // built from is not a second copy on the wire.\n delete fields.value;\n const missing = operation.requires.filter((f) => fields[f] === undefined);\n if (missing.length > 0) {\n console.error(\n `lb-hub: '${action}' from <${el.localName}> has no ` +\n `${missing.join(\", \")} in scope, not sent`,\n el,\n );\n return false;\n }\n return true;\n}\n\n/** Dispatch the bare action an element carries, for the hub to scope. */\nfunction dispatchAction(el: Element): void {\n // Pressed again while its round trip is in flight. Ignoring it makes a\n // pending native action disabled in fact, so a stylesheet only has to\n // show it. A widget is not held back this way: one that sends on change\n // must have its latest value sent, not dropped.\n if (el.hasAttribute(ATTR_PENDING)) return;\n const detail = { action: el.getAttribute(ATTR_ACTION)! } as HubRequest;\n el.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));\n}\n\n/** A form control whose state is its `value`. */\ntype ValueControl = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;\n\n/** One cell a request read, the control it read, and the value it read. */\ninterface Read {\n cell: Element;\n control: ValueControl;\n value: string;\n}\n\n/**\n * What each insert gathered, until its round trip succeeds. Keyed by the\n * request itself, so a request an ancestor stops leaves nothing behind.\n */\nconst gathered = new WeakMap<HubRequest, Read[]>();\n\n/**\n * The control a cell reads from, for gathering rather than landing.\n *\n * A cell is always either a native form control itself (a bare\n * `<input lb-cell>`) or a widget that wraps one (`<lb-input>`, `<lb-select>`)\n * — that wrapping is structural, not incidental, which is what lets this be\n * one rule instead of one per widget type. The controls are the ones a value\n * lands on, so what is read back is what landed.\n */\nfunction controlOf(el: Element): ValueControl | undefined {\n return isValueControl(el)\n ? el\n : [...el.querySelectorAll(\"input, select, textarea\")].find(isValueControl);\n}\n\n/**\n * Every lb-cell of an element's scope, gathered into one values map. Shared\n * by lb-row-insert and lb-row-update — both submit a batch of cells, and\n * differ only in whether a key comes with them. What was read comes back\n * beside the map, so an insert can reset it once it succeeds.\n *\n * Found the way a row lands, so a cell inside a nested scope is that scope's\n * and is not read: the options of a picker in the row are the picker's rows,\n * while the picker's own cell is the row's.\n */\nfunction gatherValues(el: Element): {\n values: Record<string, string>;\n read: Read[];\n} {\n const values: Record<string, string> = {};\n const read: Read[] = [];\n for (const cell of within(el, `[${ATTR_CELL}]`)) {\n const name = cell.getAttribute(ATTR_CELL)!;\n const control = controlOf(cell);\n if (control === undefined) {\n console.warn(\n `lb-hub: lb-cell '${name}' has no input, select or textarea to ` +\n `read, ignoring it`,\n cell,\n );\n continue;\n }\n values[name] = control.value;\n read.push({ cell, control, value: control.value });\n }\n return { values, read };\n}\n\n/**\n * After an insert succeeds, what it gathered goes back to its default, the\n * way `form.reset()` resets a form: the row those cells held now lives in the\n * list. A control showing something other than what was read holds an edit\n * made during the round trip, which was not sent, and is left alone.\n *\n * Where a value landed, `lb-value` comes off the cell first, so a widget that\n * renders from it sees the attribute go and returns to its default. The\n * control is reset after, so it ends at its default whatever the widget did.\n * Nothing is dispatched, as `form.reset()` fires no `change`.\n */\nfunction resetGathered(read: Read[]): void {\n for (const { cell, control, value } of read) {\n if (control.value !== value) continue;\n cell.removeAttribute(ATTR_VALUE);\n control.removeAttribute(ATTR_VALUE);\n if (control.localName === \"select\") {\n for (const option of (control as HTMLSelectElement).options) {\n option.selected = option.defaultSelected;\n }\n } else {\n const text = control as HTMLInputElement | HTMLTextAreaElement;\n text.value = text.defaultValue;\n }\n }\n}\n\n/**\n * A path names a page host, and nothing finer. The bare path resolves to\n * `index`, the same convention a static file server follows — an\n * application names its landing page that way rather than the hub singling\n * one out.\n */\nfunction pageNameFor(path: string): string {\n const name = path.replace(/^\\/+|\\/+$/g, \"\");\n return name === \"\" ? \"index\" : name;\n}\n\n/**\n * The `<template>` a page's markup ships in. The builder guarantees one per\n * page name (build/locations.ts refuses two page files sharing a stub), so\n * a second match here means the document was assembled by something else,\n * and is worth saying out loud rather than resolving by document order.\n */\nfunction pageHostFor(page: string): HTMLTemplateElement | null {\n const hosts: HTMLTemplateElement[] = [];\n for (const el of document.querySelectorAll(`template[${ATTR_PAGE}]`)) {\n if (el.getAttribute(ATTR_PAGE) === page) {\n hosts.push(el as HTMLTemplateElement);\n }\n }\n if (hosts.length === 0) {\n console.error(`lb-hub: no page host for '${page}'`);\n return null;\n }\n if (hosts.length > 1) {\n console.error(\n `lb-hub: ${hosts.length} page hosts carry ${ATTR_PAGE}=\"${page}\", ` +\n `using the first`,\n );\n }\n return hosts[0]!;\n}\n\n/**\n * Fetch one round trip against the client-side deadline. Throws — rather\n * than swallowing — on a non-ok response, a network failure, or a timeout,\n * so a caller can tell a failed request from a genuinely empty result.\n *\n * The page names the path below the endpoint and the query string the\n * browser is showing rides after it verbatim, so every round trip, a page\n * load or an action, reaches the server with the URL on screen in hand.\n */\nasync function fetchData(page: string, request?: HubRequest): Promise<HubData> {\n const path = page.split(\"/\").map(encodeURIComponent).join(\"/\");\n const url = `${LB_ENDPOINT}/${path}${location.search}`;\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), LB_REQUEST_TIMEOUT_MS);\n try {\n // An empty body is how the hub asks for the page's whole query set.\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(request ?? {}),\n signal: controller.signal,\n });\n if (!res.ok) {\n throw new Error(`lb-hub: server responded ${res.status} for ${url}`);\n }\n return (await res.json()) as HubData;\n } finally {\n clearTimeout(timeout);\n }\n}\n\n/**\n * An element's round trip is in flight, or is not. aria-busy states the same\n * fact to assistive technology, which no stylesheet on lb-pending reaches.\n */\nfunction pending(el: Element | undefined, on: boolean): void {\n if (!el) return;\n if (on) {\n el.removeAttribute(ATTR_ERROR);\n el.setAttribute(ATTR_PENDING, \"\");\n el.setAttribute(\"aria-busy\", \"true\");\n } else {\n el.removeAttribute(ATTR_PENDING);\n el.removeAttribute(\"aria-busy\");\n }\n}\n\n/**\n * The hub is a singleton outside <main>. It survives every page change,\n * so it needs no id to disambiguate it.\n */\nclass LbHub extends HTMLElement {\n private main!: HTMLElement;\n\n /**\n * The page currently in <main>. A page declares its own requests, so a request\n * has to say which page it came from. The page rides on the URL rather\n * than in the request, leaving the operation set closed.\n */\n private page = \"\";\n\n connectedCallback() {\n this.main = this.querySelector(\"main\")!;\n\n // Scoped in the capture phase, on the way down, so every ancestor that\n // sees the request on its way back up sees it whole. A request that\n // cannot be sent is stopped here, before anything sees it at all.\n this.addEventListener(\n LB_EVENT_NAME,\n (e: Event) => {\n const origin = e.target as Element;\n const request = (e as CustomEvent<HubRequest>).detail;\n if (!scope(request, origin)) e.stopPropagation();\n },\n { capture: true },\n );\n\n this.addEventListener(LB_EVENT_NAME, (e: Event) => {\n const request = (e as CustomEvent<HubRequest>).detail;\n // The element that dispatched the request, not e.currentTarget (the\n // hub itself, since the event bubbles) — pending/error state belongs\n // at the origin so a widget can watch its own attributes.\n const origin = (e.target as Element | null) ?? undefined;\n const page = this.page;\n pending(origin, true);\n void fetchData(page, request)\n .then(async (data) => {\n const { [QUERY_PARMS_ROW]: written, ...load } = data;\n if (written === undefined) applyData(this, data);\n else {\n await this.changeQueryParms(\n page,\n written as Record<string, string>,\n load,\n );\n }\n resetGathered(gathered.get(request) ?? []);\n })\n .catch((err) => {\n console.error(`lb-hub: request failed`, err);\n origin?.setAttribute(ATTR_ERROR, \"\");\n })\n .finally(() => pending(origin, false));\n });\n\n /**\n * A native element carrying an action. The hub dispatches the bare action\n * from that element as the ordinary bubbling event, rather than sending\n * it directly, so it is scoped exactly as a widget's is and an ancestor\n * widget can still stop it and confirm. A native action button and a\n * hand-written widget therefore produce identical events.\n *\n * A widget carrying an action is left alone — it owns its own interaction\n * and decides what counts as performing it, which for a <select> is a\n * change rather than a click. The test is the same lexical one applyData\n * uses: a hyphen in the tag name.\n */\n this.addEventListener(\"click\", (e: MouseEvent) => {\n const el = (e.target as Element | null)?.closest(`[${ATTR_ACTION}]`);\n if (!el || el.localName.includes(\"-\")) return;\n // A form performs its action on submit, whatever was clicked in it.\n if (el instanceof HTMLFormElement) return;\n // A button beside the cells is pressed once they are done. An element\n // that is a cell, or holds them, has no such moment: a click into its\n // own input would send the row. Deciding when a cell has changed is a\n // widget's.\n const action = el.getAttribute(ATTR_ACTION);\n if (\n (action === ACTION_ROW_INSERT || action === ACTION_ROW_UPDATE) &&\n (el.hasAttribute(ATTR_CELL) || el.querySelector(`[${ATTR_CELL}]`))\n ) {\n console.error(\n `lb-hub: '${action}' on <${el.localName}> is or holds a cell, so ` +\n `a click on it is not sent; put it on a <form>, a button or a widget`,\n el,\n );\n return;\n }\n e.preventDefault();\n dispatchAction(el);\n });\n\n // The same delegation for the event a form owns.\n this.addEventListener(\"submit\", (e: SubmitEvent) => {\n const form = e.target as Element | null;\n if (!form || !form.hasAttribute(ATTR_ACTION)) return;\n e.preventDefault();\n dispatchAction(form);\n });\n\n // Only anchors that opted in. Everything else is a real link.\n this.addEventListener(\"click\", (e: MouseEvent) => {\n const link = (e.target as Element | null)?.closest(`a[${ATTR_NAV_LINK}]`);\n if (!link) return;\n e.preventDefault();\n const { pathname, search } = new URL((link as HTMLAnchorElement).href);\n history.pushState(null, \"\", pathname + search);\n void this.navigate();\n });\n\n /**\n * A control that writes a query parm. Changing what the page shows is\n * not going anywhere, so the address updates in place and stays\n * copyable, and history does not accumulate unless the control asks\n * for it. Only the one parm is touched: the rest of the query string may\n * have arrived by link, with no control on screen to say it again.\n */\n this.addEventListener(\"change\", (e: Event) => {\n const el = (e.target as Element | null)?.closest(`[${ATTR_QUERY_PARM}]`);\n if (!el || !this.contains(el)) return;\n const control = controlOf(el);\n if (control === undefined) {\n console.warn(\n `lb-hub: ${ATTR_QUERY_PARM} on <${el.localName}> has no input, ` +\n `select or textarea to read, ignoring it`,\n el,\n );\n return;\n }\n const name = el.getAttribute(ATTR_QUERY_PARM)!;\n const url = new URL(location.href);\n if ((url.searchParams.get(name) ?? \"\") === control.value) return;\n // Empty is the default, and a default is left out of the URL, so its\n // length tracks how far the user has narrowed the page.\n url.search = withQueryParms(url.searchParams, {\n [name]: control.value,\n }).toString();\n if (el.hasAttribute(ATTR_QUERY_PARM_PUSH)) {\n history.pushState(null, \"\", url);\n } else {\n history.replaceState(null, \"\", url);\n }\n void this.navigate(el);\n });\n\n addEventListener(\"popstate\", () => void this.navigate());\n\n void this.navigate();\n }\n\n /**\n * Load the page at the URL the browser is showing. Every way of arriving\n * comes through here — a cold load, a nav link, Back, a query parm write —\n * so each one is the same page load and a URL always shows the same thing.\n *\n * Entering a page is the one operation that replaces host DOM. The host is\n * already in the document, so selecting a page is a lookup and there is no\n * fetching mechanism on the host channel. Staying on the page, with only\n * the query string changed, keeps the DOM it has: the queries re-run and\n * land by key, so a list that got its rows back keeps them, and its scroll.\n *\n * `origin` is the control that changed the query string, if one did, and\n * carries the round trip's pending and error state as a request's origin\n * does.\n */\n private async navigate(origin?: Element): Promise<void> {\n const page = pageNameFor(location.pathname);\n this.landNavigation(page);\n const template = pageHostFor(page);\n if (!template) {\n this.reportUnknownPage();\n this.reveal();\n return;\n }\n if (page !== this.page) {\n this.page = page;\n // Insert the page shell before fetching its data, not after: the\n // template is already in the document, so holding it back behind a\n // network round trip only leaves <main> empty for no reason.\n this.main.replaceChildren(template.content.cloneNode(true));\n }\n this.reveal();\n let data: HubData = {};\n pending(origin, true);\n try {\n data = await fetchData(page);\n } catch (err) {\n console.error(`lb-hub: failed to load data for '${page}'`, err);\n origin?.setAttribute(ATTR_ERROR, \"\");\n } finally {\n pending(origin, false);\n }\n this.land(data);\n }\n\n /**\n * A page load's answer, landed: the page's data, then the query string on\n * the controls that write it, so a picker already has the options its\n * value selects.\n */\n private land(data: HubData): void {\n applyData(this, data);\n applyQueryParms(this.main, new URLSearchParams(location.search));\n }\n\n /**\n * A server response changed the query parms: the request wrote parms only\n * the write could know, such as the key of a row it inserted. The server\n * has already loaded the page at them, so this is the rest of a query parm\n * write — the URL, then the load landed — with the round trip done.\n *\n * The history entry is replaced, as a control's write replaces it: the\n * page is showing something else, not going somewhere.\n *\n * A load that did not come back, which the server says by sending the\n * parms alone, is asked for again, so a failure reports as a page load's.\n * It does not mark the element that sent the request: that request's\n * write succeeded, and an error on it would invite sending it twice.\n *\n * `page` is where the request was sent from. A user who left it while the\n * request was in flight is on a URL that is not the server's to change.\n */\n private async changeQueryParms(\n page: string,\n written: Record<string, string>,\n load: HubData,\n ): Promise<void> {\n if (pageNameFor(location.pathname) !== page) return;\n const url = new URL(location.href);\n url.search = withQueryParms(url.searchParams, written).toString();\n history.replaceState(null, \"\", url);\n if (Object.keys(load).length === 0) return this.navigate();\n this.landNavigation(page);\n this.land(load);\n }\n\n /**\n * An app that ships `<body hidden>` (see docs/reference/chrome.md) is\n * asking to stay invisible until there is something coherent to show,\n * rather than flash the chrome before `<main>` has real content. This is\n * a no-op for an app that doesn't use that convention.\n */\n private reveal(): void {\n document.body.hidden = false;\n }\n\n /**\n * The hub's own row: where the page is, in the vocabulary a server\n * answer arrives in, so a chrome displays it the way it displays anything\n * — `lb-row=\"lb-navigation\"` on a subtree, `lb-cell` on what shows a\n * value. Landed before the page host is looked up, so a miss has it too;\n * the unknown-page dialog is one consumer among whatever others the\n * chrome writes.\n *\n * The label is the nav's: the text of the first lb-nav-link anchor whose\n * href names this page. Taken from the nav rather than from a click, so a\n * click, a reload and the back button all land the same label — and a\n * path no anchor names lands an empty one, explicitly, so a consumer is\n * never left showing the previous page's.\n *\n * The URI is the path and query string together, as the address bar has\n * them: a link that opens a page narrowed is a different place from the\n * bare page, though the label, which names the page, is the same.\n *\n * Landed only where a subtree names it. applyData() warns about a query\n * with no scope, and a chrome that displays no navigation has nothing to\n * be warned about.\n */\n private landNavigation(page: string): void {\n if (!this.querySelector(`[${ATTR_ROW}=\"${NAV_ROW}\"]`)) return;\n let label = \"\";\n for (const a of this.querySelectorAll<HTMLAnchorElement>(\n `a[${ATTR_NAV_LINK}]`,\n )) {\n if (pageNameFor(new URL(a.href).pathname) !== page) continue;\n label = a.textContent?.trim() ?? \"\";\n break;\n }\n applyData(this, {\n [NAV_ROW]: {\n [NAV_CELL_LABEL]: label,\n [NAV_CELL_URI]: location.pathname + location.search,\n },\n });\n }\n\n /**\n * A path that resolves to no page host is discovered client-side, after a\n * successful 200 — every route gets the same document, so there is no\n * HTTP 404 to report (see docs/tutorials/010-pages-and-navigation.md).\n * lb-unknown-page is optional: an application that declares none gets\n * nothing beyond navigate()'s console.error. The builder guarantees the\n * element it's on is a <dialog> (build/assemble.ts), so this can call\n * showModal() without checking the tag here.\n *\n * Only opens it. What the dialog says it gets from landNavigation(),\n * already run, by naming the hub's row in lb-row like any subtree.\n *\n * Scoped to the hub, like every other lookup here, so that one rule holds\n * without exception: what the hub acts on is inside the hub.\n */\n private reportUnknownPage(): void {\n const dialog = this.querySelector(`[${ATTR_UNKNOWN_PAGE}]`);\n if (!dialog) return;\n const modal = dialog as HTMLDialogElement;\n if (!modal.open) modal.showModal();\n }\n}\n\ncustomElements.define(HUB_TAG_NAME, LbHub);\n"]}
1
+ {"version":3,"file":"lb-hub.browser.js","sourceRoot":"","sources":["../../hub/lb-hub.browser.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,SAAS,EACT,eAAe,EACf,UAAU,EACV,YAAY,EACZ,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,aAAa,EACb,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,uBAAuB,EACvB,eAAe,EACf,SAAS,GACV,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,WAAW,GAKZ,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,aAAa,EACb,SAAS,EACT,gBAAgB,EAChB,MAAM,GAEP,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAExD,oEAAoE;AACpE,MAAM,KAAK,GAAkD;IAC3D,CAAC,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC;IACzC,CAAC,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC;IAChD,CAAC,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC;CACvC,CAAC;AAEF,2EAA2E;AAC3E,SAAS,OAAO,CAAC,EAAW;IAC1B,OAAO,EAAE,CAAC,aAAa,EAAE,OAAO,CAAC,IAAI,UAAU,GAAG,CAAC,IAAI,IAAI,CAAC;AAC9D,CAAC;AAED;;;GAGG;AACH,MAAM,QAAQ,GAAG,IAAI,cAAc,UAAU,UAAU,IAAI,CAAC;AAE5D,4CAA4C;AAC5C,SAAS,SAAS,CAAC,EAAW;IAC5B,OAAO,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;AAC9B,CAAC;AAED;;;GAGG;AACH,SAAS,OAAO,CAAC,EAAW,EAAE,KAAc;IAC1C,qEAAqE;IACrE,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;QAC9B,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,EAAE,CAAC,aAAa,EAAE,OAAO,CAAC,IAAI,cAAc,GAAG,CAAC,CAAC;IACrD,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,MAAM,OAAO,GAAG,GAAG,KAAK,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC;IACxD,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,cAAc,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/E,CAAC;AAED;;;;GAIG;AACH,SAAS,UAAU,CAAC,IAAqB;IACvC,OAAO,CAAC,GAAI,IAAI,CAAC,QAAyC,CAAC,CAAC;AAC9D,CAAC;AAED;;;GAGG;AACH,SAAS,WAAW,CAAC,EAAW;IAC9B,MAAM,KAAK,GAAI,EAAyB,CAAC,IAAI,CAAC;IAC9C,OAAO,KAAK,IAAK,KAAiB,CAAC,SAAS,KAAK,MAAM;QACrD,CAAC,CAAE,KAAyB;QAC5B,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAWD;;;;;;;;;;;GAWG;AACH,SAAS,QAAQ,CAAC,EAAW;IAC3B,IAAI,EAAE,CAAC,YAAY,CAAC,WAAW,CAAC;QAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IACtE,IAAI,EAAE,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,EAAqB,CAAC;QACnC,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC1D,CAAC;IACD,MAAM,IAAI,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;IAC3B,IAAI,IAAI;QAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,WAAW,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC5E,MAAM,IAAI,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC7B,IAAI,IAAI;QAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAClE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,oEAAoE;AACpE,SAAS,MAAM,CAAC,KAAmB;IAIjC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,QAAQ,IAAI,EAAE,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;QAC5C,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAAE,SAAS;QAChD,MAAM,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC;QAC3B,IAAI,MAAM,IAAI,MAAM,KAAK,KAAM,CAAC,IAAI,IAAI,KAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACrE,SAAS;QACX,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClB,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC;IAC5B,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AAC9B,CAAC;AAED,yEAAyE;AACzE,MAAM,YAAY,GAAG,IAAI,OAAO,EAA+B,CAAC;AAEhE;;;;;;GAMG;AACH,SAAS,QAAQ,CAAC,OAA+B,EAAE,EAAW;IAC5D,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;QACnB,OAAO,CAAC,IAAI,CAAC,4CAA4C,EAAE,OAAO,CAAC,CAAC;QACpE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IACzB,IAAI,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/D,OAAO,CAAC,KAAK,CACX,YAAY,IAAI,kBAAkB,kBAAkB,iBAAiB;YACnE,qCAAqC,CACxC,CAAC;QACF,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC3B,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3C,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxD,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC;IAC1B,CAAC;IAED,2EAA2E;IAC3E,2CAA2C;IAC3C,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/C,IAAI,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,CAAC,KAAK,CACX,YAAY,IAAI,sDAAsD,EACtE,EAAE,CACH,CAAC;QACF,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/B,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACnE,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,CAAC,KAAK,KAAK,KAAK,CAAC,YAAY,CAAC,UAAU,CAAE,CAAC;QAClD,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACjC,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,CAAC,GAAG,KAAK,GAAG,CAAC;IAC7C,CAAC;IAED,IAAI,IAAI,KAAK,kBAAkB,IAAI,KAAK,EAAE,IAAI,EAAE,CAAC;QAC/C,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAC1B,CAAC,KAAK,EAAE,EAAE,CACR,OAAO,CAAC,KAAK,CAAC,KAAK,SAAS;YAC5B,CAAC,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CACpE,CAAC;QACF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO,CAAC,KAAK,CACX,YAAY,IAAI,WAAW,EAAE,CAAC,SAAS,WAAW;gBAChD,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EACnC,EAAE,CACH,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,SAAS,MAAM,CAAC,EAAW;IACzB,IAAI,EAAE,CAAC,YAAY,CAAC,oBAAoB,CAAC;QAAE,OAAO;IAClD,MAAM,MAAM,GAAe,EAAE,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,YAAY,CAAE,EAAE,CAAC;IACpE,EAAE,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED,+EAA+E;AAC/E,SAAS,cAAc,CAAC,EAAW;IACjC,MAAM,IAAI,GAAI,EAAuB,CAAC,IAAc,CAAC;IACrD,MAAM,OAAO,GACX,CAAC,EAAE,CAAC,SAAS,KAAK,QAAQ,IAAI,IAAI,KAAK,QAAQ,CAAC;QAChD,CAAC,EAAE,CAAC,SAAS,KAAK,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC;IACxE,OAAO,OAAO,IAAI,WAAW,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC;AAC7C,CAAC;AAED;;;;GAIG;AACH,MAAM,WAAW,GAAG;IAClB,SAAS;IACT,QAAQ;IACR,0BAA0B;IAC1B,QAAQ;IACR,UAAU;IACV,OAAO;IACP,SAAS;IACT,QAAQ;IACR,OAAO;IACP,iBAAiB;IACjB,iBAAiB;IACjB,aAAa;CACd,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb;;;;GAIG;AACH,SAAS,MAAM,CAAC,MAAe,EAAE,EAAW;IAC1C,KAAK,IAAI,IAAI,GAAmB,MAAM,EAAE,IAAI,IAAI,IAAI,KAAK,EAAE,GAAG,CAAC;QAC7D,IAAI,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QACtE,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2DAA2D;AAC3D,SAAS,OAAO,CAAC,QAAoC,EAAE,IAAY;IACjE,OAAO,QAAQ,EAAE,YAAY,CAAC,eAAe,CAAC,IAAI,IAAI,CAAC;AACzD,CAAC;AAED;;;;GAIG;AACH,SAAS,WAAW,CAAC,IAAY;IAC/B,MAAM,KAAK,GAA0B,EAAE,CAAC;IACxC,KAAK,MAAM,EAAE,IAAI,QAAQ,CAAC,gBAAgB,CAAC,YAAY,SAAS,GAAG,CAAC,EAAE,CAAC;QACrE,IAAI,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE,CAAC;YACxC,KAAK,CAAC,IAAI,CAAC,EAAyB,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,CAAC,KAAK,CACX,WAAW,KAAK,CAAC,MAAM,gBAAgB,SAAS,KAAK,IAAI,KAAK;YAC5D,iBAAiB,CACpB,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AAC1B,CAAC;AAED;;;;;;;;GAQG;AACH,KAAK,UAAU,UAAU,CACvB,IAAY,EACZ,OAAoB;IAEpB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/D,MAAM,GAAG,GAAG,GAAG,WAAW,IAAI,IAAI,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;IACvD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,qBAAqB,CAAC,CAAC;IAC5E,IAAI,CAAC;QACH,4DAA4D;QAC5D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC;YACnC,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,4BAA4B,GAAG,CAAC,MAAM,QAAQ,GAAG,EAAE,CAAC,CAAC;QACvE,CAAC;QACD,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAgB,CAAC;IAC3C,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;AACH,CAAC;AAED,uDAAuD;AACvD,SAAS,OAAO,CAAC,EAAuB,EAAE,EAAW;IACnD,IAAI,CAAC,EAAE;QAAE,OAAO;IAChB,IAAI,EAAE,EAAE,CAAC;QACP,EAAE,CAAC,eAAe,CAAC,kBAAkB,CAAC,CAAC;QACvC,EAAE,CAAC,YAAY,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC;QAC1C,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACvC,CAAC;SAAM,CAAC;QACN,EAAE,CAAC,eAAe,CAAC,oBAAoB,CAAC,CAAC;QACzC,EAAE,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;IAClC,CAAC;AACH,CAAC;AAED,4EAA4E;AAC5E,SAAS,YAAY,CAAC,CAAa;IACjC,OAAO,CACL,CAAC,CAAC,MAAM,KAAK,CAAC;QACd,CAAC,CAAC,CAAC,gBAAgB;QACnB,CAAC,CAAC,CAAC,OAAO;QACV,CAAC,CAAC,CAAC,OAAO;QACV,CAAC,CAAC,CAAC,QAAQ;QACX,CAAC,CAAC,CAAC,MAAM,CACV,CAAC;AACJ,CAAC;AAED,2EAA2E;AAC3E,SAAS,OAAO,CAAC,GAAQ;IACvB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,YAAY,CAAC,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;IAC5E,MAAM,IAAI,GAAG,GAAG,CAAC,eAAe,CAAC,CAAC;IAClC,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC;IAClD,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;IACnC,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;AACpC,CAAC;AAED;;;GAGG;AACH,MAAM,KAAM,SAAQ,WAAW;IACrB,IAAI,CAAe;IAE3B;;;OAGG;IACK,IAAI,GAAG,EAAE,CAAC;IAElB,iBAAiB;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CAAE,CAAC;QAExC,qEAAqE;QACrE,yEAAyE;QACzE,oEAAoE;QACpE,IAAI,CAAC,gBAAgB,CACnB,aAAa,EACb,CAAC,CAAQ,EAAE,EAAE;YACX,MAAM,MAAM,GAAG,CAAC,CAAC,MAAiB,CAAC;YACnC,MAAM,OAAO,GAAI,CAA6B,CAAC,MAAM,CAAC;YACtD,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAAE,CAAC,CAAC,eAAe,EAAE,CAAC;QACtD,CAAC,EACD,EAAE,OAAO,EAAE,IAAI,EAAE,CAClB,CAAC;QAEF,IAAI,CAAC,gBAAgB,CAAC,aAAa,EAAE,CAAC,CAAQ,EAAE,EAAE;YAChD,MAAM,OAAO,GAAI,CAA6B,CAAC,MAAM,CAAC;YACtD,oEAAoE;YACpE,oCAAoC;YACpC,MAAM,MAAM,GAAI,CAAC,CAAC,MAAyB,IAAI,SAAS,CAAC;YACzD,IAAI,OAAO,CAAC,KAAK,EAAE,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC;gBAClD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;gBAC7B,OAAO;YACT,CAAC;YACD,KAAK,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAClC,CAAC,CAAC,CAAC;QAEH,wEAAwE;QACxE,kDAAkD;QAClD,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAc,EAAE,EAAE;YACjD,MAAM,IAAI,GAAG,CAAC,CAAC,MAAwB,CAAC;YACxC,IAAI,CAAC,IAAI;gBAAE,OAAO;YAClB,MAAM,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC;YAC9B,MAAM,EAAE,GAAG,SAAS,EAAE,YAAY,CAAC,YAAY,CAAC;gBAC9C,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC;oBAC/B,CAAC,CAAC,IAAI;oBACN,CAAC,CAAC,IAAI,CAAC;YACX,IAAI,CAAC,EAAE;gBAAE,OAAO;YAChB,CAAC,CAAC,cAAc,EAAE,CAAC;YACnB,MAAM,CAAC,EAAE,CAAC,CAAC;QACb,CAAC,CAAC,CAAC;QAEH,uEAAuE;QACvE,qEAAqE;QACrE,mEAAmE;QACnE,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAQ,EAAE,EAAE;YAC1C,IAAI,CAAC,CAAC,gBAAgB;gBAAE,OAAO;YAC/B,KAAK,MAAM,EAAE,IAAI,UAAU,CAAC,CAAC,CAAC,MAAyB,CAAC,EAAE,CAAC;gBACzD,EAAE,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAAC;YACxC,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,+BAA+B;QAC/B,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAQ,EAAE,EAAE;YAC3C,MAAM,EAAE,GAAG,CAAC,CAAC,MAAwB,CAAC;YACtC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;gBAAE,OAAO;YACpE,MAAM,CAAC,EAAE,CAAC,CAAC;QACb,CAAC,CAAC,CAAC;QAEH,yEAAyE;QACzE,oEAAoE;QACpE,oDAAoD;QACpD,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAa,EAAE,EAAE;YAC/C,MAAM,MAAM,GAAG,CAAC,CAAC,MAAwB,CAAC;YAC1C,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,CAAC,IAAI,YAAY,GAAG,CAAC,CAAC;YAChD,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAAE,OAAO;YACjD,IAAI,EAAE,CAAC,SAAS,KAAK,MAAM,IAAI,SAAS,CAAC,EAAE,CAAC,IAAI,cAAc,CAAC,EAAE,CAAC,EAAE,CAAC;gBACnE,OAAO;YACT,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;gBAAE,OAAO;YAChC,CAAC,CAAC,cAAc,EAAE,CAAC;YACnB,MAAM,CAAC,EAAE,CAAC,CAAC;QACb,CAAC,CAAC,CAAC;QAEH,oEAAoE;QACpE,oEAAoE;QACpE,qEAAqE;QACrE,+CAA+C;QAC/C,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAa,EAAE,EAAE;YAC/C,MAAM,IAAI,GAAI,CAAC,CAAC,MAAyB,EAAE,OAAO,CAAC,KAAK,aAAa,GAAG,CAAC,CAAC;YAC1E,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;gBAAE,OAAO;YACtC,CAAC,CAAC,cAAc,EAAE,CAAC;YACnB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAE,IAA0B,CAAC,IAAI,CAAC,CAAC;YACvD,MAAM,GAAG,GAAQ,EAAE,CAAC;YACpB,KAAK,MAAM,IAAI,IAAI,IAAI,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;gBAC/D,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;YACjB,CAAC;YACD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY;gBAAE,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;YACjE,GAAG,CAAC,eAAe,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;YACrC,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACvB,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;QACvB,CAAC,CAAC,CAAC;QAEH,gBAAgB,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QAEzD,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;IACvB,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,IAAI,CAAC,OAAmB,EAAE,MAAgB;QACtD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACvB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1D,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO;QAC3B,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;QAClD,IAAI,GAAG,KAAK,SAAS;YAAE,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;;YAC5C,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QAChD,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC;IACrC,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,SAAS,CACrB,IAAY,EACZ,OAA+B,EAC/B,MAA2B;QAE3B,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACtB,IAAI,CAAC;YACH,OAAO,MAAM,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,+BAA+B,IAAI,GAAG,EAAE,GAAG,CAAC,CAAC;YAC3D,MAAM,EAAE,YAAY,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;YAC7C,OAAO,IAAI,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED;;;OAGG;IACK,MAAM,CAAC,OAAmB,EAAE,MAAgB;QAClD,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;YACvE,OAAO,CAAC,KAAK,CACX,YAAY,OAAO,CAAC,IAAI,UAAU,OAAO,CAAC,KAAK,qBAAqB;gBAClE,2BAA2B,CAC9B,CAAC;YACF,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,EAAE,YAAY,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC;QAC5D,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,EAAE,MAAM,CAAC;YAAE,KAAK,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5E,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,GAAQ,EAAE,IAAa;QACpC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QAC7C,IAAI,KAAK,IAAI,IAAI;YAAE,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;;YAC/C,OAAO,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;QACzC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;;;OAQG;IACK,KAAK,CAAC,MAAM,CAClB,IAAY,EACZ,IAAkB,EAClB,IAAoB,EACpB,MAAgB;QAEhB,IAAI,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,IAAI;YAAE,OAAO;QACpD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,EAAE,MAAM,EAAE,YAAY,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,CAAC;QAC1E,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC/B,IAAI,QAAQ;YAAE,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC;IAED;;;;;;;;;;;OAWG;IACK,KAAK,CAAC,QAAQ,CAAC,MAAgB;QACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC/B,IAAI,CAAC,QAAQ;YAAE,OAAO;QACtB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;QACjE,aAAa,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;IACnC,CAAC;IAED;;;OAGG;IACK,MAAM;QACZ,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC5C,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,QAAQ,IAAI,IAAI,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;YACnC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;YACjB,qEAAqE;YACrE,+DAA+D;YAC/D,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9D,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACxD,qEAAqE;QACrE,uDAAuD;QACvD,QAAQ,CAAC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QAC7B,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,CAAC,KAAK,CAAC,wBAAwB,IAAI,GAAG,CAAC,CAAC;YAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,UAAU,gBAAgB,GAAG,CAAC,CAAC;YACjE,IAAI,MAAM,IAAI,CAAE,MAA4B,CAAC,IAAI,EAAE,CAAC;gBACjD,MAA4B,CAAC,SAAS,EAAE,CAAC;YAC5C,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED;;;;;;;;;;OAUG;IACK,OAAO,CAAC,KAAoB;QAClC,IAAI,KAAK,KAAK,IAAI;YAAE,QAAQ,CAAC,KAAK,GAAG,KAAK,CAAC;QAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,UAAU,KAAK,SAAS,IAAI,CAAC,CAAC;QACvE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAChC,MAAM,GAAG,GAAQ,EAAE,CAAC;QACpB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,KAAK,EAAE,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;gBACnD,IAAI,EAAE,KAAK,KAAK;oBAAE,SAAS;gBAC3B,GAAG,CAAC,EAAE,CAAC,YAAY,CAAC,WAAW,CAAE,CAAC,GAAG,EAAE,CAAC;YAC1C,CAAC;QACH,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACjE,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;QACpB,CAAC;QACD,GAAG,CAAC,eAAe,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC;QACzC,GAAG,CAAC,qBAAqB,CAAC,GAAG,KAAK,CAAC;QACnC,GAAG,CAAC,uBAAuB,CAAC,GAAG,KAAK,KAAK,IAAI,CAAC;QAC9C,aAAa,CAAC,IAAI,EAAE;YAClB,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE;SAChE,CAAC,CAAC;IACL,CAAC;CACF;AAED,cAAc,CAAC,MAAM,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC","sourcesContent":["/// <reference lib=\"dom\" />\n\nimport {\n ATTR_COLUMN,\n ATTR_COLUMN_VALUE,\n ATTR_KEY_VALUE,\n ATTR_PAGE,\n ATTR_PAGE_TITLE,\n ATTR_QUERY,\n ATTR_REQUEST,\n ATTR_REQUEST_ERROR,\n ATTR_REQUEST_PENDING,\n ATTR_URL_LINK,\n ATTR_URL_PUSH,\n ATTR_URL_UNKNOWN,\n HUB_TAG_NAME,\n KIND_ROW,\n LB_ENDPOINT,\n LB_EVENT_NAME,\n LB_REQUEST_TIMEOUT_MS,\n LB_RESERVED_PREFIX,\n REQUEST_ROW_DELETE,\n REQUEST_ROW_INSERT,\n REQUEST_ROW_UPDATE,\n URL_COLUMN_PAGE_LABEL,\n URL_COLUMN_PAGE_UNKNOWN,\n URL_COLUMN_PATH,\n URL_QUERY,\n} from \"../core/lb-constants.js\";\nimport {\n isRowRequest,\n nextLocation,\n pageNameFor,\n type HubRequest,\n type HubResponse,\n type ResponseItem,\n type Row,\n} from \"../core/lb-types.js\";\nimport {\n applyResponse,\n isControl,\n isFormAssociated,\n within,\n type Control,\n} from \"./lb-apply.js\";\n\nexport { applyResponse, applyRow } from \"./lb-apply.js\";\n\n/** What each request Loadbare provides cannot be issued without. */\nconst NEEDS: Record<string, readonly (keyof HubRequest)[]> = {\n [REQUEST_ROW_INSERT]: [\"query\", \"values\"],\n [REQUEST_ROW_UPDATE]: [\"query\", \"key\", \"values\"],\n [REQUEST_ROW_DELETE]: [\"query\", \"key\"],\n};\n\n/** The nearest ancestor carrying `lb-query`. An element is not its own. */\nfunction queryOf(el: Element): Element | null {\n return el.parentElement?.closest(`[${ATTR_QUERY}]`) ?? null;\n}\n\n/**\n * A live row: an element the hub cloned from a row template. It carries a\n * key and no `lb-query`; an element a `row` lands on carries both.\n */\nconst LIVE_ROW = `[${ATTR_KEY_VALUE}]:not([${ATTR_QUERY}])`;\n\n/** The live row an element is, or is in. */\nfunction liveRowOf(el: Element): Element | null {\n return el.closest(LIVE_ROW);\n}\n\n/**\n * The key of the nearest ancestor row, when that row is of `query`: a live\n * row whose query it is, or the element a `row` of it landed on.\n */\nfunction keyFrom(el: Element, query: Element): string | undefined {\n // A live row is its own row; any other element's row is an ancestor.\n const row = el.matches(LIVE_ROW)\n ? el\n : el.parentElement?.closest(`[${ATTR_KEY_VALUE}]`);\n if (!row) return undefined;\n const ofQuery = row === query || queryOf(row) === query;\n return ofQuery ? (row.getAttribute(ATTR_KEY_VALUE) ?? undefined) : undefined;\n}\n\n/**\n * Every control whose form owner is `form`: HTML's own list, which holds a\n * control nested in the form or joined to it by the `form` attribute, and a\n * form-associated custom element as well as a native control.\n */\nfunction controlsOf(form: HTMLFormElement): Element[] {\n return [...(form.elements as unknown as Iterable<Element>)];\n}\n\n/**\n * The form owner of an element that has one, as HTML reports it on the\n * element's `form` property.\n */\nfunction formOwnerOf(el: Element): HTMLFormElement | null {\n const owner = (el as { form?: unknown }).form;\n return owner && (owner as Element).localName === \"form\"\n ? (owner as HTMLFormElement)\n : null;\n}\n\n/** The group a request gathers from. */\ninterface Group {\n controls: Element[];\n /** Set when the group is a form, so a successful insert resets it. */\n form?: HTMLFormElement;\n /** What the group is: the form or the live row. */\n root: Element;\n}\n\n/**\n * The controls a request gathers — see docs/reference/data-binding.md,\n * \"Gathering\":\n *\n * 1. the element carrying `lb-request` has `lb-column`: it alone;\n * 2. the element is a `<form>`: the controls whose form owner it is;\n * 3. the element is in a live row: the controls in that live row;\n * 4. otherwise: the controls whose form owner is the element's.\n *\n * A control whose nearest `lb-query` is inside the group belongs to a query\n * nested in it, and is skipped.\n */\nfunction groupFor(el: Element): Group | null {\n if (el.hasAttribute(ATTR_COLUMN)) return { controls: [el], root: el };\n if (el.localName === \"form\") {\n const form = el as HTMLFormElement;\n return { controls: controlsOf(form), form, root: form };\n }\n const live = liveRowOf(el);\n if (live) return { controls: within(live, `[${ATTR_COLUMN}]`), root: live };\n const form = formOwnerOf(el);\n if (form) return { controls: controlsOf(form), form, root: form };\n return null;\n}\n\n/** The gathered controls, with their values under their columns. */\nfunction gather(group: Group | null): {\n controls: Control[];\n values: Record<string, string>;\n} {\n const controls: Control[] = [];\n const values: Record<string, string> = {};\n for (const el of group?.controls ?? []) {\n const column = el.getAttribute(ATTR_COLUMN);\n if (column === null || !isControl(el)) continue;\n const nested = queryOf(el);\n if (nested && nested !== group!.root && group!.root.contains(nested)) {\n continue;\n }\n controls.push(el);\n values[column] = el.value;\n }\n return { controls, values };\n}\n\n/** The form each insert gathered from, until its round trip succeeds. */\nconst insertedFrom = new WeakMap<HubRequest, HTMLFormElement>();\n\n/**\n * Complete a request from where its element sits. Every request passes\n * through here, whoever issued it; a field it already carries is its\n * issuer's to decide and is left as it is.\n *\n * False when the request cannot be issued, having said why.\n */\nfunction complete(request: HubRequest | undefined, el: Element): boolean {\n if (!request?.name) {\n console.warn(`lb-hub: a request without a name, ignoring`, request);\n return false;\n }\n const { name } = request;\n if (name.startsWith(LB_RESERVED_PREFIX) && !isRowRequest(name)) {\n console.error(\n `lb-hub: '${name}' begins with '${LB_RESERVED_PREFIX}' but names no ` +\n `request Loadbare provides, ignoring`,\n );\n return false;\n }\n\n const group = groupFor(el);\n const { controls, values } = gather(group);\n if (request.values === undefined && controls.length > 0) {\n request.values = values;\n }\n\n // The request is for the nearest `lb-query` of what it gathered, or of the\n // element itself when it gathered nothing.\n const queries = new Set(controls.map(queryOf));\n if (queries.size > 1) {\n console.error(\n `lb-hub: '${name}' gathered controls of more than one query, not sent`,\n el,\n );\n return false;\n }\n const from = controls[0] ?? el;\n const query = controls.length > 0 ? [...queries][0]! : queryOf(el);\n if (query) {\n request.query ??= query.getAttribute(ATTR_QUERY)!;\n const key = keyFrom(from, query);\n if (key !== undefined) request.key ??= key;\n }\n\n if (name === REQUEST_ROW_INSERT && group?.form) {\n insertedFrom.set(request, group.form);\n }\n\n const needs = NEEDS[name];\n if (needs) {\n const missing = needs.filter(\n (field) =>\n request[field] === undefined ||\n (field === \"values\" && Object.keys(request.values!).length === 0),\n );\n if (missing.length > 0) {\n console.error(\n `lb-hub: '${name}' from <${el.localName}> has no ` +\n `${missing.join(\", \")}, not sent`,\n el,\n );\n return false;\n }\n }\n return true;\n}\n\n/**\n * Commit: dispatch the request an element names, for the hub to complete. A\n * commit on an element whose round trip is in flight is ignored.\n */\nfunction commit(el: Element): void {\n if (el.hasAttribute(ATTR_REQUEST_PENDING)) return;\n const detail: HubRequest = { name: el.getAttribute(ATTR_REQUEST)! };\n el.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));\n}\n\n/** A submit button with a form owner commits with its form, never on click. */\nfunction isSubmitButton(el: Element): boolean {\n const type = (el as HTMLInputElement).type as string;\n const submits =\n (el.localName === \"button\" && type === \"submit\") ||\n (el.localName === \"input\" && (type === \"submit\" || type === \"image\"));\n return submits && formOwnerOf(el) !== null;\n}\n\n/**\n * HTML's interactive content: elements a click already means something to.\n * HTML forbids nesting them inside `<a>` or `<button>` so a click means one\n * thing, and the hub draws the same line.\n */\nconst INTERACTIVE = [\n \"a[href]\",\n \"button\",\n \"input:not([type=hidden])\",\n \"select\",\n \"textarea\",\n \"label\",\n \"details\",\n \"iframe\",\n \"embed\",\n \"audio[controls]\",\n \"video[controls]\",\n \"img[usemap]\",\n].join(\", \");\n\n/**\n * Whether a click on `target` is the click of `el`, the element carrying\n * `lb-request` above it: no interactive content lies between them. A click on\n * a control, link or button inside `el` belongs to that element.\n */\nfunction clicks(target: Element, el: Element): boolean {\n for (let node: Element | null = target; node && node !== el;) {\n if (node.matches(INTERACTIVE) || isFormAssociated(node)) return false;\n node = node.parentElement;\n }\n return true;\n}\n\n/** The page file's title, or its stub when it has none. */\nfunction titleOf(template: HTMLTemplateElement | null, page: string): string {\n return template?.getAttribute(ATTR_PAGE_TITLE) || page;\n}\n\n/**\n * The `<template>` a page ships in. The builder guarantees one per stub\n * (build/locations.ts refuses two page files sharing one), so a second match\n * here means the document was assembled by something else.\n */\nfunction pageHostFor(page: string): HTMLTemplateElement | null {\n const hosts: HTMLTemplateElement[] = [];\n for (const el of document.querySelectorAll(`template[${ATTR_PAGE}]`)) {\n if (el.getAttribute(ATTR_PAGE) === page) {\n hosts.push(el as HTMLTemplateElement);\n }\n }\n if (hosts.length > 1) {\n console.error(\n `lb-hub: ${hosts.length} pages carry ${ATTR_PAGE}=\"${page}\", ` +\n `using the first`,\n );\n }\n return hosts[0] ?? null;\n}\n\n/**\n * One round trip against the client-side deadline. Throws on a non-ok\n * response, a network failure or a timeout, so a caller can tell a failed\n * request from an empty answer.\n *\n * The stub names the path below the endpoint and the query string the\n * browser is showing rides after it verbatim, so every round trip reaches\n * the server with the query parms in hand.\n */\nasync function fetchItems(\n page: string,\n request?: HubRequest,\n): Promise<HubResponse> {\n const path = page.split(\"/\").map(encodeURIComponent).join(\"/\");\n const url = `${LB_ENDPOINT}/${path}${location.search}`;\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), LB_REQUEST_TIMEOUT_MS);\n try {\n // An empty body is how the hub asks for the page's queries.\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(request ?? {}),\n signal: controller.signal,\n });\n if (!res.ok) {\n throw new Error(`lb-hub: server responded ${res.status} for ${url}`);\n }\n return (await res.json()) as HubResponse;\n } finally {\n clearTimeout(timeout);\n }\n}\n\n/** An element's round trip is in flight, or is not. */\nfunction pending(el: Element | undefined, on: boolean): void {\n if (!el) return;\n if (on) {\n el.removeAttribute(ATTR_REQUEST_ERROR);\n el.setAttribute(ATTR_REQUEST_PENDING, \"\");\n el.setAttribute(\"aria-busy\", \"true\");\n } else {\n el.removeAttribute(ATTR_REQUEST_PENDING);\n el.removeAttribute(\"aria-busy\");\n }\n}\n\n/** A plain primary click: no other button, no modifier, not yet handled. */\nfunction isPlainClick(e: MouseEvent): boolean {\n return (\n e.button === 0 &&\n !e.defaultPrevented &&\n !e.metaKey &&\n !e.ctrlKey &&\n !e.shiftKey &&\n !e.altKey\n );\n}\n\n/** The URL a row for `lb-url` leads to, by the rule the server follows. */\nfunction nextUrl(row: Row): { url: URL; moved: boolean } {\n const url = new URL(location.href);\n const next = nextLocation(pageNameFor(url.pathname), url.searchParams, row);\n const path = row[URL_COLUMN_PATH];\n if (typeof path === \"string\") url.pathname = path;\n url.search = next.parms.toString();\n return { url, moved: next.moved };\n}\n\n/**\n * The hub is a singleton outside <main>. It survives every page change,\n * so it needs no id to disambiguate it.\n */\nclass LbHub extends HTMLElement {\n private main!: HTMLElement;\n\n /**\n * The stub of the page in <main>. A page declares its own requests, so a\n * request says which page it came from, on the path below the endpoint.\n */\n private page = \"\";\n\n connectedCallback() {\n this.main = this.querySelector(\"main\")!;\n\n // Completed in the capture phase, on the way down, so every ancestor\n // that sees the request on its way back up sees it whole. A request that\n // cannot be issued is stopped here, before anything sees it at all.\n this.addEventListener(\n LB_EVENT_NAME,\n (e: Event) => {\n const origin = e.target as Element;\n const request = (e as CustomEvent<HubRequest>).detail;\n if (!complete(request, origin)) e.stopPropagation();\n },\n { capture: true },\n );\n\n this.addEventListener(LB_EVENT_NAME, (e: Event) => {\n const request = (e as CustomEvent<HubRequest>).detail;\n // The element that issued the request, not e.currentTarget (the hub\n // itself, since the event bubbles).\n const origin = (e.target as Element | null) ?? undefined;\n if (request.query?.startsWith(LB_RESERVED_PREFIX)) {\n this.answer(request, origin);\n return;\n }\n void this.send(request, origin);\n });\n\n // A form commits on submit. The submitter's own lb-request replaces the\n // form's, the way `formaction` replaces `action`.\n this.addEventListener(\"submit\", (e: SubmitEvent) => {\n const form = e.target as Element | null;\n if (!form) return;\n const submitter = e.submitter;\n const el = submitter?.hasAttribute(ATTR_REQUEST)\n ? submitter\n : form.hasAttribute(ATTR_REQUEST)\n ? form\n : null;\n if (!el) return;\n e.preventDefault();\n commit(el);\n });\n\n // A reset returns a form's controls to their defaults, so none of them\n // shows what landed any longer. The browser resets the controls; the\n // hub drops its own stamp from the same controls a submit gathers.\n this.addEventListener(\"reset\", (e: Event) => {\n if (e.defaultPrevented) return;\n for (const el of controlsOf(e.target as HTMLFormElement)) {\n el.removeAttribute(ATTR_COLUMN_VALUE);\n }\n });\n\n // A control commits on change.\n this.addEventListener(\"change\", (e: Event) => {\n const el = e.target as Element | null;\n if (!el || !el.hasAttribute(ATTR_REQUEST) || !isControl(el)) return;\n commit(el);\n });\n\n // Anything else commits on click. A form commits on submit, a control on\n // change, and a submit button with its form. A click on interactive\n // content inside the element is that content's own.\n this.addEventListener(\"click\", (e: MouseEvent) => {\n const target = e.target as Element | null;\n const el = target?.closest(`[${ATTR_REQUEST}]`);\n if (!target || !el || !this.contains(el)) return;\n if (el.localName === \"form\" || isControl(el) || isSubmitButton(el)) {\n return;\n }\n if (!clicks(target, el)) return;\n e.preventDefault();\n commit(el);\n });\n\n // A link that opted in. Everything else is a real link, and so is a\n // click that opens a new tab or window. The link's URL is a row for\n // lb-url: its path, its query parms, and every other parm emptied. A\n // link pushes a history entry, as a link does.\n this.addEventListener(\"click\", (e: MouseEvent) => {\n const link = (e.target as Element | null)?.closest(`a[${ATTR_URL_LINK}]`);\n if (!link || !isPlainClick(e)) return;\n e.preventDefault();\n const href = new URL((link as HTMLAnchorElement).href);\n const row: Row = {};\n for (const name of new URLSearchParams(location.search).keys()) {\n row[name] = \"\";\n }\n for (const [name, value] of href.searchParams) row[name] = value;\n row[URL_COLUMN_PATH] = href.pathname;\n this.moveTo(row, true);\n void this.navigate();\n });\n\n addEventListener(\"popstate\", () => void this.navigate());\n\n void this.navigate();\n }\n\n /**\n * Send a request to the server and land its answer. An answer naming\n * `lb-url` moves the URL first; the server has already loaded the page\n * there, in the same round trip.\n */\n private async send(request: HubRequest, origin?: Element): Promise<void> {\n const page = this.page;\n const items = await this.roundTrip(page, request, origin);\n if (items === null) return;\n const url = items.find((item) => item.query === URL_QUERY);\n const load = items.filter((item) => item !== url);\n if (url === undefined) applyResponse(this, load);\n else await this.follow(page, url, load, origin);\n insertedFrom.get(request)?.reset();\n }\n\n /**\n * One round trip for a page: a request, or with none a page load. The\n * element that issued it carries `lb-request-pending` while it is in\n * flight, and `lb-request-error` when it fails. Null on failure.\n */\n private async roundTrip(\n page: string,\n request: HubRequest | undefined,\n origin: Element | undefined,\n ): Promise<HubResponse | null> {\n pending(origin, true);\n try {\n return await fetchItems(page, request);\n } catch (err) {\n console.error(`lb-hub: request failed for '${page}'`, err);\n origin?.setAttribute(ATTR_REQUEST_ERROR, \"\");\n return null;\n } finally {\n pending(origin, false);\n }\n }\n\n /**\n * Answer a request for a query the hub serves, with no round trip. The\n * hub serves `lb-url`, and updates it.\n */\n private answer(request: HubRequest, origin?: Element): void {\n if (request.query !== URL_QUERY || request.name !== REQUEST_ROW_UPDATE) {\n console.error(\n `lb-hub: '${request.name}' for '${request.query}' is not a request ` +\n `the hub answers, ignoring`,\n );\n return;\n }\n const pushed = origin?.hasAttribute(ATTR_URL_PUSH) ?? false;\n if (this.moveTo(request.values ?? {}, pushed)) void this.navigate(origin);\n }\n\n /**\n * Write a row for `lb-url` into the URL. A new page pushes a history\n * entry, and so does `push`; a change of query parms replaces the entry.\n * False when the URL is already the row's.\n */\n private moveTo(row: Row, push: boolean): boolean {\n const { url, moved } = nextUrl(row);\n if (url.href === location.href) return false;\n if (moved || push) history.pushState(null, \"\", url);\n else history.replaceState(null, \"\", url);\n return true;\n }\n\n /**\n * A server answer named `lb-url`: write the URL, then land the page the\n * server loaded there. A load that did not come back is asked for again,\n * without marking the element that issued the request: its write\n * succeeded, and an error would invite sending it twice.\n *\n * `page` is where the request was sent from. A user who left it while the\n * request was in flight is on a URL that is not the server's to change.\n */\n private async follow(\n page: string,\n item: ResponseItem,\n load: ResponseItem[],\n origin?: Element,\n ): Promise<void> {\n if (pageNameFor(location.pathname) !== page) return;\n this.moveTo(item.row ?? {}, origin?.hasAttribute(ATTR_URL_PUSH) ?? false);\n if (load.length === 0) return this.navigate();\n const template = this.arrive();\n if (template) applyResponse(this, load);\n }\n\n /**\n * Load the page at the URL the browser is showing. Every way of arriving\n * comes through here — a cold load, a link, Back, a new query parm — so\n * each is the same page load and a URL always shows the same thing.\n *\n * Entering a page is the one operation that replaces what <main> holds.\n * Staying on it, with only the query parms changed, keeps what it has: the\n * queries re-run and land by key, so rows that come back keep their place.\n *\n * `origin` is the element whose request changed the URL, if one did, and\n * carries the round trip's pending and error state.\n */\n private async navigate(origin?: Element): Promise<void> {\n const template = this.arrive();\n if (!template) return;\n const items = await this.roundTrip(this.page, undefined, origin);\n applyResponse(this, items ?? []);\n }\n\n /**\n * Show the page the path names, and land `lb-url`. Null when no page's\n * stub matches the path.\n */\n private arrive(): HTMLTemplateElement | null {\n const page = pageNameFor(location.pathname);\n const template = pageHostFor(page);\n if (template && page !== this.page) {\n this.page = page;\n // The template is already in the document, so the page's markup goes\n // in before its data arrives rather than leaving <main> empty.\n this.main.replaceChildren(template.content.cloneNode(true));\n }\n this.landUrl(template ? titleOf(template, page) : null);\n // An app that ships `<body hidden>` (docs/reference/chrome.md) stays\n // invisible until there is something coherent to show.\n document.body.hidden = false;\n if (!template) {\n console.error(`lb-hub: no page for '${page}'`);\n const dialog = this.querySelector(`dialog[${ATTR_URL_UNKNOWN}]`);\n if (dialog && !(dialog as HTMLDialogElement).open) {\n (dialog as HTMLDialogElement).showModal();\n }\n }\n return template;\n }\n\n /**\n * The hub's own query: one row, keyed by the path, whose other columns are\n * the query parms.\n *\n * The one exception to landing. A row sets the columns it names and\n * leaves the rest as they were, and a URL names only the parms it carries.\n * So the row also names, as empty, every column an element under\n * `lb-query=\"lb-url\"` shows: a control then shows what the address bar\n * says after Back as well as after a reload, and never a parm the URL has\n * dropped.\n */\n private landUrl(label: string | null): void {\n if (label !== null) document.title = label;\n const scopes = this.querySelectorAll(`[${ATTR_QUERY}=\"${URL_QUERY}\"]`);\n if (scopes.length === 0) return;\n const row: Row = {};\n for (const scope of scopes) {\n for (const el of within(scope, `[${ATTR_COLUMN}]`)) {\n if (el === scope) continue;\n row[el.getAttribute(ATTR_COLUMN)!] = \"\";\n }\n }\n for (const [name, value] of new URLSearchParams(location.search)) {\n row[name] = value;\n }\n row[URL_COLUMN_PATH] = location.pathname;\n row[URL_COLUMN_PAGE_LABEL] = label;\n row[URL_COLUMN_PAGE_UNKNOWN] = label === null;\n applyResponse(this, [\n { query: URL_QUERY, kind: KIND_ROW, key: URL_COLUMN_PATH, row },\n ]);\n }\n}\n\ncustomElements.define(HUB_TAG_NAME, LbHub);\n"]}
@@ -8,11 +8,11 @@
8
8
  *
9
9
  * One endpoint, and no second:
10
10
  *
11
- * POST /lb/<page>?<query string>
12
- * an empty body asks for the page's whole query
13
- * set; a body carrying an action runs that action
14
- * and answers with its refresh set, or with the
15
- * page loaded at the query parms it wrote
11
+ * POST /lb/<stub>?<query string>
12
+ * an empty body asks for the page's queries; a body
13
+ * carrying a request runs its handler and answers
14
+ * with the refresh set, or with the page loaded at
15
+ * the new URL the handler returned
16
16
  *
17
17
  * The page rides on the path rather than in the body, which is what lets the
18
18
  * operation set stay closed. The query string is the one the browser is
@@ -1 +1 @@
1
- {"version":3,"file":"lb-express.d.ts","sourceRoot":"","sources":["../../server/lb-express.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAgB,EAEd,KAAK,OAAO,EAEZ,KAAK,MAAM,EACZ,MAAM,SAAS,CAAC;AAcjB,OAAO,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEtD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,eAAe,KAAK,UAAU,CAAC;AAE9E,wBAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,UAAU,GAAG,MAAM,CA2HlE"}
1
+ {"version":3,"file":"lb-express.d.ts","sourceRoot":"","sources":["../../server/lb-express.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAgB,EAEd,KAAK,OAAO,EAEZ,KAAK,MAAM,EACZ,MAAM,SAAS,CAAC;AAOjB,OAAO,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEtD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,eAAe,KAAK,UAAU,CAAC;AAE9E,wBAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,UAAU,GAAG,MAAM,CA0FlE"}
@@ -8,11 +8,11 @@
8
8
  *
9
9
  * One endpoint, and no second:
10
10
  *
11
- * POST /lb/<page>?<query string>
12
- * an empty body asks for the page's whole query
13
- * set; a body carrying an action runs that action
14
- * and answers with its refresh set, or with the
15
- * page loaded at the query parms it wrote
11
+ * POST /lb/<stub>?<query string>
12
+ * an empty body asks for the page's queries; a body
13
+ * carrying a request runs its handler and answers
14
+ * with the refresh set, or with the page loaded at
15
+ * the new URL the handler returned
16
16
  *
17
17
  * The page rides on the path rather than in the body, which is what lets the
18
18
  * operation set stay closed. The query string is the one the browser is
@@ -22,8 +22,8 @@
22
22
  * name is reserved.
23
23
  */
24
24
  import express from "express";
25
- import { ACTION_ROW_DELETE, ACTION_ROW_INSERT, ACTION_ROW_UPDATE, LB_ENDPOINT, QUERY_PARMS_ROW, } from "../core/lb-constants.js";
26
- import { isOperation, withQueryParms, } from "../core/lb-types.js";
25
+ import { LB_ENDPOINT, URL_QUERY } from "../core/lb-constants.js";
26
+ import { nextLocation, } from "../core/lb-types.js";
27
27
  export function hubRoutes(hub, contextFor) {
28
28
  const router = express.Router();
29
29
  // Mounted here rather than on the app so that Hub's need for a parsed
@@ -39,80 +39,49 @@ export function hubRoutes(hub, contextFor) {
39
39
  const parms = new URL(req.originalUrl, "http://localhost").searchParams;
40
40
  const ctx = contextFor(req, parms);
41
41
  // No body at all is the page load: the browser is asking for this
42
- // page's whole query set, which is every request it makes that names no
43
- // action. A body that carries something but not an action is malformed.
42
+ // page's queries. A body that carries something but no name is
43
+ // malformed.
44
44
  if (request === undefined || Object.keys(request).length === 0) {
45
45
  res.json(await hub.dataForPage(page, ctx));
46
46
  return;
47
47
  }
48
- if (!request.action) {
49
- console.warn(`loadbare: request without an action`);
50
- res.status(400).json({});
48
+ if (!request.name) {
49
+ console.warn(`loadbare: request without a name`);
50
+ res.status(400).json([]);
51
51
  return;
52
52
  }
53
- // A thrown error is how application code (a query, an action, a CRUD
54
- // op) says "this request is invalid" or hits an unexpected failure —
55
- // it resolves to a real response rather than an unhandled rejection, so
56
- // the browser side has something to catch.
57
- let data;
53
+ // A thrown error is how application code (a query, a handler) says
54
+ // "this request is invalid" or hits an unexpected failure — it resolves
55
+ // to a real response rather than an unhandled rejection, so the browser
56
+ // side has something to catch.
57
+ let items;
58
58
  try {
59
- // The reserved prefix is the whole of the discriminant: a name so
60
- // prefixed is one of the three operations, and anything else is a name
61
- // the page declared. Nothing else tells them apart, here or anywhere.
62
- if (!isOperation(request)) {
63
- const { action, list, row, key, cell, value } = request;
64
- data = await hub.runAction(page, action, { list, row, key, cell, value }, ctx);
65
- }
66
- else {
67
- switch (request.action) {
68
- case ACTION_ROW_DELETE: {
69
- const { list, key } = request;
70
- data = await hub.runRowDelete(page, { list, key }, ctx);
71
- break;
72
- }
73
- case ACTION_ROW_INSERT: {
74
- const { list, values } = request;
75
- data = await hub.runRowInsert(page, { list, values }, ctx);
76
- break;
77
- }
78
- case ACTION_ROW_UPDATE: {
79
- const { list, key, values } = request;
80
- data = await hub.runRowUpdate(page, { list, key, values }, ctx);
81
- break;
82
- }
83
- default:
84
- console.warn(`loadbare: operation '${request.action}' not implemented`);
85
- res.status(400).json({});
86
- return;
87
- }
88
- }
59
+ items = await hub.runRequest(page, request, ctx);
89
60
  }
90
61
  catch (err) {
91
- console.error(`loadbare: '${request.action}' threw`, err);
92
- res.status(500).json({});
62
+ console.error(`loadbare: '${request.name}' threw`, err);
63
+ res.status(500).json([]);
93
64
  return;
94
65
  }
95
- const written = data[QUERY_PARMS_ROW];
96
- if (written === undefined) {
97
- res.json(data);
66
+ const moved = items.find((item) => item.query === URL_QUERY);
67
+ if (moved === undefined) {
68
+ res.json(items);
98
69
  return;
99
70
  }
100
- // The request changed the query parms, so the page loads at them, as a
101
- // cold load of the new URL would. The write has already happened: from
102
- // here a failure must not read as the write failing, or the user would
103
- // send it again. The parms go back alone instead, and the hub, writing
104
- // them into the URL with nothing to land, loads the page itself.
71
+ // The handler returned a new row for lb-url, so the page loads there, as
72
+ // a cold load of the new URL would. The handler has already run: from
73
+ // here a failure must not read as the request failing, or the user would
74
+ // send it again. The item goes back alone instead, and the hub, writing
75
+ // the URL with nothing to land, loads the page itself.
76
+ const { page: next, parms: nextParms } = nextLocation(page, parms, moved.row ?? {});
105
77
  try {
106
- const next = contextFor(req, withQueryParms(parms, written));
107
- res.json({
108
- ...(await hub.dataForPage(page, next)),
109
- [QUERY_PARMS_ROW]: written,
110
- });
78
+ const load = await hub.dataForPage(next, contextFor(req, nextParms));
79
+ res.json([moved, ...load]);
111
80
  }
112
81
  catch (err) {
113
- console.error(`loadbare: '${request.action}' wrote query parms, and loading ` +
114
- `'${page}' at them threw`, err);
115
- res.json({ [QUERY_PARMS_ROW]: written });
82
+ console.error(`loadbare: '${request.name}' moved the URL, and loading ` +
83
+ `'${next}' there threw`, err);
84
+ res.json([moved]);
116
85
  }
117
86
  });
118
87
  // A body that never parsed is refused like any other malformed request,
@@ -126,7 +95,7 @@ export function hubRoutes(hub, contextFor) {
126
95
  return;
127
96
  }
128
97
  console.warn(`loadbare: request body is not JSON`);
129
- res.status(400).json({});
98
+ res.status(400).json([]);
130
99
  });
131
100
  return router;
132
101
  }
@@ -1 +1 @@
1
- {"version":3,"file":"lb-express.js","sourceRoot":"","sources":["../../server/lb-express.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The Express adapter — the standard hand-off from an Express router to a\n * Hub built by createHub(). Every Hub application wires this same router,\n * which is why it ships from here rather than being copied into each one.\n *\n * Express itself is a peer dependency: nothing outside this file imports it,\n * so an application that hosts Hub some other way never pays for it.\n *\n * One endpoint, and no second:\n *\n * POST /lb/<page>?<query string>\n * an empty body asks for the page's whole query\n * set; a body carrying an action runs that action\n * and answers with its refresh set, or with the\n * page loaded at the query parms it wrote\n *\n * The page rides on the path rather than in the body, which is what lets the\n * operation set stay closed. The query string is the one the browser is\n * showing, verbatim, and `contextFor` is handed its parms. What a parm means\n * is the application's: nothing here reads one. Every hub call is a POST, so\n * an application's own GET routes never collide with this one and no page\n * name is reserved.\n */\n\nimport express, {\n type NextFunction,\n type Request,\n type Response,\n type Router,\n} from \"express\";\nimport {\n ACTION_ROW_DELETE,\n ACTION_ROW_INSERT,\n ACTION_ROW_UPDATE,\n LB_ENDPOINT,\n QUERY_PARMS_ROW,\n} from \"../core/lb-constants.js\";\nimport {\n isOperation,\n withQueryParms,\n type HubData,\n type HubRequest,\n} from \"../core/lb-types.js\";\nimport type { Hub, HubContext } from \"./lb-server.js\";\n\n/**\n * Building the context is the application's job — it is where an\n * authenticated database handle comes from — so it is injected rather than\n * assumed. Whatever established identity has already run by the time this\n * router is reached; where that happens is the application's ordering\n * decision, not Hub's.\n *\n * The query parms arrive as `parms`, and are read there rather than off\n * `req.query`. The two agree except after a request that wrote parms: the\n * page is then loaded at the new query string in the same round trip, and\n * this is called a second time for that load, with the same `req` and the\n * new `parms`. So it must be safe to call twice for one request, and a\n * write must be visible to the second context by the time its `run`\n * returns.\n *\n * Parms are user input like any other, typed into an address bar or mailed\n * in a link, and are validated where they are read.\n */\nexport type ContextFor = (req: Request, parms: URLSearchParams) => HubContext;\n\nexport function hubRoutes(hub: Hub, contextFor: ContextFor): Router {\n const router = express.Router();\n\n // Mounted here rather than on the app so that Hub's need for a parsed\n // body is stated where the routes that need it are.\n router.use(express.json());\n\n router.post(`${LB_ENDPOINT}/*page`, async (req, res) => {\n // A page name may hold a slash, so the path below the endpoint is\n // gathered whole.\n const page = (req.params.page as string[]).join(\"/\");\n const request = req.body as HubRequest | undefined;\n // Parsed here rather than taken from req.query, so that the parms a\n // context is built from are the same kind of thing on both calls.\n const parms = new URL(req.originalUrl, \"http://localhost\").searchParams;\n const ctx = contextFor(req, parms);\n\n // No body at all is the page load: the browser is asking for this\n // page's whole query set, which is every request it makes that names no\n // action. A body that carries something but not an action is malformed.\n if (request === undefined || Object.keys(request).length === 0) {\n res.json(await hub.dataForPage(page, ctx));\n return;\n }\n\n if (!request.action) {\n console.warn(`loadbare: request without an action`);\n res.status(400).json({});\n return;\n }\n\n // A thrown error is how application code (a query, an action, a CRUD\n // op) says \"this request is invalid\" or hits an unexpected failure —\n // it resolves to a real response rather than an unhandled rejection, so\n // the browser side has something to catch.\n let data: HubData;\n try {\n // The reserved prefix is the whole of the discriminant: a name so\n // prefixed is one of the three operations, and anything else is a name\n // the page declared. Nothing else tells them apart, here or anywhere.\n if (!isOperation(request)) {\n const { action, list, row, key, cell, value } = request;\n data = await hub.runAction(\n page,\n action,\n { list, row, key, cell, value },\n ctx,\n );\n } else {\n switch (request.action) {\n case ACTION_ROW_DELETE: {\n const { list, key } = request;\n data = await hub.runRowDelete(page, { list, key }, ctx);\n break;\n }\n case ACTION_ROW_INSERT: {\n const { list, values } = request;\n data = await hub.runRowInsert(page, { list, values }, ctx);\n break;\n }\n case ACTION_ROW_UPDATE: {\n const { list, key, values } = request;\n data = await hub.runRowUpdate(page, { list, key, values }, ctx);\n break;\n }\n default:\n console.warn(\n `loadbare: operation '${(request as HubRequest).action}' not implemented`,\n );\n res.status(400).json({});\n return;\n }\n }\n } catch (err) {\n console.error(`loadbare: '${request.action}' threw`, err);\n res.status(500).json({});\n return;\n }\n\n const written = data[QUERY_PARMS_ROW] as Record<string, string> | undefined;\n if (written === undefined) {\n res.json(data);\n return;\n }\n // The request changed the query parms, so the page loads at them, as a\n // cold load of the new URL would. The write has already happened: from\n // here a failure must not read as the write failing, or the user would\n // send it again. The parms go back alone instead, and the hub, writing\n // them into the URL with nothing to land, loads the page itself.\n try {\n const next = contextFor(req, withQueryParms(parms, written));\n res.json({\n ...(await hub.dataForPage(page, next)),\n [QUERY_PARMS_ROW]: written,\n });\n } catch (err) {\n console.error(\n `loadbare: '${request.action}' wrote query parms, and loading ` +\n `'${page}' at them threw`,\n err,\n );\n res.json({ [QUERY_PARMS_ROW]: written });\n }\n });\n\n // A body that never parsed is refused like any other malformed request,\n // rather than falling through to the application's error handler, which\n // would print a stack and call it a server failure. Only for the hub's own\n // path: express.json() above sees every request, and a bad body sent to one\n // of the application's routes is the application's to answer.\n router.use(\n `${LB_ENDPOINT}/*page`,\n (err: unknown, _req: Request, res: Response, next: NextFunction) => {\n if ((err as { type?: string }).type !== \"entity.parse.failed\") {\n next(err);\n return;\n }\n console.warn(`loadbare: request body is not JSON`);\n res.status(400).json({});\n },\n );\n\n return router;\n}\n"]}
1
+ {"version":3,"file":"lb-express.js","sourceRoot":"","sources":["../../server/lb-express.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The Express adapter — the standard hand-off from an Express router to a\n * Hub built by createHub(). Every Hub application wires this same router,\n * which is why it ships from here rather than being copied into each one.\n *\n * Express itself is a peer dependency: nothing outside this file imports it,\n * so an application that hosts Hub some other way never pays for it.\n *\n * One endpoint, and no second:\n *\n * POST /lb/<stub>?<query string>\n * an empty body asks for the page's queries; a body\n * carrying a request runs its handler and answers\n * with the refresh set, or with the page loaded at\n * the new URL the handler returned\n *\n * The page rides on the path rather than in the body, which is what lets the\n * operation set stay closed. The query string is the one the browser is\n * showing, verbatim, and `contextFor` is handed its parms. What a parm means\n * is the application's: nothing here reads one. Every hub call is a POST, so\n * an application's own GET routes never collide with this one and no page\n * name is reserved.\n */\n\nimport express, {\n type NextFunction,\n type Request,\n type Response,\n type Router,\n} from \"express\";\nimport { LB_ENDPOINT, URL_QUERY } from \"../core/lb-constants.js\";\nimport {\n nextLocation,\n type HubRequest,\n type HubResponse,\n} from \"../core/lb-types.js\";\nimport type { Hub, HubContext } from \"./lb-server.js\";\n\n/**\n * Building the context is the application's job — it is where an\n * authenticated database handle comes from — so it is injected rather than\n * assumed. Whatever established identity has already run by the time this\n * router is reached; where that happens is the application's ordering\n * decision, not Hub's.\n *\n * The query parms arrive as `parms`, and are read there rather than off\n * `req.query`. The two agree except after a request that wrote parms: the\n * page is then loaded at the new query string in the same round trip, and\n * this is called a second time for that load, with the same `req` and the\n * new `parms`. So it must be safe to call twice for one request, and a\n * write must be visible to the second context by the time its `run`\n * returns.\n *\n * Parms are user input like any other, typed into an address bar or mailed\n * in a link, and are validated where they are read.\n */\nexport type ContextFor = (req: Request, parms: URLSearchParams) => HubContext;\n\nexport function hubRoutes(hub: Hub, contextFor: ContextFor): Router {\n const router = express.Router();\n\n // Mounted here rather than on the app so that Hub's need for a parsed\n // body is stated where the routes that need it are.\n router.use(express.json());\n\n router.post(`${LB_ENDPOINT}/*page`, async (req, res) => {\n // A page name may hold a slash, so the path below the endpoint is\n // gathered whole.\n const page = (req.params.page as string[]).join(\"/\");\n const request = req.body as HubRequest | undefined;\n // Parsed here rather than taken from req.query, so that the parms a\n // context is built from are the same kind of thing on both calls.\n const parms = new URL(req.originalUrl, \"http://localhost\").searchParams;\n const ctx = contextFor(req, parms);\n\n // No body at all is the page load: the browser is asking for this\n // page's queries. A body that carries something but no name is\n // malformed.\n if (request === undefined || Object.keys(request).length === 0) {\n res.json(await hub.dataForPage(page, ctx));\n return;\n }\n\n if (!request.name) {\n console.warn(`loadbare: request without a name`);\n res.status(400).json([]);\n return;\n }\n\n // A thrown error is how application code (a query, a handler) says\n // \"this request is invalid\" or hits an unexpected failure — it resolves\n // to a real response rather than an unhandled rejection, so the browser\n // side has something to catch.\n let items: HubResponse;\n try {\n items = await hub.runRequest(page, request, ctx);\n } catch (err) {\n console.error(`loadbare: '${request.name}' threw`, err);\n res.status(500).json([]);\n return;\n }\n\n const moved = items.find((item) => item.query === URL_QUERY);\n if (moved === undefined) {\n res.json(items);\n return;\n }\n // The handler returned a new row for lb-url, so the page loads there, as\n // a cold load of the new URL would. The handler has already run: from\n // here a failure must not read as the request failing, or the user would\n // send it again. The item goes back alone instead, and the hub, writing\n // the URL with nothing to land, loads the page itself.\n const { page: next, parms: nextParms } = nextLocation(\n page,\n parms,\n moved.row ?? {},\n );\n try {\n const load = await hub.dataForPage(next, contextFor(req, nextParms));\n res.json([moved, ...load]);\n } catch (err) {\n console.error(\n `loadbare: '${request.name}' moved the URL, and loading ` +\n `'${next}' there threw`,\n err,\n );\n res.json([moved]);\n }\n });\n\n // A body that never parsed is refused like any other malformed request,\n // rather than falling through to the application's error handler, which\n // would print a stack and call it a server failure. Only for the hub's own\n // path: express.json() above sees every request, and a bad body sent to one\n // of the application's routes is the application's to answer.\n router.use(\n `${LB_ENDPOINT}/*page`,\n (err: unknown, _req: Request, res: Response, next: NextFunction) => {\n if ((err as { type?: string }).type !== \"entity.parse.failed\") {\n next(err);\n return;\n }\n console.warn(`loadbare: request body is not JSON`);\n res.status(400).json([]);\n },\n );\n\n return router;\n}\n"]}