@loadbare/app 0.7.4 → 0.8.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 (48) hide show
  1. package/dist/build/skills-cli.d.ts +12 -0
  2. package/dist/build/skills-cli.d.ts.map +1 -0
  3. package/dist/build/skills-cli.js +81 -0
  4. package/dist/build/skills-cli.js.map +1 -0
  5. package/dist/build/skills.d.ts +47 -0
  6. package/dist/build/skills.d.ts.map +1 -0
  7. package/dist/build/skills.js +124 -0
  8. package/dist/build/skills.js.map +1 -0
  9. package/dist/core/lb-constants.d.ts +1 -2
  10. package/dist/core/lb-constants.d.ts.map +1 -1
  11. package/dist/core/lb-constants.js +13 -12
  12. package/dist/core/lb-constants.js.map +1 -1
  13. package/dist/core/lb-types.d.ts +4 -10
  14. package/dist/core/lb-types.d.ts.map +1 -1
  15. package/dist/core/lb-types.js.map +1 -1
  16. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  17. package/dist/hub/lb-hub.browser.js +47 -24
  18. package/dist/hub/lb-hub.browser.js.map +1 -1
  19. package/dist/server/lb-express.d.ts.map +1 -1
  20. package/dist/server/lb-express.js +2 -7
  21. package/dist/server/lb-express.js.map +1 -1
  22. package/dist/server/lb-server.d.ts +8 -15
  23. package/dist/server/lb-server.d.ts.map +1 -1
  24. package/dist/server/lb-server.js +0 -3
  25. package/dist/server/lb-server.js.map +1 -1
  26. package/docs/TECHREF-1.0.md +21 -13
  27. package/docs/analysis-closed-set.md +16 -9
  28. package/docs/comparison.md +7 -6
  29. package/docs/reference/custom-elements.md +11 -6
  30. package/docs/reference/data-binding.md +36 -12
  31. package/docs/reference/page-files.md +13 -9
  32. package/docs/reference/widgets.md +4 -4
  33. package/docs/roadmap.md +1 -1
  34. package/docs/testing.md +5 -1
  35. package/docs/theory.md +1 -1
  36. package/docs/tutorials/080-widget-requests.md +9 -28
  37. package/package.json +8 -4
  38. package/skills/loadbare-app/SKILL.md +258 -0
  39. package/skills/loadbare-app/references/TECHREF-1.0.md +1189 -0
  40. package/skills/loadbare-app/references/builder.md +134 -0
  41. package/skills/loadbare-app/references/chrome.md +158 -0
  42. package/skills/loadbare-app/references/css.md +44 -0
  43. package/skills/loadbare-app/references/custom-elements.md +397 -0
  44. package/skills/loadbare-app/references/data-binding.md +457 -0
  45. package/skills/loadbare-app/references/overview.md +38 -0
  46. package/skills/loadbare-app/references/page-files.md +194 -0
  47. package/skills/loadbare-app/references/server.md +142 -0
  48. package/skills/loadbare-app/references/widgets.md +174 -0
@@ -0,0 +1,142 @@
1
+ # The Express Server
2
+
3
+ Loadbare ships no server. The application writes an ordinary Express app and
4
+ serves four things from it: the client script, the stylesheet, the data
5
+ channel, and the one HTML document.
6
+
7
+ Express is a peer dependency. The application installs it.
8
+
9
+ ## A complete server
10
+
11
+ Here is a minimal but complete server for a typical app:
12
+
13
+ ```ts
14
+ // server.ts
15
+ import path from "node:path";
16
+ import { readFileSync } from "node:fs";
17
+ import express, { type Request } from "express";
18
+ import { hubRoutes } from "@loadbare/app/express";
19
+ import type { HubContext } from "@loadbare/app/server";
20
+ import { hub } from "./dist/pages";
21
+ import { openDb } from "./src/database";
22
+
23
+ const DIST = path.resolve("dist");
24
+ const app = express();
25
+
26
+ app.get("/client.js", (_req, res) => res.sendFile(path.join(DIST, "client.js")));
27
+ app.get("/app.css", (_req, res) => res.sendFile(path.join(DIST, "app.css")));
28
+
29
+ function contextFor(_req: Request): HubContext {
30
+ return { db: openDb() };
31
+ }
32
+ app.use(hubRoutes(hub, contextFor));
33
+
34
+ app.get(/.*/, (_req, res) =>
35
+ res.type("html").send(readFileSync(path.join(DIST, "app.html"), "utf-8")),
36
+ );
37
+
38
+ app.listen(8787);
39
+ ```
40
+
41
+ ## What the server serves
42
+
43
+ | Required | Serves |
44
+ |------------------------------|-------------------------------|
45
+ | `/client.js` | `dist/client.js` |
46
+ | `/app.css` | `dist/app.css` |
47
+ | `hubRoutes(hub, contextFor)` | The hub's own route |
48
+ | Every other GET | `dist/app.html` |
49
+
50
+ Everything else is optional:
51
+
52
+ | Optional | Description |
53
+ |-------------------------------|----------------------------------------------|
54
+ | Middleware before `hubRoutes` | Sessions, authentication, logging |
55
+ | The application's own routes | Uploads, webhooks, anything outside Loadbare |
56
+ | An error handler | Express sends its own 500 without one |
57
+
58
+
59
+ ## Rules for writing the server
60
+
61
+ Register the static routes and `hubRoutes` before the catch-all.
62
+
63
+ Register authentication before `hubRoutes`.
64
+
65
+ Serve `dist/app.html` for every route the application does not claim,
66
+ including a path that names no page. See [`chrome.html`](./chrome.md) for the
67
+ `<dialog lb-unknown-page>` that announces that case to the user.
68
+
69
+ Give the server the origin root. The hub reaches its own route by absolute
70
+ path, so the application cannot be hosted under a subpath such as
71
+ `example.com/myapp/`, and anything proxying in front of the server passes the
72
+ whole path space through unchanged.
73
+
74
+ Leave `express.json()` to `hubRoutes`, which mounts it on its own routes.
75
+
76
+ ## Running the server
77
+
78
+ Run the server under a TypeScript-capable runner. The builder writes
79
+ `dist/pages.ts`, which exports `hub`, as TypeScript:
80
+
81
+ ```json
82
+ {
83
+ "scripts": {
84
+ "dev": "loadbare-app-build --watch & tsx server.ts"
85
+ }
86
+ }
87
+ ```
88
+
89
+ Node 22.18 and later also runs it with no runner at all, since Node strips
90
+ TypeScript types itself. `dist/pages.ts` imports each `.requests.ts` and
91
+ `.queries.ts` file by its full name, extension included, because Node looks
92
+ for exactly the path an import names.
93
+
94
+ Enable `allowImportingTsExtensions` in the application's `tsconfig.json`
95
+ when `tsc` type-checks the server. Without it, `tsc` rejects the `.ts`
96
+ extensions in `dist/pages.ts`:
97
+
98
+ ```json
99
+ {
100
+ "compilerOptions": {
101
+ "noEmit": true,
102
+ "allowImportingTsExtensions": true
103
+ }
104
+ }
105
+ ```
106
+
107
+ Restart the server after adding or changing a `.requests.ts` or `.queries.ts`
108
+ file.
109
+
110
+ ## Database layer
111
+
112
+ Loadbare ships no data layer. The application opens its own database and hands
113
+ it to Loadbare as the request context.
114
+
115
+ The application writes `contextFor` and passes it to `hubRoutes`. Loadbare
116
+ calls it on every data request:
117
+
118
+ ```ts
119
+ // server.ts
120
+ function contextFor(req: Request): HubContext {
121
+ return { db: openDb(req.session.userId) };
122
+ }
123
+ ```
124
+
125
+ Open the handle in `contextFor` rather than once at startup, so that each
126
+ request works through a database opened for the caller it authenticated.
127
+
128
+ Declare what the context holds, once, anywhere in the application's own
129
+ source. Next to the database module is the natural place:
130
+
131
+ ```ts
132
+ // src/database.ts
133
+ declare module "@loadbare/app/server" {
134
+ interface HubContext {
135
+ db: Db;
136
+ }
137
+ }
138
+ ```
139
+
140
+ Add a field for anything else a request needs — the authenticated user, a
141
+ request id, a feature flag set. Queries and requests read them from `ctx`; see
142
+ [page files](./page-files.md).
@@ -0,0 +1,174 @@
1
+ # The Basic Widget Library
2
+
3
+ Seven widgets: `lb-input`, `lb-select`, `lb-list`, `lb-options`, `lb-table`,
4
+ `lb-picker`, `lb-unknown-page`. Every one of them is written against the same two contracts
5
+ documented elsewhere — [Custom Elements](./custom-elements.md#html) for its
6
+ definition, [Custom Elements](./custom-elements.md#code) for its class —
7
+ nothing here is special-cased machinery.
8
+
9
+ They ship compiled, in `@loadbare/widgets`, a package the builder resolves the
10
+ way it resolves anyone else's — it declares `"loadbare": { "widgets": "./dist" }`
11
+ and the builder scans that. Install it and list it:
12
+
13
+ ```
14
+ npm install @loadbare/widgets
15
+ ```
16
+
17
+ ```ts
18
+ // src/imports.ts
19
+ export default ["@loadbare/widgets"];
20
+ ```
21
+
22
+ See [Widgets from packages](./builder.md#widgets-from-packages) for what
23
+ listing a package does, and [The Builder](./builder.md#where-the-builder-looks)
24
+ for where a listed package sits in the cascade.
25
+
26
+ ## `lb-input`
27
+
28
+ Wraps an `<input>`. `lb-value` sets the input's `.value`. The widget sends
29
+ nothing on its own: `lb-action` names what the input's `change` sends. The
30
+ reserved `lb-row-update` saves the input's own cell; any other name sends that
31
+ action. Either carries the input's value, and the hub adds the scope the
32
+ input sits in.
33
+
34
+ An input inside an `lb-row-insert` or `lb-row-update` form leaves the attribute off.
35
+ The form reads every `lb-cell` in it on submit and sends one request for all
36
+ of them, so an input that also sent its own would write the same edit twice.
37
+
38
+ | Parameter | Fills |
39
+ | ---------- | ----- |
40
+ | `exp-label` | the visible `<label>` text |
41
+ | `exp-readonly` | the input's `readonly` attribute |
42
+
43
+ | Attribute | Asks for |
44
+ | ---------- | ----- |
45
+ | `lb-action` | what to send on `change`; `lb-row-update` to save the cell's own edit |
46
+
47
+ ## `lb-select`
48
+
49
+ Wraps a `<select>` whose `<option>`s the author writes directly inside (via
50
+ `lb-slot`). `lb-value` sets the select's `.value`; a `change` sends the
51
+ action named by `lb-action`, with the select's `.value` as the request's
52
+ `value` — the choice is the interaction, so this is the case where an
53
+ action carries a value.
54
+
55
+ | Parameter | Fills |
56
+ | ---------- | ----- |
57
+ | `exp-label` | the visible `<label>` text |
58
+
59
+ Requires `lb-action` — a change with none logs and sends nothing.
60
+
61
+ ## `lb-options`
62
+
63
+ A `<select>` whose `<option>`s come from a query instead of being written by
64
+ hand. The author supplies the row template inside the widget (via
65
+ `lb-slot`), same as any list widget:
66
+
67
+ ```html
68
+ <lb-options lb-list="statuses" exp-label="Status" lb-action="setStatus">
69
+ <template lb-key="id" data-group="category">
70
+ <option lb-cell="label"></option>
71
+ </template>
72
+ </lb-options>
73
+ ```
74
+
75
+ | Parameter | Fills |
76
+ | ---------- | ----- |
77
+ | `exp-label` | the visible `<label>` text |
78
+
79
+ - The row's key becomes the option's `value`: the `lb-key-value` the hub
80
+ stamps on each option is copied across, so the page names the key column
81
+ once, with `lb-key`.
82
+ - `data-group` on the row template sections the options into `<optgroup>`s,
83
+ one per distinct value, created and removed as rows arrive and leave.
84
+ - A `change` sends the action named by `lb-action`, value from the
85
+ select's `.value`.
86
+
87
+ `lb-picker` is this same class with its row template supplied by the
88
+ definition instead of the page — see below.
89
+
90
+ ## `lb-table`
91
+
92
+ A `<table>` that supplies its own scaffolding; the author supplies the
93
+ heading row, the row template, and optionally a footer, each as a
94
+ `<template>` matched to a destination:
95
+
96
+ ```html
97
+ <lb-table lb-list="ledger" exp-caption="Ledger">
98
+ <template lb-template="head">
99
+ <tr><th>Date</th><th>Amount</th></tr>
100
+ </template>
101
+ <template lb-key="id" data-sort="date" data-group="month">
102
+ <tr><td lb-cell="date"></td><td lb-cell="amount"></td></tr>
103
+ </template>
104
+ <template lb-template="foot">
105
+ <tr lb-row="ledger-total"><td>Total</td><td lb-cell="total"></td></tr>
106
+ </template>
107
+ </lb-table>
108
+ ```
109
+
110
+ | Parameter | Fills |
111
+ | ---------- | ----- |
112
+ | `exp-caption` | the `<caption>` text |
113
+
114
+ | Destination | Fills |
115
+ | ------------ | ----- |
116
+ | `head` (`lb-template="head"`) | the `<thead>` content |
117
+ | `foot` (`lb-template="foot"`) | the `<tfoot>` content |
118
+ | slot (no `lb-template`) | the row template, via `lb-slot` on `<tbody>` |
119
+
120
+ - `data-group` on the row template sections rows under a derived heading row,
121
+ one per distinct value, whose `colSpan` matches the row's own column
122
+ count. `data-sort` orders rows within a section (or the whole body, with no
123
+ grouping) by comparing each row's cell text.
124
+ - The `foot` destination is not delivered through `lbPlaceRow` — it's an
125
+ ordinary scope carrying its own `lb-row`, resolved by name like any
126
+ other on the page. A grand total is a second query over the same
127
+ data, not a row the hub hands the table.
128
+
129
+ ## `lb-picker`
130
+
131
+ `lb-options`, with the row template supplied by the definition instead of
132
+ the page — for when every row is one option and nothing else varies:
133
+
134
+ ```html
135
+ <lb-picker
136
+ lb-list="statuses"
137
+ exp-label="Status"
138
+ exp-key="id"
139
+ exp-cell="label"
140
+ exp-group="category"
141
+ lb-action="setStatus"
142
+ ></lb-picker>
143
+ ```
144
+
145
+ | Parameter | Fills |
146
+ | ---------- | ----- |
147
+ | `exp-label` | the visible `<label>` text |
148
+ | `exp-key` | the row template's `lb-key` |
149
+ | `exp-cell` | the option's `lb-cell` |
150
+ | `exp-group` | the row template's `data-group` |
151
+
152
+ Behavior — grouping, key-as-value, the action on change — is inherited
153
+ whole from `lb-options`; a page author who needs a second element in the
154
+ row, or an option built from two columns, writes `lb-options` and its own
155
+ `<template>` instead.
156
+
157
+ ## `lb-unknown-page`
158
+
159
+ The chrome's dialog for a URL that names no page, as one tag:
160
+
161
+ ```html
162
+ <lb-hub>
163
+ <nav>...</nav>
164
+ <main></main>
165
+ <lb-unknown-page></lb-unknown-page>
166
+ </lb-hub>
167
+ ```
168
+
169
+ It expands to a `<dialog lb-unknown-page>` scoped to the hub's own
170
+ `lb-navigation` query, so `page-label` and `page-uri` land in it the way any
171
+ cell lands anywhere, and the hub opens it on a miss. It takes no parameters
172
+ and, so far, shows both cells rather than choosing between them. See
173
+ [`chrome.html`](./chrome.md#where-the-page-is) for the query, and for the same
174
+ dialog written by hand.