@loadbare/app 0.19.0 → 0.19.1

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.
@@ -0,0 +1,250 @@
1
+ # Names
2
+
3
+ Every name Loadbare/app owns, in one place. Each row links to the section
4
+ that defines it, and the definition lives there and only there.
5
+
6
+ An application names nothing with the `lb-` prefix: no attribute, query,
7
+ column, request, command, custom element or event. A name Loadbare has not
8
+ defined today it may define tomorrow. `createHub` refuses at startup a page
9
+ that declares a query, a handler or a `crud` entry whose name begins with
10
+ `lb-`, and the builder refuses the rest; see
11
+ [The markup checks](./data-binding.md#the-markup-checks).
12
+
13
+ ## Builder flags
14
+
15
+ | Flag | Default | Names | Defined in |
16
+ | ---------- | ------- | ----------------------------------------- | --------------------------------------------------- |
17
+ | `--src` | `src` | The application's own tree, scanned whole | [Running the builder](./builder.md#running-the-builder) |
18
+ | `--out` | `dist` | Where the builder writes | [Running the builder](./builder.md#running-the-builder) |
19
+ | `--watch` | off | Rebuild on change under `--src` | [Running the builder](./builder.md#running-the-builder) |
20
+ | `--minify` | off | Minify `client.js` and `app.css` | [Running the builder](./builder.md#running-the-builder) |
21
+
22
+ ## Reserved file names
23
+
24
+ A file so named carries its meaning wherever it sits in the `--src` tree,
25
+ so an application uses none of these names for anything else.
26
+
27
+ | File | How many | Holds | Defined in |
28
+ | ----------------------- | ------------------------------ | -------------------------------------------------------- | --------------------------------------------------- |
29
+ | `chrome.html` | Exactly one | The application's one HTML document | [chrome.html](./chrome.md) |
30
+ | `imports.ts` | Zero or one | Default-exports an array of widget package names | [Widgets from packages](./builder.md#widgets-from-packages) |
31
+ | `<stub>.page.html` | One per page | One page's markup, as a fragment | [HTML](./page-files.md#html) |
32
+ | `<stub>.queries.ts` | Zero or one per page | That page's queries | [Queries](./page-files.md#queries) |
33
+ | `<stub>.requests.ts` | Zero or one per page | That page's requests | [Requests](./page-files.md#requests) |
34
+ | `<tag-name>.html` | Zero or one per custom element | One element file, as a fragment | [HTML](./custom-elements.md#html) |
35
+ | `<tag-name>.browser.ts` | Zero or one per custom element | One custom element's script, `.js` also accepted | [Code](./custom-elements.md#code) |
36
+ | `*.css` | Any number | A stylesheet, concatenated into `app.css` | [CSS](./css.md) |
37
+
38
+ ## Build outputs
39
+
40
+ The builder writes these into `--out`.
41
+
42
+ | File | Written | Holds | Defined in |
43
+ | ----------------- | ----------------- | ------------------------------------------------------------------ | ------------------------------------------------------- |
44
+ | `app.html` | Always | The chrome, with every page inside a `<template>` | [What the builder writes](./builder.md#what-the-builder-writes) |
45
+ | `client.js` | Always | `<lb-hub>` and every custom element the application uses | [What the builder writes](./builder.md#what-the-builder-writes) |
46
+ | `client-entry.ts` | Always | The generated entry `client.js` is bundled from | [What the builder writes](./builder.md#what-the-builder-writes) |
47
+ | `app.css` | With a stylesheet | Every stylesheet in the cascade, concatenated | [CSS](./css.md) |
48
+ | `pages.ts` | With page data | The one `createHub()` call, over every page's queries and requests | [Running the server](./server.md#running-the-server) |
49
+
50
+ ## Reserved package.json keys
51
+
52
+ A widget package declares these. An application never does.
53
+
54
+ | Key | Value | Means | Defined in |
55
+ | ------------------ | ------------------------- | ------------------------------------------------------------------ | --------------------------------------------------- |
56
+ | `loadbare.widgets` | A path inside the package | Where its custom elements are; absent means the whole installed directory | [Widgets from packages](./builder.md#widgets-from-packages) |
57
+
58
+ ## Reserved namespaces
59
+
60
+ | Namespace | Applies to | Loadbare owns | Defined in |
61
+ | --------- | ----------------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------ |
62
+ | `lb-*` | Attributes, their values, queries, columns of `lb-` queries, requests, custom elements, events | The names and their behavior | [Attributes](#attributes) |
63
+ | `--lb-*` | Values of HTML's `command` attribute | The names and their behavior | [Reserved commands](#reserved-commands) |
64
+ | `exp-*` | Attributes on a custom element | The behavior; element file authors pick the names | [Parameters](./custom-elements.md#parameters) |
65
+ | `lb*` | Methods on custom elements: `lb` and a capital | The names and their behavior | [Reserved methods](#reserved-methods) |
66
+
67
+ ## Attributes
68
+
69
+ The developer writes sixteen attributes in pages and the chrome, and the
70
+ builder refuses any other `lb-` attribute there. `lb-exp-slot` and
71
+ `lb-exp-template` are written for expansion, which removes them before the
72
+ check. The hub and the builder write the rest, as stamps, and script never
73
+ assigns one.
74
+
75
+ | Attribute | Written by | Defined in |
76
+ | -------------------- | --------------- | ---------------------------------------------------------------- |
77
+ | `lb-query` | Developer | [Landing](./data-binding.md#landing) |
78
+ | `lb-column` | Developer | [Landing](./data-binding.md#landing) |
79
+ | `lb-show` | Developer | [Conditional rendering](./data-binding.md#conditional-rendering) |
80
+ | `lb-order` | Developer | [Order](./data-binding.md#order) |
81
+ | `lb-filter` | Developer | [Filters](./data-binding.md#filters) |
82
+ | `lb-group` | Developer | [Groups](./data-binding.md#groups) |
83
+ | `lb-count`, `lb-sum`, `lb-avg`, `lb-min`, `lb-max` | Developer | [Aggregates](./data-binding.md#aggregates) |
84
+ | `lb-select` | Developer | [Selecting rows](./data-binding.md#selecting-rows) |
85
+ | `lb-request` | Developer | [Requests](./data-binding.md#requests) |
86
+ | `lb-url-link` | Developer | [Links](./chrome.md#links) |
87
+ | `lb-url-push` | Developer | [Query parms](./chrome.md#query-parms) |
88
+ | `lb-url-unknown` | Developer | [An unknown page](./chrome.md#an-unknown-page) |
89
+ | `lb-exp-slot` | Developer, consumed by expansion | [The slot](./custom-elements.md#the-slot) |
90
+ | `lb-exp-template` | Developer, consumed by expansion | [Destinations](./custom-elements.md#destinations) |
91
+ | `lb-column-value` | Hub | [Where a column lands](./data-binding.md#where-a-column-lands) |
92
+ | `lb-key-value` | Hub | [Landing](./data-binding.md#landing), [Editing a row in one form](./data-binding.md#editing-a-row-in-one-form) |
93
+ | `lb-row-live` | Hub | [Landing](./data-binding.md#landing) |
94
+ | `lb-query-row-count` | Hub | [Counting rows](./data-binding.md#counting-rows) |
95
+ | `lb-group-live`, `lb-group-column`, `lb-group-value` | Hub | [Groups](./data-binding.md#groups) |
96
+ | `lb-row-created`, `lb-row-changed`, `lb-row-moved`, `lb-row-requested` | Hub | [What happened to a row](./data-binding.md#what-happened-to-a-row) |
97
+ | `lb-row-leaving`, `lb-group-leaving` | Hub | [What happened to a row](./data-binding.md#what-happened-to-a-row) |
98
+ | `lb-row-editing` | Hub | [Editing a row in one form](./data-binding.md#editing-a-row-in-one-form) |
99
+ | `lb-row-selected` | Hub | [Selecting rows](./data-binding.md#selecting-rows) |
100
+ | `lb-request-pending` | Hub | [Request state](./data-binding.md#request-state) |
101
+ | `lb-request-error` | Hub | [Request state](./data-binding.md#request-state) |
102
+ | `lb-request-refused` | Hub | [Refusals](./data-binding.md#refusals) |
103
+ | `lb-show` on a `<template>` | Hub and builder | [Where an absent element is](./data-binding.md#where-an-absent-element-is) |
104
+ | `lb-page` | Builder | [What the builder writes](./builder.md#what-the-builder-writes) |
105
+ | `lb-page-title` | Builder | [The page title](./page-files.md#the-page-title) |
106
+
107
+ ## Reserved queries
108
+
109
+ The hub serves these in the browser. Every page and the chrome may name
110
+ them, and a page declares neither.
111
+
112
+ | Query | Kind | Key | Columns | Defined in |
113
+ | ------------ | ----- | --------- | --------------------------------------------------------------- | ------------------------------------- |
114
+ | `lb-url` | `row` | `lb-path` | `lb-path`, `lb-page-label`, `lb-page-unknown`, every query parm | [The URL](./chrome.md#the-url) |
115
+ | `lb-choices` | `row` | `lb-path` | `lb-path`, every choice | [Choices](./data-binding.md#choices) |
116
+
117
+ No query parm is reserved: a column of `lb-url` whose name begins with
118
+ `lb-` is never a query parm.
119
+
120
+ ## Reserved request names
121
+
122
+ | Request name | Runs under `crud` | Defined in |
123
+ | --------------- | ----------------- | --------------------------------------------------------------------------- |
124
+ | `lb-row-insert` | `rowInsert` | [Inserting, updating and deleting rows](./data-binding.md#inserting-updating-and-deleting-rows) |
125
+ | `lb-row-update` | `rowUpdate` | [Inserting, updating and deleting rows](./data-binding.md#inserting-updating-and-deleting-rows) |
126
+ | `lb-row-delete` | `rowDelete` | [Inserting, updating and deleting rows](./data-binding.md#inserting-updating-and-deleting-rows) |
127
+
128
+ ## Reserved commands
129
+
130
+ Loadbare/app owns every value of HTML's `command` attribute beginning with
131
+ `--lb-`.
132
+
133
+ | Command | Defined in |
134
+ | -------------- | ------------------------------------------------------------------------ |
135
+ | `--lb-edit` | [Editing a row in one form](./data-binding.md#editing-a-row-in-one-form) |
136
+ | `--lb-new` | [Editing a row in one form](./data-binding.md#editing-a-row-in-one-form) |
137
+ | `--lb-release` | [Editing a row in one form](./data-binding.md#editing-a-row-in-one-form) |
138
+
139
+ ## Reserved tags
140
+
141
+ An application writes these and never defines them.
142
+
143
+ | Tag | Written in | Means | Defined in |
144
+ | ---------- | ---------- | ------------------------------ | -------------------------------------------------- |
145
+ | `<lb-hub>` | The chrome | The application's live element | [The parts of a chrome](./chrome.md#the-parts-of-a-chrome) |
146
+
147
+ ## Reserved events
148
+
149
+ Loadbare/app owns each event's name and what its `detail` carries.
150
+
151
+ | Event | Dispatched from | Bubbles | Cancelable | Defined in |
152
+ | ----------------- | -------------------------- | ------- | ---------- | ----------------------------------------------------------- |
153
+ | `lb-request` | The element that committed | Yes | No | [The request event](./data-binding.md#the-request-event) |
154
+ | `lb-request-done` | The element that committed | Yes | No | [Hearing how it turned out](./custom-elements.md#hearing-how-it-turned-out) |
155
+
156
+ ## Reserved methods
157
+
158
+ The build refuses a custom element script declaring any other method,
159
+ accessor or field named `lb` and a capital.
160
+
161
+ | Method | On | Defined in |
162
+ | --------------- | ------------------------------------------------------ | ----------------------------------------------------------- |
163
+ | `lbAttached` | Any custom element the hub places | [When its code runs](./custom-elements.md#when-its-code-runs) |
164
+ | `lbRowsLanded` | A custom element with `lb-query` and a row template | [Holding rows](./custom-elements.md#holding-rows) |
165
+ | `lbRequestDone` | A custom element that committed, or holds one that did | [Hearing how it turned out](./custom-elements.md#hearing-how-it-turned-out) |
166
+ | `lbDetached` | Any custom element the hub removes | [When its code runs](./custom-elements.md#when-its-code-runs) |
167
+
168
+ ## Exports
169
+
170
+ | Entry point | Exports | Defined in |
171
+ | -------------------------- | ---------------------------------------------------------------- | ---------------------------------------------- |
172
+ | `@loadbare/app/server` | `row`, `rows`, `patch`, `url`, `choices`, `refusal`, `createHub`, and the types `Queries`, `Requests`, `HubContext`, `Hub` | [Page Files](./page-files.md), [The data channel](./server.md#the-data-channel) |
173
+ | `@loadbare/app/express` | `hubRoutes` | [The Express Server](./server.md) |
174
+ | `@loadbare/app/types` | `HubRequest`, `ResponseItem`, `HubRequestDone`, `HubLifecycle`, and the rules the hub applies: `readAs`, `parseFilter`, `passes`, `passesTerm` | [The data channel](./server.md#the-data-channel), [Order](./data-binding.md#order), [Filters](./data-binding.md#filters) |
175
+ | `@loadbare/app` | `applyRow` | [Filling a subtree by hand](./custom-elements.md#filling-a-subtree-by-hand) |
176
+ | `@loadbare/app/constants` | Every name on this page; see [Constants](#constants) | [Code](./custom-elements.md#code) |
177
+
178
+ ## Constants
179
+
180
+ `@loadbare/app/constants` exports every name above. Custom element code
181
+ imports them rather than writing a string.
182
+
183
+ | Constant | Value |
184
+ | ------------------------- | --------------------------------------- |
185
+ | `ATTR_QUERY` | `lb-query` |
186
+ | `ATTR_COLUMN` | `lb-column` |
187
+ | `ATTR_SHOW` | `lb-show` |
188
+ | `SHOW_NOT` | `!`, written before a column in `lb-show` |
189
+ | `ATTR_COLUMN_VALUE` | `lb-column-value` |
190
+ | `ATTR_KEY_VALUE` | `lb-key-value` |
191
+ | `ATTR_ROW_LIVE` | `lb-row-live` |
192
+ | `LIVE_ROW` | `[lb-row-live]`, the selector for a live row |
193
+ | `ATTR_QUERY_ROW_COUNT` | `lb-query-row-count` |
194
+ | `ATTR_GROUP` | `lb-group` |
195
+ | `ATTR_GROUP_LIVE` | `lb-group-live` |
196
+ | `ATTR_GROUP_COLUMN` | `lb-group-column` |
197
+ | `ATTR_GROUP_VALUE` | `lb-group-value` |
198
+ | `LIVE_GROUP` | `[lb-group-live]`, the selector for a group's element |
199
+ | `ATTR_COUNT`, `ATTR_SUM`, `ATTR_AVG`, `ATTR_MIN`, `ATTR_MAX` | `lb-count`, `lb-sum`, `lb-avg`, `lb-min`, `lb-max` |
200
+ | `AGGREGATES` | All five aggregate attributes |
201
+ | `ORDER_DESCENDING` | `-`, written before a column in an order |
202
+ | `COLUMN_TYPES` | `number`, `boolean`, `text` |
203
+ | `CHOICES_QUERY` | `lb-choices` |
204
+ | `ATTR_ORDER` | `lb-order` |
205
+ | `ATTR_FILTER` | `lb-filter` |
206
+ | `FILTER_OPERATORS` | Every filter operator, the longer first |
207
+ | `ATTR_ROW_CREATED` | `lb-row-created` |
208
+ | `ATTR_ROW_CHANGED` | `lb-row-changed` |
209
+ | `ATTR_ROW_MOVED` | `lb-row-moved` |
210
+ | `ATTR_ROW_REQUESTED` | `lb-row-requested` |
211
+ | `REQUESTED_CREATED`, `REQUESTED_CHANGED`, `REQUESTED_MOVED` | `created`, `changed`, `moved` |
212
+ | `ATTR_ROW_LEAVING` | `lb-row-leaving` |
213
+ | `ATTR_GROUP_LEAVING` | `lb-group-leaving` |
214
+ | `ATTR_REQUEST` | `lb-request` |
215
+ | `LB_EVENT_NAME` | `lb-request`, the event every request travels as |
216
+ | `LB_DONE_EVENT_NAME` | `lb-request-done` |
217
+ | `ATTR_REQUEST_PENDING` | `lb-request-pending` |
218
+ | `ATTR_REQUEST_ERROR` | `lb-request-error` |
219
+ | `ATTR_REQUEST_REFUSED` | `lb-request-refused` |
220
+ | `LB_REFUSAL_STATUS` | `422` |
221
+ | `COMMAND_EDIT`, `COMMAND_NEW`, `COMMAND_RELEASE` | `--lb-edit`, `--lb-new`, `--lb-release` |
222
+ | `LB_COMMANDS` | All three commands |
223
+ | `LB_COMMAND_PREFIX` | `--lb-` |
224
+ | `ATTR_ROW_EDITING` | `lb-row-editing` |
225
+ | `ATTR_SELECT` | `lb-select` |
226
+ | `ATTR_ROW_SELECTED` | `lb-row-selected` |
227
+ | `LB_RESERVED_PREFIX` | `lb-` |
228
+ | `REQUEST_ROW_INSERT` | `lb-row-insert` |
229
+ | `REQUEST_ROW_UPDATE` | `lb-row-update` |
230
+ | `REQUEST_ROW_DELETE` | `lb-row-delete` |
231
+ | `LB_ROW_REQUESTS` | All three request names |
232
+ | `LB_METHODS` | The four reserved methods |
233
+ | `BROWSER_LIFECYCLE` | The browser callbacks the build refuses |
234
+ | `KIND_ROW` | `row` |
235
+ | `KIND_ROWS` | `rows` |
236
+ | `URL_QUERY` | `lb-url` |
237
+ | `URL_COLUMN_PATH` | `lb-path` |
238
+ | `URL_COLUMN_PAGE_LABEL` | `lb-page-label` |
239
+ | `URL_COLUMN_PAGE_UNKNOWN` | `lb-page-unknown` |
240
+ | `ATTR_URL_LINK` | `lb-url-link` |
241
+ | `ATTR_URL_PUSH` | `lb-url-push` |
242
+ | `ATTR_URL_UNKNOWN` | `lb-url-unknown` |
243
+ | `ATTR_EXP_SLOT` | `lb-exp-slot` |
244
+ | `ATTR_EXP_TEMPLATE` | `lb-exp-template` |
245
+ | `ATTR_PAGE` | `lb-page` |
246
+ | `ATTR_PAGE_TITLE` | `lb-page-title` |
247
+ | `DEVELOPER_ATTRIBUTES` | The sixteen attributes a developer writes |
248
+ | `HUB_TAG_NAME` | `lb-hub` |
249
+ | `LB_ENDPOINT` | `/lb` |
250
+ | `LB_REQUEST_TIMEOUT_MS` | `10000` |
@@ -38,3 +38,9 @@ serves, and the custom elements those pages are made of.
38
38
  | [`<tag-name>.html`](./custom-elements.md#html) | The markup the tag expands into |
39
39
  | [`<tag-name>.browser.ts`](./custom-elements.md#code) | The class the tag registers |
40
40
  | [The Basic Widget Library](./widgets.md) | `@loadbare/widgets` |
41
+
42
+ ## Names
43
+
44
+ | Topic | Description |
45
+ |--------------------------|--------------------------------------------------------------|
46
+ | [Names](./names.md) | Every name Loadbare owns, the constants, and where each is defined |
@@ -14,7 +14,9 @@ Name the landing page `index.page.html`. A bare `/` resolves to `index`, and
14
14
  every other path names the page whose stub it is.
15
15
 
16
16
  Put the files anywhere under `src/`. The builder pairs them by stub, not by
17
- directory; `src/pages/` is the convention.
17
+ directory; `src/pages/` is the convention. The page namespace is flat, so
18
+ `src/deep/path/mypage.page.html` is the page for `/mypage`, and a stub is
19
+ unique across the application.
18
20
 
19
21
  ## What the page files are
20
22
 
@@ -122,8 +124,19 @@ including a `row` query's: an aggregate row answers with a constant key. The
122
124
  hub matches rows to live rows by key, and a request names a row by key.
123
125
 
124
126
  Kind and key belong to the name, so one name answers with one shape, always,
125
- and `createHub` leaves out an answer that disagrees with its declaration. A
126
- page that needs the same data as one row and as a set declares two queries.
127
+ and `createHub` leaves out an answer that disagrees with its declaration,
128
+ with a warning naming the query. A page that needs the same data as one row
129
+ and as a set declares two queries.
130
+
131
+ `createHub` refuses at startup a page that declares a query, a handler or a
132
+ `crud` entry whose name begins with `lb-`, a query whose order has a term
133
+ naming no column, and a type it does not know.
134
+
135
+ Every query and every handler receives `ctx`, the application's own request
136
+ context, built by `contextFor`; see
137
+ [the Express server](./server.md#database-layer). Loadbare declares it
138
+ empty and never reads it, so a query finds exactly what the application put
139
+ there.
127
140
 
128
141
  The hub hands each value to the browser untouched, so what a number, a date
129
142
  or a null looks like is decided here, in the query.
@@ -230,13 +243,20 @@ export const requests: Requests = {
230
243
  ```
231
244
 
232
245
  Declare every request the page allows. The builder refuses markup naming a
233
- request the page does not declare, and the server refuses one that arrives
234
- anyway; see [The markup checks](./data-binding.md#the-markup-checks).
246
+ request the page does not declare; see
247
+ [The markup checks](./data-binding.md#the-markup-checks). One can still
248
+ arrive, from a widget's script or from outside the page. The server then
249
+ warns on its console, naming the page and the missing entry, and answers
250
+ 200 with no response items, so nothing lands and `lb-request-pending`
251
+ clears. A request carrying no name answers 400, and a `run` that throws
252
+ anything but a [refusal](#refusing-a-request) answers 500.
235
253
 
236
254
  ### crud
237
255
 
238
256
  Declare what the three requests Loadbare provides do under `crud`, keyed by
239
- the query they operate on:
257
+ the query they operate on. Each key is the request name with its `lb-`
258
+ stripped and the rest camel-cased, so the attribute, the wire and the key
259
+ are one vocabulary:
240
260
 
241
261
  | Request name | Runs | `run` receives |
242
262
  |-----------------|-------------|------------------|
@@ -295,8 +315,9 @@ export const requests: Requests = {
295
315
  ```
296
316
 
297
317
  Declare every request the query permits. The builder refuses markup sending
298
- a request a query does not declare, and the server refuses one that arrives
299
- anyway. A query with no `crud` entry permits none.
318
+ a request a query does not declare, and the server answers one that arrives
319
+ anyway as it does an undeclared handler, with a console warning and nothing
320
+ to land. A query with no `crud` entry permits none.
300
321
 
301
322
  Write `rowUpdate` to set the columns `values` names and leave every other
302
323
  column as it is. A form or a live row sends every control it gathers, and a
@@ -374,7 +395,7 @@ removing a row never reorders the rest. So does an insert, which has the new
374
395
  row's key once it has written it, and the hub places the new row by the
375
396
  query's order. `createHub` refuses at startup a `crud` `rowInsert`,
376
397
  `rowUpdate` or `rowDelete` on a `rows` query whose `refresh` names that same
377
- query.
398
+ query. A `row` query sends one row and may refresh itself.
378
399
 
379
400
  Each request's `refresh` is its own, and every query in it needs its own
380
401
  reason. A list shared by several requests is a warning sign.
@@ -440,6 +461,21 @@ nothing else `run` returned is sent, since both answered for the URL the page
440
461
  left. The loaded page reads the new parms through `contextFor`; see
441
462
  [the Express server](./server.md#database-layer).
442
463
 
464
+ - A `url()` naming no column, or holding a value that is not a string, is
465
+ warned about on the server console and ignored, and the refresh set runs
466
+ as usual.
467
+ - If loading the page fails, the write has still happened. The response
468
+ carries the `lb-url` item alone, and the hub loads the page itself. A
469
+ failure there is a page load's, and is not stamped on the element that
470
+ sent the request.
471
+ - A user who has moved by the time the response arrives, to another page or
472
+ to other query parms, keeps the URL they are on.
473
+
474
+ Saving what the user has on screen, for `onPageEnter` to restore, is a
475
+ declared request with `refresh: []`, whose handler each page passes to a
476
+ routine the application's pages share. A custom element naming `lb-url`
477
+ sees every change of the URL and sends it.
478
+
443
479
  ### Refusing a request
444
480
 
445
481
  A handler refuses a request by throwing `refusal()`, with words written for
@@ -17,7 +17,7 @@ import { readFileSync } from "node:fs";
17
17
  import express, { type Request } from "express";
18
18
  import { hubRoutes } from "@loadbare/app/express";
19
19
  import type { HubContext } from "@loadbare/app/server";
20
- import { hub } from "./dist/pages";
20
+ import { hub } from "./dist/pages.ts";
21
21
  import { openDb } from "./src/database";
22
22
 
23
23
  const DIST = path.resolve("dist");
@@ -181,3 +181,56 @@ open across the two, is both.
181
181
  Add a field for anything else a request needs — the authenticated user, a
182
182
  request id, a feature flag set. Queries and requests read them from `ctx`; see
183
183
  [page files](./page-files.md).
184
+
185
+ ## The data channel
186
+
187
+ One path, one method. The hub sends every round trip as
188
+ `POST /lb/<stub>?<query string>`, with the query string the browser is
189
+ showing, verbatim. An empty body, or `{}`, asks for the page load. Any other
190
+ body is a request:
191
+
192
+ ```ts
193
+ interface HubRequest {
194
+ name: string;
195
+ query?: string;
196
+ key?: string;
197
+ keys?: string[];
198
+ values?: Record<string, string>;
199
+ rows?: Record<string, { key: string; values: Record<string, string> }[]>;
200
+ }
201
+ ```
202
+
203
+ A refused request is answered with status 422 and
204
+ `{ refusal: { message?, columns? } }`, a request with no name with 400 and
205
+ `[]`, and
206
+ any other failure with 500 and no body; see
207
+ [Refusing a request](./page-files.md#refusing-a-request). Every other
208
+ response is an array of response items:
209
+
210
+ ```ts
211
+ interface ResponseItem {
212
+ query: string;
213
+ kind: "row" | "rows";
214
+ key: string;
215
+ row?: Row;
216
+ rows?: Row[];
217
+ patch?: { rows?: Row[]; drop?: unknown[] };
218
+ }
219
+ ```
220
+
221
+ A response item carries exactly one of `row`, `rows` or `patch`. A patch
222
+ answers a `rows` query only: rows named in `rows` are added or updated, keys
223
+ in `drop` are removed, and every other row is left alone. A `rows` item on
224
+ the answer that loads the page also carries the query's declared `order`
225
+ and `types`, which the hub keeps for every answer after it.
226
+
227
+ A choice is sent as `lb-row-update` for `lb-choices`, keyed by the path,
228
+ with every choice made on the page as `values`; see
229
+ [Choices](./data-binding.md#choices).
230
+
231
+ `createHub(pages)` builds the `Hub`, which has two calls, both answering
232
+ with response items: `dataForPage(page, ctx, options)` for a page load, and
233
+ `runRequest(page, request, ctx)` for a request. `options.parms` are the
234
+ parms the page is loaded at, and `options.follow` false ignores a `url()`
235
+ from `onPageEnter`. `hubRoutes` passes both, and an application calls
236
+ neither.
@@ -130,7 +130,7 @@ requests to the correct page code.
130
130
  import express from "express";
131
131
  import { readFileSync } from "node:fs";
132
132
  import { hubRoutes } from "@loadbare/app/express";
133
- import { hub } from "./dist/pages";
133
+ import { hub } from "./dist/pages.ts";
134
134
  import { openDb } from "./src/database";
135
135
 
136
136
  const app = express();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@loadbare/app",
3
3
  "description": "High performance web app framework for server-bound applications",
4
- "version": "0.19.0",
4
+ "version": "0.19.1",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",
@@ -278,8 +278,8 @@ fetched to be edited is another page, with that row as its master.
278
278
  Each works on a `row` query as well as a `rows` query, since the element a
279
279
  `row` lands on carries its key. Every other request name is a declared
280
280
  request, under `handlers`. The build refuses markup naming a query or
281
- request the page does not declare, and the server refuses one that arrives
282
- anyway.
281
+ request the page does not declare. One that arrives anyway, from a script,
282
+ is warned about on the server console and answered with nothing.
283
283
 
284
284
  **An empty key is a row not yet entered.** A `row` query answering with an
285
285
  empty key, or a form `--lb-new` pointed, carries `lb-key-value=""`. The hub
@@ -392,10 +392,13 @@ lb-query="lb-url">` in the chrome for it.
392
392
  whole data channel.
393
393
 
394
394
  **The developer writes `lb-*` attributes in markup, and script never
395
- assigns them.** The builder refuses any `lb-` attribute other than
396
- `lb-query`, `lb-column`, `lb-show`, `lb-order`, `lb-filter`, `lb-request`,
397
- `lb-url-link`, `lb-url-push` and `lb-url-unknown`. Everything else with the
398
- prefix is a stamp the hub or the builder writes.
395
+ assigns them.** The builder refuses any `lb-` attribute other than the
396
+ sixteen in [Attributes](references/names.md#attributes): `lb-query`,
397
+ `lb-column`, `lb-show`, `lb-order`, `lb-filter`, `lb-group`, `lb-count`,
398
+ `lb-sum`, `lb-avg`, `lb-min`, `lb-max`, `lb-select`, `lb-request`,
399
+ `lb-url-link`, `lb-url-push` and `lb-url-unknown`. `lb-exp-slot` and
400
+ `lb-exp-template` are expansion's, and never reach the page. Everything
401
+ else with the prefix is a stamp the hub or the builder writes.
399
402
 
400
403
  **`lb-` belongs to Loadbare.** The application should never name anything
401
404
  with the `lb-` prefix anywhere: no attribute, query, column, request, custom
@@ -500,7 +503,6 @@ with the package, so no network is needed.
500
503
  - [`references/css.md`](references/css.md) — how stylesheets are ordered.
501
504
  - [`references/widgets.md`](references/widgets.md) — the basic widget
502
505
  library.
503
- - [`references/TECHREF-1.0.md`](references/TECHREF-1.0.md) — the reserved
504
- names, what a URL names and
505
- [query parms](references/TECHREF-1.0.md#query-parms). Read it before designing around something the other
506
- references do not mention.
506
+ - [`references/names.md`](references/names.md) — every name Loadbare
507
+ owns, the constants, and the section defining each. Check a name here
508
+ before designing around something the other references do not mention.
@@ -44,11 +44,16 @@ The builder classifies by name, not location.
44
44
  | `*.queries.ts` | A page's queries, matched by base name |
45
45
  | `imports.ts` | The packages this app takes widgets from |
46
46
  | `*.css` | A stylesheet — see [CSS](./css.md) |
47
- | Any other `.html` | A widget definition, named for the tag it defines |
47
+ | `<tag-name>.html` | A widget definition, named for the tag it defines |
48
48
  | `<tag>.browser.ts` | A widget script, named for the tag it registers |
49
49
 
50
50
  Give the application exactly one `chrome.html` and at most one `imports.ts`.
51
- Give every `.requests.ts` and `.queries.ts` a `.page.html` of the same base name.
51
+ Give every `.requests.ts` and `.queries.ts` a `.page.html` of the same base name;
52
+ one with no page is a build error.
53
+
54
+ A widget definition is exactly `<kebab-case-name>.html`. Any other `.html`
55
+ file is an error when it looks like a definition with capitals in its name,
56
+ and is skipped otherwise.
52
57
 
53
58
  Name a widget script `<tag>.browser.ts`, not `<tag>.ts`. Only a file whose
54
59
  name carries `.browser` is bundled for the browser; every other module under
@@ -141,6 +146,10 @@ does when the server starts, so a module that connects to a database at
141
146
  import connects during the build too; open connections in `contextFor`
142
147
  instead.
143
148
 
149
+ It also writes `client-entry.ts`, the generated entry `client.js` is bundled
150
+ from, and leaves it there. Nothing reads it after the bundle, and an
151
+ application neither writes nor imports it.
152
+
144
153
  The builder writes `app.css` only when it finds a stylesheet, and `pages.ts`
145
154
  only when some page has a `.requests.ts` or a `.queries.ts` file. Import `hub`
146
155
  from `pages.ts` — see [The Express Server](./server.md) for the rest of the
@@ -103,6 +103,12 @@ server's row lands, so a chrome shows where the page is with no code:
103
103
  | `lb-page-unknown` | `true` when no page's stub matches the path |
104
104
  | Any other name | The query parm of that name, or empty when it is absent |
105
105
 
106
+ The path names a page, never a resource: `/accounts/42` is not a way to
107
+ reach account 42, and an application designs no endpoints. The query string
108
+ describes what that page has on screen, `/accounts?acct=23`, and never
109
+ commands. The server remembers nothing about the view, so a URL reloads,
110
+ bookmarks and mails as the same view.
111
+
106
112
  A path names a page by its stub: `/members` shows `members.page.html`, and
107
113
  `/` shows `index.page.html`. A page's title is the text of the `<title>` its
108
114
  page file carries; see [page files](./page-files.md#the-page-title). The hub
@@ -139,11 +145,17 @@ Write `lb-url-link` on an `<a>` to move between pages:
139
145
  </nav>
140
146
  ```
141
147
 
142
- On a plain primary click, the hub pushes a history entry for the `href`,
143
- path and query string both, and shows the page it names. A click with a
144
- modifier key or another button, and a link without `lb-url-link`, behave as
145
- the browser has them behave. Back and Forward show the page at the URL they
146
- arrive at.
148
+ On a plain primary click, the first button with no modifier key and not
149
+ already handled, the hub pushes a history entry for the `href`, path and
150
+ query string both, and shows the page it names. Any other click, and a link
151
+ without `lb-url-link`, behave as the browser has them behave, so leaving the
152
+ application is the default and staying in it is the opt-in. The attribute
153
+ takes no value. Back and Forward show the page at the URL they arrive at.
154
+
155
+ Path space is flat: `/members` shows `members.page.html` wherever under
156
+ `--src` it sits. The query string is kept, so
157
+ `/transactions?date_begin=2026-09-01` opens the page narrowed to those
158
+ dates.
147
159
 
148
160
  ### Query parms
149
161
 
@@ -170,11 +182,28 @@ pushes one instead when the element carrying `lb-request` also carries
170
182
 
171
183
  The hub answers the request itself, with no round trip, then reloads the
172
184
  page's queries at the new URL. The page stays in place, and rows that come
173
- back keep their place.
185
+ back keep their place. A request that leaves the URL as it was does
186
+ nothing. `lb-page-label` and `lb-page-unknown` are the hub's, and a request
187
+ never sets them.
188
+
189
+ A request that gives `lb-path` a new value enters that page, pushes a
190
+ history entry, and the new URL carries only the query parms the request
191
+ names.
174
192
 
175
193
  A control whose column the URL does not carry lands empty, so every control
176
194
  inside `lb-query="lb-url"` shows what the address bar says, after a reload
177
- and after Back alike.
195
+ and after Back alike
196
+ ([miswart](./miswarts.md#lb-url-lands-every-parm-it-names)).
197
+
198
+ Loadbare assigns no parm a meaning, and a column of `lb-url` whose name
199
+ begins with `lb-` is never a query parm.
200
+
201
+ A query parm is user input. A query still takes no argument from the
202
+ browser, and a parm arrives the way the session cookie does, as part of the
203
+ request the context is built from, but a user who types `?acct=99999`
204
+ influences what a query returns. Validate it where the application reads
205
+ it; a per-session database role means an id outside the caller's reach
206
+ finds nothing.
178
207
 
179
208
  An order or a filter the hub applies to rows the page already holds is not
180
209
  a query parm. It is a [choice](./data-binding.md#choices), which holds until
@@ -198,8 +227,13 @@ show the path:
198
227
  ```
199
228
 
200
229
  When no page's stub matches the path, `lb-page-unknown` is `true`,
201
- `lb-page-label` is null, `<main>` keeps the page it had, and the hub calls
202
- `showModal()` on the dialog.
230
+ `lb-page-label` is null, `<main>` keeps the page it had, the hub calls
231
+ `showModal()` on the dialog, and reports the path to the console. A chrome
232
+ with no dialog shows nothing. `lb-url` lands before the page is looked up,
233
+ so the dialog shows the path by naming it like any other element.
234
+
235
+ Every route gets the same document, so an unknown path is found out in the
236
+ browser after a successful 200, and there is no server-delivered 404.
203
237
 
204
238
  `@loadbare/widgets` ships this dialog as `<lb-unknown-page>`, for a chrome
205
239
  that would rather write one tag; see
@@ -41,7 +41,6 @@ Serve it at `/app.css` and link it from the chrome:
41
41
  See [`chrome.html`](./chrome.md) for the link, [The Express
42
42
  server](./server.md) for the route, and
43
43
  [`loadbare-app-build`](./builder.md#running-the-builder) for `--minify`.
44
- </content>
45
44
 
46
45
  ## Selecting what the hub stamps
47
46
 
@@ -250,30 +250,8 @@ takes part in data binding in one of four ways: it is a control, it renders
250
250
  a value, it sends a request, or it holds rows.
251
251
 
252
252
  Import every Loadbare name from `@loadbare/app/constants`, and never write
253
- one as a string literal:
254
-
255
- | Constant | Value |
256
- |------------------------|----------------------|
257
- | `ATTR_QUERY` | `lb-query` |
258
- | `ATTR_COLUMN` | `lb-column` |
259
- | `ATTR_SHOW` | `lb-show` |
260
- | `ATTR_REQUEST` | `lb-request` |
261
- | `ATTR_COLUMN_VALUE` | `lb-column-value` |
262
- | `ATTR_KEY_VALUE` | `lb-key-value` |
263
- | `ATTR_ROW_LIVE` | `lb-row-live` |
264
- | `LIVE_ROW` | `[lb-row-live]` |
265
- | `ATTR_QUERY_ROW_COUNT` | `lb-query-row-count` |
266
- | `ATTR_REQUEST_PENDING` | `lb-request-pending` |
267
- | `ATTR_REQUEST_ERROR` | `lb-request-error` |
268
- | `REQUEST_ROW_INSERT`, `REQUEST_ROW_UPDATE`, `REQUEST_ROW_DELETE` | The requests Loadbare provides |
269
- | `LB_ROW_REQUESTS` | All three, in one array |
270
- | `LB_EVENT_NAME` | `lb-request`, the event every request travels as |
271
- | `LB_DONE_EVENT_NAME` | `lb-request-done`, the event that says how it turned out |
272
- | `SHOW_NOT` | `!`, written before a column in `lb-show` |
273
- | `URL_QUERY` | `lb-url` |
274
- | `CHOICES_QUERY` | `lb-choices` |
275
- | `URL_COLUMN_PATH`, `URL_COLUMN_PAGE_LABEL`, `URL_COLUMN_PAGE_UNKNOWN` | `lb-path`, `lb-page-label`, `lb-page-unknown` |
276
- | `LB_RESERVED_PREFIX` | `lb-` |
253
+ one as a string literal: `ATTR_COLUMN_VALUE` rather than `"lb-column-value"`.
254
+ [Constants](./names.md#constants) lists them all.
277
255
 
278
256
  A custom element reads `lb-` attributes and never assigns one. The developer
279
257
  writes them in markup, and the hub and the builder write their stamps.