@loadbare/app 0.18.2 → 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.
Files changed (51) hide show
  1. package/README.md +17 -17
  2. package/dist/build/assemble.d.ts +7 -1
  3. package/dist/build/assemble.d.ts.map +1 -1
  4. package/dist/build/assemble.js +81 -11
  5. package/dist/build/assemble.js.map +1 -1
  6. package/dist/build/cli.d.ts +3 -0
  7. package/dist/build/cli.d.ts.map +1 -1
  8. package/dist/build/cli.js +6 -1
  9. package/dist/build/cli.js.map +1 -1
  10. package/dist/build/declared.d.ts +25 -0
  11. package/dist/build/declared.d.ts.map +1 -0
  12. package/dist/build/declared.js +62 -0
  13. package/dist/build/declared.js.map +1 -0
  14. package/dist/build/pages.d.ts +6 -0
  15. package/dist/build/pages.d.ts.map +1 -1
  16. package/dist/build/pages.js +32 -12
  17. package/dist/build/pages.js.map +1 -1
  18. package/docs/comparison.md +697 -484
  19. package/docs/departures.md +63 -0
  20. package/docs/miswarts.md +140 -0
  21. package/docs/opinions.md +155 -0
  22. package/docs/reference/builder.md +20 -4
  23. package/docs/reference/chrome.md +45 -10
  24. package/docs/reference/css.md +0 -1
  25. package/docs/reference/custom-elements.md +3 -25
  26. package/docs/reference/data-binding.md +265 -48
  27. package/docs/reference/names.md +250 -0
  28. package/docs/reference/overview.md +6 -0
  29. package/docs/reference/page-files.md +77 -9
  30. package/docs/reference/server.md +54 -1
  31. package/docs/roadmap.md +55 -43
  32. package/docs/testing.md +62 -28
  33. package/docs/theory.md +348 -108
  34. package/docs/tutorials/040-displaying-data.md +1 -1
  35. package/package.json +1 -1
  36. package/skills/loadbare-app/SKILL.md +19 -13
  37. package/skills/loadbare-app/references/builder.md +20 -4
  38. package/skills/loadbare-app/references/chrome.md +45 -10
  39. package/skills/loadbare-app/references/css.md +0 -1
  40. package/skills/loadbare-app/references/custom-elements.md +3 -25
  41. package/skills/loadbare-app/references/data-binding.md +265 -48
  42. package/skills/loadbare-app/references/miswarts.md +140 -0
  43. package/skills/loadbare-app/references/names.md +250 -0
  44. package/skills/loadbare-app/references/overview.md +6 -0
  45. package/skills/loadbare-app/references/page-files.md +77 -9
  46. package/skills/loadbare-app/references/server.md +54 -1
  47. package/docs/TECHREF-1.0.md +0 -2312
  48. package/docs/possible-ideas.md +0 -314
  49. package/docs/theory-old.md +0 -839
  50. package/docs/what-does-loadbare-extend.md +0 -124
  51. package/skills/loadbare-app/references/TECHREF-1.0.md +0 -2312
@@ -0,0 +1,140 @@
1
+ # Miswarts
2
+
3
+ A miswart is a place where Loadbare/app breaks one of its own rules.
4
+ Anybody paying attention will see it as a violation, and they will be
5
+ right. Yet in context it turns out to be the RightThing(tm), and doing it
6
+ any other way would be worse for the user or the developer.
7
+
8
+ The reference describes each miswart's behavior where it applies, and
9
+ links here. This document is the one place that admits they are
10
+ exceptions, and says why each one is allowed to stay.
11
+
12
+ ## The test
13
+
14
+ A miswart must be:
15
+
16
+ - **Small.** It can be stated in a sentence or two.
17
+ - **Orthogonal.** It has no interaction with any other miswart.
18
+ - **Terminating.** It does not, by logical extension, require another
19
+ miswart. If fixing the edges of one exception calls for a second, we
20
+ probably should be looking for a weakness in earlier implementation
21
+ decisions.
22
+
23
+ Each entry also names two things:
24
+
25
+ - **The rule it breaks.** If we cannot name the rule, it is not a
26
+ miswart, it is undocumented behavior.
27
+ - **The motivation that justifies it**: performance, correctness or
28
+ ergonomics, as set out in the theory document. That is what makes it
29
+ the RightThing(tm) rather than a convenience.
30
+
31
+ ## Radio button names in a row template
32
+
33
+ **What.** The hub stamps the `name` of each radio button in a row
34
+ template with the `name` written, or its column when none is, followed by
35
+ `-` and the live row's key.
36
+
37
+ **Rule broken.** The hub writes only its own stamps, which always
38
+ begin with `lb-`.
39
+
40
+ **Why it is the RightThing(tm).** Correctness. The browser groups radio
41
+ buttons by `name`, and a row template repeats one `name` in every live
42
+ row, so without the stamp every row's buttons would form a single group,
43
+ and checking one row would uncheck all the others.
44
+
45
+ **Why it terminates.** It applies only to radio buttons inside a row
46
+ template, and only the `name`.
47
+
48
+ ## Checkboxes and radio groups always gather a value
49
+
50
+ **What.** An unchecked checkbox gathers `"false"`, a checked one
51
+ `"true"`, and its `value` attribute means nothing to the hub. A radio
52
+ group with none checked gathers `""`.
53
+
54
+ **Rule broken.** Where HTML already has an opinion, Loadbare/app
55
+ follows it. HTML sends a checkbox's `value` when it
56
+ is checked, and sends nothing for an unchecked checkbox or an empty radio
57
+ group.
58
+
59
+ **Why it is the RightThing(tm).** Correctness. A write must be able to
60
+ set a flag back to false, or clear a choice, and nothing on the wire
61
+ cannot say either.
62
+
63
+ **Why it terminates.** It applies only to gathering from these two
64
+ controls. Landing still follows HTML's own idea of checked.
65
+
66
+ ## Rows hidden by a filter
67
+
68
+ **What.** A row that does not pass the user's filter gets the `hidden`
69
+ attribute, and so does a group whose rows are all hidden.
70
+
71
+ **Rule broken.** The hub writes only its own stamps, which always
72
+ begin with `lb-`.
73
+
74
+ **Why it is the RightThing(tm).** Performance and ergonomics. A filter
75
+ over rows already on the page needs no round trip, and `hidden` is HTML's
76
+ own word for an element that is present but not shown. A stamp of our
77
+ own would make every stylesheet restate what the browser already does.
78
+
79
+ **Why it terminates.** It applies only to live rows and groups under a
80
+ filter.
81
+
82
+ ## Leaving rows are inert
83
+
84
+ **What.** A row on its way out takes `inert` while its animation runs,
85
+ and is removed when the animation finishes.
86
+
87
+ **Rule broken.** The hub writes only its own stamps, which always
88
+ begin with `lb-`.
89
+
90
+ **Why it is the RightThing(tm).** Correctness. A row that is gone from
91
+ the database must not take a click or the focus during its exit
92
+ animation, and `inert` is exactly HTML's word for that.
93
+
94
+ **Why it terminates.** It applies only to a row already stamped
95
+ `lb-row-leaving`, which never comes back.
96
+
97
+ ## Busy elements carry aria-busy
98
+
99
+ **What.** While an element's request is in flight, it carries
100
+ `aria-busy="true"` alongside `lb-request-pending`.
101
+
102
+ **Rule broken.** The hub writes only its own stamps, which always
103
+ begin with `lb-`.
104
+
105
+ **Why it is the RightThing(tm).** Correctness, for a user of assistive
106
+ technology. `lb-request-pending` tells a stylesheet, and `aria-busy` tells
107
+ a screen reader, the same fact.
108
+
109
+ **Why it terminates.** It rides with `lb-request-pending` and nothing
110
+ else.
111
+
112
+ ## The hub unhides the body
113
+
114
+ **What.** When the chrome writes `<body hidden>`, the hub removes
115
+ `hidden` once the first page has landed.
116
+
117
+ **Rule broken.** The hub writes only its own stamps, which always
118
+ begin with `lb-`.
119
+
120
+ **Why it is the RightThing(tm).** Performance, as the user perceives it.
121
+ Without it, the first load blinks an empty page before the data lands.
122
+ Only the hub knows when the first page has landed.
123
+
124
+ **Why it terminates.** It happens once, to `<body>`, and only when the
125
+ application opted in by writing `hidden`.
126
+
127
+ ## `lb-url` lands every parm it names
128
+
129
+ **What.** When `lb-url` lands, a query parm the URL does not carry lands
130
+ empty on every element naming it as a column.
131
+
132
+ **Rule broken.** Landing a row sets the columns the row carries, and
133
+ leaves the rest as they were.
134
+
135
+ **Why it is the RightThing(tm).** Correctness. A control must show what
136
+ the address bar says, after Back as well as after a reload, and a parm
137
+ that is absent from the URL is a parm whose value is empty.
138
+
139
+ **Why it terminates.** It applies only to `lb-url`, the one query whose
140
+ columns are the URL itself.
@@ -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.
@@ -229,13 +242,21 @@ export const requests: Requests = {
229
242
  };
230
243
  ```
231
244
 
232
- Declare every request the page allows. The server refuses a name the page
233
- does not declare.
245
+ Declare every request the page allows. The builder refuses markup naming a
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.
234
253
 
235
254
  ### crud
236
255
 
237
256
  Declare what the three requests Loadbare provides do under `crud`, keyed by
238
- 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:
239
260
 
240
261
  | Request name | Runs | `run` receives |
241
262
  |-----------------|-------------|------------------|
@@ -293,8 +314,10 @@ export const requests: Requests = {
293
314
  };
294
315
  ```
295
316
 
296
- Declare every request the query permits. The server refuses a request a
297
- query does not declare, and a query with no `crud` entry permits none.
317
+ Declare every request the query permits. The builder refuses markup sending
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.
298
321
 
299
322
  Write `rowUpdate` to set the columns `values` names and leave every other
300
323
  column as it is. A form or a live row sends every control it gathers, and a
@@ -303,6 +326,36 @@ columns the page may edit.
303
326
 
304
327
  Every value in `values` is a string, as a control's value is.
305
328
 
329
+ Two users writing the same row overlap only on the columns their requests
330
+ carry, and the last write wins. A single control sends its own column, so
331
+ two users editing different columns of a row do not overwrite each other.
332
+ A form sends every control it gathers, so two forms saved over one row
333
+ overlap on all of their columns.
334
+
335
+ A write whose outcome matters states its precondition in its own SQL, and
336
+ refuses when no row was updated:
337
+
338
+ ```ts
339
+ handlers: {
340
+ approve: {
341
+ run: async (ctx, { key }) => {
342
+ const updated = await ctx.db.run(
343
+ "UPDATE invoice SET status = 'approved' WHERE id = $1 AND status = 'pending'",
344
+ [key],
345
+ );
346
+ if (updated === 0) {
347
+ throw refusal("Someone else has already decided this invoice.");
348
+ }
349
+ },
350
+ refresh: ["invoice"],
351
+ },
352
+ },
353
+ ```
354
+
355
+ The request's name says what the user meant, and the precondition follows
356
+ from it, so no earlier value is needed. See
357
+ [Refusing a request](#refusing-a-request).
358
+
306
359
  ### refresh and patch
307
360
 
308
361
  List in `refresh` every query whose whole answer the request changed.
@@ -342,7 +395,7 @@ removing a row never reorders the rest. So does an insert, which has the new
342
395
  row's key once it has written it, and the hub places the new row by the
343
396
  query's order. `createHub` refuses at startup a `crud` `rowInsert`,
344
397
  `rowUpdate` or `rowDelete` on a `rows` query whose `refresh` names that same
345
- query.
398
+ query. A `row` query sends one row and may refresh itself.
346
399
 
347
400
  Each request's `refresh` is its own, and every query in it needs its own
348
401
  reason. A list shared by several requests is a warning sign.
@@ -408,6 +461,21 @@ nothing else `run` returned is sent, since both answered for the URL the page
408
461
  left. The loaded page reads the new parms through `contextFor`; see
409
462
  [the Express server](./server.md#database-layer).
410
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
+
411
479
  ### Refusing a request
412
480
 
413
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.