@abide/abide 0.117.1 → 0.118.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.
- package/CHANGELOG.md +18 -0
- package/docs/REGISTRY.md +16 -14
- package/docs/RULEBOOK.md +49 -10
- package/docs/abide.md +246 -156
- package/package.json +1 -1
- package/scaffold/AGENTS.md +1 -1
- package/scaffold/src/getGreeting.test.ts +1 -1
- package/scaffold/src/index.abide +1 -1
- package/src/compiler/ShadowMap.ts +1 -1
- package/src/compiler/TypeSession.ts +177 -51
- package/src/compiler/appLayout.ts +52 -2
- package/src/compiler/appMap.ts +15 -0
- package/src/compiler/compileApp.ts +1 -1
- package/src/compiler/compiledEntry.ts +1 -1
- package/src/compiler/handlersUnder.ts +5 -2
- package/src/compiler/matchersOf.ts +147 -0
- package/src/compiler/mountsOf.ts +3 -0
- package/src/compiler/paramFile.ts +124 -0
- package/src/compiler/projectShadow.ts +1 -1
- package/src/compiler/readsIn.ts +90 -2
- package/src/compiler/serveLanguage.ts +2 -2
- package/src/server/answerRequest.ts +42 -5
- package/src/server/answerRpc.ts +3 -0
- package/src/server/appTree.ts +15 -8
- package/src/server/generatedSurfaces.ts +75 -7
- package/src/server/placement.ts +12 -2
- package/src/server/remote.ts +6 -2
- package/src/server/surfaceOf.ts +5 -1
- package/src/shared/filledPath.ts +16 -10
- package/src/shared/matchRoute.ts +16 -4
- package/src/shared/reactive/Reactive.ts +4 -0
- package/src/shared/routePattern.ts +66 -14
- package/src/shared/url.ts +24 -7
- /package/scaffold/src/{getGreeting.ts → getGreeting.handler.ts} +0 -0
package/docs/abide.md
CHANGED
|
@@ -310,7 +310,7 @@ A file's path below `src` is its address. Nothing else names a route.
|
|
|
310
310
|
| --- | --- |
|
|
311
311
|
| `src/index.abide` | `/` |
|
|
312
312
|
| `src/contacts/index.abide` | `/contacts` |
|
|
313
|
-
| `src/contacts/listContacts.ts` | `GET /contacts/listContacts`, the handler its default export declares |
|
|
313
|
+
| `src/contacts/listContacts.handler.ts` | `GET /contacts/listContacts`, the handler its default export declares |
|
|
314
314
|
| `src/contacts/lib/ContactList.abide` | no address: a component the pages import |
|
|
315
315
|
| `src/contacts/lib/ContactList.test.ts` | no address: what `bun test` runs |
|
|
316
316
|
|
|
@@ -335,7 +335,7 @@ Any other file beside the pages fails the build, so listing a directory lists it
|
|
|
335
335
|
```abide src/contacts/lib/ContactList.abide
|
|
336
336
|
<script>
|
|
337
337
|
import { reactive } from '@abide/abide'
|
|
338
|
-
import listContacts from '../listContacts.ts'
|
|
338
|
+
import listContacts from '../listContacts.handler.ts'
|
|
339
339
|
|
|
340
340
|
const contacts = reactive(() => listContacts())
|
|
341
341
|
</script>
|
|
@@ -347,7 +347,7 @@ const contacts = reactive(() => listContacts())
|
|
|
347
347
|
</ul>
|
|
348
348
|
```
|
|
349
349
|
|
|
350
|
-
```ts src/contacts/listContacts.ts
|
|
350
|
+
```ts src/contacts/listContacts.handler.ts
|
|
351
351
|
import { GET } from '@abide/abide/server'
|
|
352
352
|
import { CONTACTS } from './lib/CONTACTS.server.ts'
|
|
353
353
|
|
|
@@ -360,7 +360,7 @@ export default GET(async () => {
|
|
|
360
360
|
|
|
361
361
|
```ts src/contacts/lib/ContactList.test.ts
|
|
362
362
|
import { expect, test } from 'bun:test'
|
|
363
|
-
import listContacts from '../listContacts.ts'
|
|
363
|
+
import listContacts from '../listContacts.handler.ts'
|
|
364
364
|
|
|
365
365
|
test('the handler the list reads answers with every contact', async () => {
|
|
366
366
|
const contacts = await listContacts()
|
|
@@ -372,7 +372,7 @@ test('the handler the list reads answers with every contact', async () => {
|
|
|
372
372
|
})
|
|
373
373
|
```
|
|
374
374
|
|
|
375
|
-
`listContacts.ts` runs on the server. The list imports it like any module, and the browser is sent a call under the same name in its place, so there is no `fetch`, no route to register, and no second copy of the contact type.
|
|
375
|
+
`listContacts.handler.ts` runs on the server. The list imports it like any module, and the browser is sent a call under the same name in its place, so there is no `fetch`, no route to register, and no second copy of the contact type.
|
|
376
376
|
|
|
377
377
|
The test imports the same handler and calls it, in process. A handler only one page calls can live in that page's `<script server>` instead. See [Talk to the server](/guide/talk-to-the-server.md).
|
|
378
378
|
|
|
@@ -383,7 +383,7 @@ The test imports the same handler and calls it, in process. A handler only one p
|
|
|
383
383
|
```abide src/contacts/lib/PageTiming.abide
|
|
384
384
|
<script>
|
|
385
385
|
import { reactive } from '@abide/abide'
|
|
386
|
-
import listContacts from '../listContacts.ts'
|
|
386
|
+
import listContacts from '../listContacts.handler.ts'
|
|
387
387
|
|
|
388
388
|
let pageComplete = reactive('—')
|
|
389
389
|
let contactsAnswered = reactive('—')
|
|
@@ -473,7 +473,7 @@ Naming `starred` in the markup reads it and follows it from then on. Assigning t
|
|
|
473
473
|
```abide src/contacts/lib/ContactNote.abide
|
|
474
474
|
<script>
|
|
475
475
|
import { reactive } from '@abide/abide'
|
|
476
|
-
import listContacts from '../listContacts.ts'
|
|
476
|
+
import listContacts from '../listContacts.handler.ts'
|
|
477
477
|
|
|
478
478
|
// Owned, derived, loaded, and waiting for its first write.
|
|
479
479
|
let draft = reactive('')
|
|
@@ -1085,15 +1085,15 @@ Every server call in this chapter is a handler: a function wrapped in `GET`, `PO
|
|
|
1085
1085
|
|
|
1086
1086
|
## A handler file answers at its own path
|
|
1087
1087
|
|
|
1088
|
-
A
|
|
1088
|
+
A file named `<name>.handler.ts` beside the pages is a handler file. Its default export is the handler, and its path below `src` is the address it answers on.
|
|
1089
1089
|
|
|
1090
1090
|
| File below `src` | Answers on |
|
|
1091
1091
|
| --- | --- |
|
|
1092
|
-
| `**/<name>.ts` | its directory's route, then `<name>` |
|
|
1093
|
-
| `deals/getDeal.ts` | `GET /deals/getDeal` |
|
|
1094
|
-
| `users/getUser.ts` | `GET /users/getUser` |
|
|
1095
|
-
| `health.ts` | `GET /health` |
|
|
1096
|
-
| `media/[id]/save.ts` | `POST /media/42/save`, with `id` in its args |
|
|
1092
|
+
| `**/<name>.handler.ts` | its directory's route, then `<name>` |
|
|
1093
|
+
| `deals/getDeal.handler.ts` | `GET /deals/getDeal` |
|
|
1094
|
+
| `users/getUser.handler.ts` | `GET /users/getUser` |
|
|
1095
|
+
| `health.handler.ts` | `GET /health` |
|
|
1096
|
+
| `media/[id]/save.handler.ts` | `POST /media/42/save`, with `id` in its args |
|
|
1097
1097
|
| `media/[id]/lib/Comments.abide` | `/__abide/handlers/media/id/lib/Comments/addComment`, for the `addComment` its `<script server>` declares |
|
|
1098
1098
|
|
|
1099
1099
|
A handler file exports its handler and nothing else. A page imports it, a script calls its address, and the CLI and MCP offer it as a command and a tool.
|
|
@@ -1105,7 +1105,7 @@ A handler file exports its handler and nothing else. A page imports it, a script
|
|
|
1105
1105
|
```abide src/deals/lib/DealDetail.abide
|
|
1106
1106
|
<script>
|
|
1107
1107
|
import { reactive } from '@abide/abide'
|
|
1108
|
-
import getDeal from '../getDeal.ts'
|
|
1108
|
+
import getDeal from '../getDeal.handler.ts'
|
|
1109
1109
|
|
|
1110
1110
|
let id = reactive('d1')
|
|
1111
1111
|
|
|
@@ -1125,7 +1125,7 @@ const deal = reactive(() => getDeal({ id }))
|
|
|
1125
1125
|
</ul>
|
|
1126
1126
|
```
|
|
1127
1127
|
|
|
1128
|
-
```ts src/deals/getDeal.ts
|
|
1128
|
+
```ts src/deals/getDeal.handler.ts
|
|
1129
1129
|
import { refuse } from '@abide/abide'
|
|
1130
1130
|
import { GET } from '@abide/abide/server'
|
|
1131
1131
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
@@ -1143,7 +1143,7 @@ export default GET(
|
|
|
1143
1143
|
|
|
1144
1144
|
```ts src/deals/lib/DealDetail.test.ts
|
|
1145
1145
|
import { expect, test } from 'bun:test'
|
|
1146
|
-
import getDeal from '../getDeal.ts'
|
|
1146
|
+
import getDeal from '../getDeal.handler.ts'
|
|
1147
1147
|
|
|
1148
1148
|
test('the handler answers the deal its args name', async () => {
|
|
1149
1149
|
const deal = await getDeal({ id: 'd2' })
|
|
@@ -1226,7 +1226,7 @@ It still answers over http, below `/__abide/handlers` at its file's path and the
|
|
|
1226
1226
|
```abide src/deals/lib/NewDeal.abide
|
|
1227
1227
|
<script>
|
|
1228
1228
|
import { type Issues, reactive } from '@abide/abide'
|
|
1229
|
-
import createDeal from '../createDeal.ts'
|
|
1229
|
+
import createDeal from '../createDeal.handler.ts'
|
|
1230
1230
|
|
|
1231
1231
|
type NewDeal = { name: string; company: string; amount: number }
|
|
1232
1232
|
|
|
@@ -1261,7 +1261,7 @@ async function add() {
|
|
|
1261
1261
|
</ul>
|
|
1262
1262
|
```
|
|
1263
1263
|
|
|
1264
|
-
```ts src/deals/createDeal.ts
|
|
1264
|
+
```ts src/deals/createDeal.handler.ts
|
|
1265
1265
|
import { jsonSchema } from '@abide/abide'
|
|
1266
1266
|
import { POST } from '@abide/abide/server'
|
|
1267
1267
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
@@ -1341,8 +1341,8 @@ Two presses of Add inside one flight send one request and write one deal, and bo
|
|
|
1341
1341
|
```abide src/deals/lib/DealStage.abide
|
|
1342
1342
|
<script>
|
|
1343
1343
|
import { reactive } from '@abide/abide'
|
|
1344
|
-
import getDeal from '../getDeal.ts'
|
|
1345
|
-
import moveDeal from '../moveDeal.ts'
|
|
1344
|
+
import getDeal from '../getDeal.handler.ts'
|
|
1345
|
+
import moveDeal from '../moveDeal.handler.ts'
|
|
1346
1346
|
|
|
1347
1347
|
let id = reactive('d2')
|
|
1348
1348
|
let refusal = reactive('')
|
|
@@ -1381,7 +1381,7 @@ async function move(stage: string) {
|
|
|
1381
1381
|
<p class="status">{refusal}</p>
|
|
1382
1382
|
```
|
|
1383
1383
|
|
|
1384
|
-
```ts src/deals/moveDeal.ts
|
|
1384
|
+
```ts src/deals/moveDeal.handler.ts
|
|
1385
1385
|
import { jsonSchema, refuse } from '@abide/abide'
|
|
1386
1386
|
import { PATCH } from '@abide/abide/server'
|
|
1387
1387
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
@@ -1409,7 +1409,7 @@ export default PATCH(
|
|
|
1409
1409
|
|
|
1410
1410
|
```ts src/deals/lib/DealStage.test.ts
|
|
1411
1411
|
import { expect, test } from 'bun:test'
|
|
1412
|
-
import moveDeal from '../moveDeal.ts'
|
|
1412
|
+
import moveDeal from '../moveDeal.handler.ts'
|
|
1413
1413
|
|
|
1414
1414
|
test('a closed deal is refused by name, with its stage as data', async () => {
|
|
1415
1415
|
try {
|
|
@@ -1440,7 +1440,7 @@ Every refusal is a `Failed<Name, Data>`, in process and over the wire alike. The
|
|
|
1440
1440
|
|
|
1441
1441
|
```abide src/deals/lib/ExportDeals.abide
|
|
1442
1442
|
<script>
|
|
1443
|
-
import exportDeals from '../exportDeals.ts'
|
|
1443
|
+
import exportDeals from '../exportDeals.handler.ts'
|
|
1444
1444
|
</script>
|
|
1445
1445
|
|
|
1446
1446
|
<div class="row">
|
|
@@ -1448,7 +1448,7 @@ import exportDeals from '../exportDeals.ts'
|
|
|
1448
1448
|
</div>
|
|
1449
1449
|
```
|
|
1450
1450
|
|
|
1451
|
-
```ts src/deals/exportDeals.ts
|
|
1451
|
+
```ts src/deals/exportDeals.handler.ts
|
|
1452
1452
|
import { GET } from '@abide/abide/server'
|
|
1453
1453
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
1454
1454
|
|
|
@@ -1466,7 +1466,7 @@ export default GET(
|
|
|
1466
1466
|
|
|
1467
1467
|
```ts src/deals/lib/ExportDeals.test.ts
|
|
1468
1468
|
import { expect, test } from 'bun:test'
|
|
1469
|
-
import exportDeals from '../exportDeals.ts'
|
|
1469
|
+
import exportDeals from '../exportDeals.handler.ts'
|
|
1470
1470
|
|
|
1471
1471
|
test('the export answers a CSV file', async () => {
|
|
1472
1472
|
const file = await exportDeals()
|
|
@@ -1504,11 +1504,11 @@ Every answer carries headers you did not write:
|
|
|
1504
1504
|
```
|
|
1505
1505
|
abide routes
|
|
1506
1506
|
GET /deals deals/index.abide
|
|
1507
|
-
POST /deals/createDeal deals/createDeal.ts
|
|
1508
|
-
GET /deals/exportDeals deals/exportDeals.ts
|
|
1509
|
-
GET /deals/getDeal deals/getDeal.ts
|
|
1510
|
-
GET /deals/listDeals deals/listDeals.ts
|
|
1511
|
-
PATCH /deals/moveDeal deals/moveDeal.ts
|
|
1507
|
+
POST /deals/createDeal deals/createDeal.handler.ts
|
|
1508
|
+
GET /deals/exportDeals deals/exportDeals.handler.ts
|
|
1509
|
+
GET /deals/getDeal deals/getDeal.handler.ts
|
|
1510
|
+
GET /deals/listDeals deals/listDeals.handler.ts
|
|
1511
|
+
PATCH /deals/moveDeal deals/moveDeal.handler.ts
|
|
1512
1512
|
```
|
|
1513
1513
|
|
|
1514
1514
|
`abide routes [--json]` prints each address, its method and the file answering it, with every private handler marked `(private)`. With `--json` it prints the same as one document.
|
|
@@ -1520,7 +1520,7 @@ PATCH /deals/moveDeal deals/moveDeal.ts
|
|
|
1520
1520
|
```abide src/deals/lib/Pipeline.abide
|
|
1521
1521
|
<script>
|
|
1522
1522
|
import { reactive, refresh } from '@abide/abide'
|
|
1523
|
-
import listDeals from '../listDeals.ts'
|
|
1523
|
+
import listDeals from '../listDeals.handler.ts'
|
|
1524
1524
|
|
|
1525
1525
|
const deals = reactive(() => listDeals())
|
|
1526
1526
|
|
|
@@ -1544,7 +1544,7 @@ const total = reactive(() => {
|
|
|
1544
1544
|
</ul>
|
|
1545
1545
|
```
|
|
1546
1546
|
|
|
1547
|
-
```ts src/deals/listDeals.ts
|
|
1547
|
+
```ts src/deals/listDeals.handler.ts
|
|
1548
1548
|
import { GET } from '@abide/abide/server'
|
|
1549
1549
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
1550
1550
|
|
|
@@ -1561,7 +1561,7 @@ export default GET(
|
|
|
1561
1561
|
```ts src/deals/lib/Pipeline.test.ts
|
|
1562
1562
|
import { expect, test } from 'bun:test'
|
|
1563
1563
|
import { reactive, refresh } from '@abide/abide'
|
|
1564
|
-
import listDeals from '../listDeals.ts'
|
|
1564
|
+
import listDeals from '../listDeals.handler.ts'
|
|
1565
1565
|
|
|
1566
1566
|
test('a refresh is busy and still ready; a first load is not', async () => {
|
|
1567
1567
|
const deals = reactive(() => listDeals())
|
|
@@ -1620,7 +1620,7 @@ import StageValue from './StageValue.abide'
|
|
|
1620
1620
|
```abide src/pipeline/lib/StageCount.abide
|
|
1621
1621
|
<script>
|
|
1622
1622
|
import { reactive } from '@abide/abide'
|
|
1623
|
-
import getStage from '../getStage.ts'
|
|
1623
|
+
import getStage from '../getStage.handler.ts'
|
|
1624
1624
|
import { STAGES } from './STAGES.ts'
|
|
1625
1625
|
|
|
1626
1626
|
let stage = reactive('lead')
|
|
@@ -1642,7 +1642,7 @@ const totals = reactive(() => getStage({ stage }))
|
|
|
1642
1642
|
```abide src/pipeline/lib/StageValue.abide
|
|
1643
1643
|
<script>
|
|
1644
1644
|
import { reactive } from '@abide/abide'
|
|
1645
|
-
import getStage from '../getStage.ts'
|
|
1645
|
+
import getStage from '../getStage.handler.ts'
|
|
1646
1646
|
import { dollars } from './dollars.ts'
|
|
1647
1647
|
import { STAGES } from './STAGES.ts'
|
|
1648
1648
|
|
|
@@ -1663,7 +1663,7 @@ const totals = reactive(() => getStage({ stage }))
|
|
|
1663
1663
|
</ul>
|
|
1664
1664
|
```
|
|
1665
1665
|
|
|
1666
|
-
```ts src/pipeline/getStage.ts
|
|
1666
|
+
```ts src/pipeline/getStage.handler.ts
|
|
1667
1667
|
import { GET } from '@abide/abide/server'
|
|
1668
1668
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
1669
1669
|
import { LOADS } from './lib/LOADS.server.ts'
|
|
@@ -1690,7 +1690,7 @@ export default GET(
|
|
|
1690
1690
|
```ts src/pipeline/lib/StageTiles.test.ts
|
|
1691
1691
|
import { expect, test } from 'bun:test'
|
|
1692
1692
|
import { mount } from '@abide/harness/abide'
|
|
1693
|
-
import getStage from '../getStage.ts'
|
|
1693
|
+
import getStage from '../getStage.handler.ts'
|
|
1694
1694
|
import StageTiles from './StageTiles.abide'
|
|
1695
1695
|
import { LOADS } from './LOADS.server.ts'
|
|
1696
1696
|
|
|
@@ -1732,7 +1732,7 @@ A body with a parameter is a keyed value: one entry per set of arguments, held.
|
|
|
1732
1732
|
```abide src/pipeline/lib/Forecast.abide
|
|
1733
1733
|
<script>
|
|
1734
1734
|
import { reactive } from '@abide/abide'
|
|
1735
|
-
import getForecast from '../getForecast.ts'
|
|
1735
|
+
import getForecast from '../getForecast.handler.ts'
|
|
1736
1736
|
import { dollars } from './dollars.ts'
|
|
1737
1737
|
|
|
1738
1738
|
const forecast = reactive(() => getForecast())
|
|
@@ -1754,7 +1754,7 @@ const reloading = reactive(() => forecast.busy() && forecast.ready())
|
|
|
1754
1754
|
</div>
|
|
1755
1755
|
```
|
|
1756
1756
|
|
|
1757
|
-
```ts src/pipeline/getForecast.ts
|
|
1757
|
+
```ts src/pipeline/getForecast.handler.ts
|
|
1758
1758
|
import { GET } from '@abide/abide/server'
|
|
1759
1759
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
1760
1760
|
import { LOADS } from './lib/LOADS.server.ts'
|
|
@@ -1836,8 +1836,8 @@ Press Mark stale and then Reload now, and the forecast blanks between the two. T
|
|
|
1836
1836
|
|
|
1837
1837
|
```abide src/pipeline/lib/StageBoard.abide
|
|
1838
1838
|
<script>
|
|
1839
|
-
import getStage from '../getStage.ts'
|
|
1840
|
-
import winDeal from '../winDeal.ts'
|
|
1839
|
+
import getStage from '../getStage.handler.ts'
|
|
1840
|
+
import winDeal from '../winDeal.handler.ts'
|
|
1841
1841
|
import StageTile from './StageTile.abide'
|
|
1842
1842
|
import { STAGES } from './STAGES.ts'
|
|
1843
1843
|
|
|
@@ -1863,7 +1863,7 @@ async function win() {
|
|
|
1863
1863
|
```abide src/pipeline/lib/StageTile.abide
|
|
1864
1864
|
<script>
|
|
1865
1865
|
import { props, reactive } from '@abide/abide'
|
|
1866
|
-
import getStage from '../getStage.ts'
|
|
1866
|
+
import getStage from '../getStage.handler.ts'
|
|
1867
1867
|
|
|
1868
1868
|
const { stage } = props<{ stage: string }>()
|
|
1869
1869
|
const totals = reactive(() => getStage({ stage }))
|
|
@@ -1875,7 +1875,7 @@ const totals = reactive(() => getStage({ stage }))
|
|
|
1875
1875
|
</li>
|
|
1876
1876
|
```
|
|
1877
1877
|
|
|
1878
|
-
```ts src/pipeline/winDeal.ts
|
|
1878
|
+
```ts src/pipeline/winDeal.handler.ts
|
|
1879
1879
|
import { POST } from '@abide/abide/server'
|
|
1880
1880
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
1881
1881
|
|
|
@@ -1934,7 +1934,7 @@ The triggers also come as free functions, and those take a `Selection`: the set
|
|
|
1934
1934
|
```abide src/pipeline/lib/DealSearch.abide
|
|
1935
1935
|
<script>
|
|
1936
1936
|
import { reactive } from '@abide/abide'
|
|
1937
|
-
import searchDeals from '../searchDeals.ts'
|
|
1937
|
+
import searchDeals from '../searchDeals.handler.ts'
|
|
1938
1938
|
|
|
1939
1939
|
let q = reactive('')
|
|
1940
1940
|
const found = reactive(() => searchDeals({ q }), { debounce: 300 })
|
|
@@ -1951,7 +1951,7 @@ const found = reactive(() => searchDeals({ q }), { debounce: 300 })
|
|
|
1951
1951
|
</ul>
|
|
1952
1952
|
```
|
|
1953
1953
|
|
|
1954
|
-
```ts src/pipeline/searchDeals.ts
|
|
1954
|
+
```ts src/pipeline/searchDeals.handler.ts
|
|
1955
1955
|
import { GET } from '@abide/abide/server'
|
|
1956
1956
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
1957
1957
|
import { LOADS } from './lib/LOADS.server.ts'
|
|
@@ -2020,7 +2020,7 @@ Every keystroke is a new `q`, and so a new key. `debounce: 300` holds the search
|
|
|
2020
2020
|
```abide src/pipeline/lib/DealFeed.abide
|
|
2021
2021
|
<script>
|
|
2022
2022
|
import { reactive } from '@abide/abide'
|
|
2023
|
-
import listDeals from '../listDeals.ts'
|
|
2023
|
+
import listDeals from '../listDeals.handler.ts'
|
|
2024
2024
|
import { dollars } from './dollars.ts'
|
|
2025
2025
|
|
|
2026
2026
|
const deals = reactive(() => listDeals())
|
|
@@ -2055,7 +2055,7 @@ function applyNextUpdate() {
|
|
|
2055
2055
|
</div>
|
|
2056
2056
|
```
|
|
2057
2057
|
|
|
2058
|
-
```ts src/pipeline/listDeals.ts
|
|
2058
|
+
```ts src/pipeline/listDeals.handler.ts
|
|
2059
2059
|
import { GET } from '@abide/abide/server'
|
|
2060
2060
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
2061
2061
|
|
|
@@ -2109,7 +2109,7 @@ Change the held value without `patch` and nothing on the page moves, because no
|
|
|
2109
2109
|
```abide src/pipeline/lib/DealPicker.abide
|
|
2110
2110
|
<script>
|
|
2111
2111
|
import { reactive } from '@abide/abide'
|
|
2112
|
-
import listDeals from '../listDeals.ts'
|
|
2112
|
+
import listDeals from '../listDeals.handler.ts'
|
|
2113
2113
|
import DealDetail from './DealDetail.abide'
|
|
2114
2114
|
import DealRow from './DealRow.abide'
|
|
2115
2115
|
|
|
@@ -2145,7 +2145,7 @@ let selected = reactive.share('selectedDeal', () => reactive(''))
|
|
|
2145
2145
|
```abide src/pipeline/lib/DealDetail.abide
|
|
2146
2146
|
<script>
|
|
2147
2147
|
import { reactive } from '@abide/abide'
|
|
2148
|
-
import listDeals from '../listDeals.ts'
|
|
2148
|
+
import listDeals from '../listDeals.handler.ts'
|
|
2149
2149
|
|
|
2150
2150
|
const deals = reactive(() => listDeals())
|
|
2151
2151
|
const selected = reactive.share('selectedDeal', () => reactive(''))
|
|
@@ -2193,7 +2193,7 @@ Use the value `reactive.share` returns, and no other name. A value lives in the
|
|
|
2193
2193
|
```abide src/pipeline/lib/DealNote.abide
|
|
2194
2194
|
<script>
|
|
2195
2195
|
import { reactive, watch } from '@abide/abide'
|
|
2196
|
-
import saveNote from '../saveNote.ts'
|
|
2196
|
+
import saveNote from '../saveNote.handler.ts'
|
|
2197
2197
|
|
|
2198
2198
|
let note = reactive('Wants a quote by Friday.')
|
|
2199
2199
|
let saved = reactive('Wants a quote by Friday.')
|
|
@@ -2220,7 +2220,7 @@ watch(() => {
|
|
|
2220
2220
|
</ul>
|
|
2221
2221
|
```
|
|
2222
2222
|
|
|
2223
|
-
```ts src/pipeline/saveNote.ts
|
|
2223
|
+
```ts src/pipeline/saveNote.handler.ts
|
|
2224
2224
|
import { POST } from '@abide/abide/server'
|
|
2225
2225
|
import { LOADS } from './lib/LOADS.server.ts'
|
|
2226
2226
|
import { NOTES } from './lib/NOTES.server.ts'
|
|
@@ -2313,8 +2313,8 @@ let view = reactive('board', { store: preferenceStore('view') })
|
|
|
2313
2313
|
|
|
2314
2314
|
```ts src/pipeline/lib/preferenceStore.ts
|
|
2315
2315
|
import type { Store } from '@abide/abide'
|
|
2316
|
-
import getPreference from '../getPreference.ts'
|
|
2317
|
-
import setPreference from '../setPreference.ts'
|
|
2316
|
+
import getPreference from '../getPreference.handler.ts'
|
|
2317
|
+
import setPreference from '../setPreference.handler.ts'
|
|
2318
2318
|
|
|
2319
2319
|
/** Where a preference lives between visits: both halves, or neither. */
|
|
2320
2320
|
export function preferenceStore(name: string): Store<string> {
|
|
@@ -2330,7 +2330,7 @@ export function preferenceStore(name: string): Store<string> {
|
|
|
2330
2330
|
}
|
|
2331
2331
|
```
|
|
2332
2332
|
|
|
2333
|
-
```ts src/pipeline/getPreference.ts
|
|
2333
|
+
```ts src/pipeline/getPreference.handler.ts
|
|
2334
2334
|
import { GET } from '@abide/abide/server'
|
|
2335
2335
|
import { PREFERENCES } from './lib/PREFERENCES.server.ts'
|
|
2336
2336
|
|
|
@@ -2345,7 +2345,7 @@ export default GET(
|
|
|
2345
2345
|
)
|
|
2346
2346
|
```
|
|
2347
2347
|
|
|
2348
|
-
```ts src/pipeline/setPreference.ts
|
|
2348
|
+
```ts src/pipeline/setPreference.handler.ts
|
|
2349
2349
|
import { POST } from '@abide/abide/server'
|
|
2350
2350
|
import { PREFERENCES } from './lib/PREFERENCES.server.ts'
|
|
2351
2351
|
|
|
@@ -2567,8 +2567,8 @@ A value keeps no past of its own. Where you want what came before, make the valu
|
|
|
2567
2567
|
```abide src/activity/lib/AccountFeed.abide
|
|
2568
2568
|
<script>
|
|
2569
2569
|
import { reactive } from '@abide/abide'
|
|
2570
|
-
import feed from '../feed.ts'
|
|
2571
|
-
import logToFeed from '../logToFeed.ts'
|
|
2570
|
+
import feed from '../feed.handler.ts'
|
|
2571
|
+
import logToFeed from '../logToFeed.handler.ts'
|
|
2572
2572
|
|
|
2573
2573
|
const account = 'hopper-co'
|
|
2574
2574
|
const entries = reactive(() => feed({ account }))
|
|
@@ -2600,7 +2600,7 @@ export const feeds = reactive((_account: { account: string }): Entry[] => [], {
|
|
|
2600
2600
|
})
|
|
2601
2601
|
```
|
|
2602
2602
|
|
|
2603
|
-
```ts src/activity/feed.ts
|
|
2603
|
+
```ts src/activity/feed.handler.ts
|
|
2604
2604
|
import { GET } from '@abide/abide/server'
|
|
2605
2605
|
import { feeds } from './lib/feeds.ts'
|
|
2606
2606
|
|
|
@@ -2608,7 +2608,7 @@ import { feeds } from './lib/feeds.ts'
|
|
|
2608
2608
|
export default GET(feeds, { description: "An account's activity feed." })
|
|
2609
2609
|
```
|
|
2610
2610
|
|
|
2611
|
-
```ts src/activity/logToFeed.ts
|
|
2611
|
+
```ts src/activity/logToFeed.handler.ts
|
|
2612
2612
|
import { POST } from '@abide/abide/server'
|
|
2613
2613
|
import { feeds } from './lib/feeds.ts'
|
|
2614
2614
|
|
|
@@ -2674,7 +2674,7 @@ The feed lives in memory. A restart starts it empty, and each instance of the ap
|
|
|
2674
2674
|
```abide src/activity/lib/ActivityHistory.abide
|
|
2675
2675
|
<script>
|
|
2676
2676
|
import { reactive, refresh } from '@abide/abide'
|
|
2677
|
-
import olderActivity from '../olderActivity.ts'
|
|
2677
|
+
import olderActivity from '../olderActivity.handler.ts'
|
|
2678
2678
|
|
|
2679
2679
|
const entries = reactive(() => olderActivity())
|
|
2680
2680
|
</script>
|
|
@@ -2691,7 +2691,7 @@ const entries = reactive(() => olderActivity())
|
|
|
2691
2691
|
</div>
|
|
2692
2692
|
```
|
|
2693
2693
|
|
|
2694
|
-
```ts src/activity/olderActivity.ts
|
|
2694
|
+
```ts src/activity/olderActivity.handler.ts
|
|
2695
2695
|
import { GET } from '@abide/abide/server'
|
|
2696
2696
|
|
|
2697
2697
|
const OLDER = [
|
|
@@ -2715,7 +2715,7 @@ export default GET(
|
|
|
2715
2715
|
|
|
2716
2716
|
```ts src/activity/lib/ActivityHistory.test.ts
|
|
2717
2717
|
import { expect, test } from 'bun:test'
|
|
2718
|
-
import olderActivity from '../olderActivity.ts'
|
|
2718
|
+
import olderActivity from '../olderActivity.handler.ts'
|
|
2719
2719
|
|
|
2720
2720
|
test('the first entry is in hand while the rest are still read', async () => {
|
|
2721
2721
|
const entries = olderActivity()
|
|
@@ -2746,7 +2746,7 @@ A page rendered on the server is not held for an unfinished stream. What the str
|
|
|
2746
2746
|
```abide src/activity/lib/CsvExport.abide
|
|
2747
2747
|
<script>
|
|
2748
2748
|
import { reactive } from '@abide/abide'
|
|
2749
|
-
import exportCsv from '../exportCsv.ts'
|
|
2749
|
+
import exportCsv from '../exportCsv.handler.ts'
|
|
2750
2750
|
|
|
2751
2751
|
const csv = reactive(async () => (await exportCsv.raw()).text())
|
|
2752
2752
|
</script>
|
|
@@ -2759,7 +2759,7 @@ const csv = reactive(async () => (await exportCsv.raw()).text())
|
|
|
2759
2759
|
</div>
|
|
2760
2760
|
```
|
|
2761
2761
|
|
|
2762
|
-
```ts src/activity/exportCsv.ts
|
|
2762
|
+
```ts src/activity/exportCsv.handler.ts
|
|
2763
2763
|
import { bytes, GET } from '@abide/abide/server'
|
|
2764
2764
|
|
|
2765
2765
|
const ENCODER = new TextEncoder()
|
|
@@ -2819,7 +2819,7 @@ A stream whose first chunk is binary is sent as bytes without the helper. `bytes
|
|
|
2819
2819
|
```abide src/activity/lib/Wallboard.abide
|
|
2820
2820
|
<script>
|
|
2821
2821
|
import { reactive } from '@abide/abide'
|
|
2822
|
-
import ticker from '../ticker.ts'
|
|
2822
|
+
import ticker from '../ticker.handler.ts'
|
|
2823
2823
|
|
|
2824
2824
|
// A plain request, as a wallboard sends it: no `Accept` asks for events.
|
|
2825
2825
|
const answer = reactive(async () => {
|
|
@@ -2835,7 +2835,7 @@ const answer = reactive(async () => {
|
|
|
2835
2835
|
<pre class="rounded-md border border-rule bg-raised p-3">{answer.body}</pre>
|
|
2836
2836
|
```
|
|
2837
2837
|
|
|
2838
|
-
```ts src/activity/ticker.ts
|
|
2838
|
+
```ts src/activity/ticker.handler.ts
|
|
2839
2839
|
import { GET, sse } from '@abide/abide/server'
|
|
2840
2840
|
|
|
2841
2841
|
const MOVES = [
|
|
@@ -2912,10 +2912,11 @@ The CRM outgrows one page. This chapter adds a page per company, the chrome they
|
|
|
2912
2912
|
| File | Answers on |
|
|
2913
2913
|
| --- | --- |
|
|
2914
2914
|
| `src/companies/index.abide` | `/companies` |
|
|
2915
|
-
| `src/companies/[id].abide` | `/companies/hopper-and-co`, and every other id |
|
|
2915
|
+
| `src/companies/[id=SLUG].abide` | `/companies/hopper-and-co`, and every other id |
|
|
2916
|
+
| `src/companies/SLUG.param.ts` | no address: it says what a segment naming `SLUG` takes |
|
|
2916
2917
|
| `src/companies/layout.abide` | no address: it wraps every page at `/companies` and below |
|
|
2917
2918
|
| `src/companies/error.abide` | no address: it answers when a page below it fails |
|
|
2918
|
-
| `src/companies/getCompany.ts` | `GET /companies/getCompany` |
|
|
2919
|
+
| `src/companies/getCompany.handler.ts` | `GET /companies/getCompany` |
|
|
2919
2920
|
|
|
2920
2921
|
An `**/index.abide` answers its directory's own address, and any other `**/<name>.abide` adds its name. A part of the path in brackets is a value the page reads:
|
|
2921
2922
|
|
|
@@ -2925,9 +2926,9 @@ An `**/index.abide` answers its directory's own address, and any other `**/<name
|
|
|
2925
2926
|
| `[[name]]` | one segment or none | text, absent when nothing matched |
|
|
2926
2927
|
| `[...name]` | every remaining segment | a list, empty when nothing matched |
|
|
2927
2928
|
|
|
2928
|
-
## `[id].abide` answers for every company
|
|
2929
|
+
## `[id=SLUG].abide` answers for every company
|
|
2929
2930
|
|
|
2930
|
-
**`[id].abide` — one file for every company** — Two companies, one page file. Pick one: the address changes, and the page loads the company the address names.
|
|
2931
|
+
**`[id=SLUG].abide` — one file for every company** — Two companies, one page file. Pick one: the address changes, and the page loads the company the address names.
|
|
2931
2932
|
|
|
2932
2933
|
```abide src/companies/lib/CompanyPages.abide
|
|
2933
2934
|
<script>
|
|
@@ -2936,7 +2937,7 @@ import { reactive, url } from '@abide/abide'
|
|
|
2936
2937
|
const IDS = ['analytical-engines', 'hopper-and-co']
|
|
2937
2938
|
|
|
2938
2939
|
let open = reactive('hopper-and-co')
|
|
2939
|
-
const address = reactive(() => url('/companies/[id]', { id: open }))
|
|
2940
|
+
const address = reactive(() => url('/companies/[id=SLUG]', { id: open }))
|
|
2940
2941
|
</script>
|
|
2941
2942
|
|
|
2942
2943
|
<div class="tabs">
|
|
@@ -2950,10 +2951,10 @@ const address = reactive(() => url('/companies/[id]', { id: open }))
|
|
|
2950
2951
|
class="h-60 w-full rounded-md border border-rule"></iframe>
|
|
2951
2952
|
```
|
|
2952
2953
|
|
|
2953
|
-
```abide src/companies/[id].abide
|
|
2954
|
+
```abide src/companies/[id=SLUG].abide
|
|
2954
2955
|
<script>
|
|
2955
2956
|
import { reactive, route } from '@abide/abide'
|
|
2956
|
-
import getCompany from './getCompany.ts'
|
|
2957
|
+
import getCompany from './getCompany.handler.ts'
|
|
2957
2958
|
import CompanyTabs from './lib/CompanyTabs.abide'
|
|
2958
2959
|
|
|
2959
2960
|
const company = reactive(() => getCompany({ id: route.params.id }))
|
|
@@ -2995,10 +2996,97 @@ test('one file answers for every company, reading its id', async () => {
|
|
|
2995
2996
|
|
|
2996
2997
|
| Name | What it does for the CRM |
|
|
2997
2998
|
| --- | --- |
|
|
2998
|
-
| `route.name` | the pattern that matched, `/companies/[id]` for every company, so a log line keys on the page rather than on each address |
|
|
2999
|
+
| `route.name` | the pattern that matched, `/companies/[id=SLUG]` for every company, so a log line keys on the page rather than on each address |
|
|
2999
3000
|
| `Params` | what the segments matched; a query string is never in it |
|
|
3000
3001
|
| `RouteParams<P>` | the type `route.params` has in a page or layout filed under `P`, so `id` is text and a key the route lacks is an error. A component under `lib/` has no route of its own and reads the loose `Params`: pass it the value as a prop, or narrow it with `RouteParams<'/…'>` |
|
|
3001
3002
|
|
|
3003
|
+
## A param file says what an id looks like
|
|
3004
|
+
|
|
3005
|
+
**`SLUG.param.ts` — an address that cannot be an id never reaches the page** — A rep pastes a company's name where its id goes. No id has a capital or a space, so no page takes the address, and nothing asks the database for it.
|
|
3006
|
+
|
|
3007
|
+
```abide src/companies/lib/PastedName.abide
|
|
3008
|
+
<script>
|
|
3009
|
+
import { reactive, url } from '@abide/abide'
|
|
3010
|
+
|
|
3011
|
+
const PASTED = ['hopper-and-co', 'Hopper & Co']
|
|
3012
|
+
|
|
3013
|
+
let open = reactive('Hopper & Co')
|
|
3014
|
+
const address = reactive(() => url('/companies/[id=SLUG]', { id: open }))
|
|
3015
|
+
</script>
|
|
3016
|
+
|
|
3017
|
+
<div class="tabs">
|
|
3018
|
+
{#for id of PASTED}
|
|
3019
|
+
<button aria-selected={open === id}
|
|
3020
|
+
onclick={() => (open = id)}>{id}</button>
|
|
3021
|
+
{/for}
|
|
3022
|
+
</div>
|
|
3023
|
+
<p class="status">{address}</p>
|
|
3024
|
+
<iframe src={address} title="An address the company page may not take"
|
|
3025
|
+
class="h-48 w-full rounded-md border border-rule"></iframe>
|
|
3026
|
+
```
|
|
3027
|
+
|
|
3028
|
+
```ts src/companies/SLUG.param.ts
|
|
3029
|
+
// A company's id: lowercase words and digits, joined by hyphens.
|
|
3030
|
+
export const SLUG = /[a-z0-9-]+/
|
|
3031
|
+
```
|
|
3032
|
+
|
|
3033
|
+
```abide src/companies/[id=SLUG].abide
|
|
3034
|
+
<script>
|
|
3035
|
+
import { reactive, route } from '@abide/abide'
|
|
3036
|
+
import getCompany from './getCompany.handler.ts'
|
|
3037
|
+
import CompanyTabs from './lib/CompanyTabs.abide'
|
|
3038
|
+
|
|
3039
|
+
const company = reactive(() => getCompany({ id: route.params.id }))
|
|
3040
|
+
</script>
|
|
3041
|
+
|
|
3042
|
+
<head><title>{company.name} — CRM</title></head>
|
|
3043
|
+
|
|
3044
|
+
<h2 class="text-heading font-semibold"
|
|
3045
|
+
style:view-transition-name="company-{route.params.id}">{company.name}</h2>
|
|
3046
|
+
<ul class="fields">
|
|
3047
|
+
<li><span>contact</span><strong>{company.contact}</strong></li>
|
|
3048
|
+
<li><span>pipeline</span><strong>{company.pipeline}</strong></li>
|
|
3049
|
+
</ul>
|
|
3050
|
+
<CompanyTabs />
|
|
3051
|
+
|
|
3052
|
+
<style>
|
|
3053
|
+
::view-transition-group(*) { animation-duration: 0.5s; }
|
|
3054
|
+
</style>
|
|
3055
|
+
```
|
|
3056
|
+
|
|
3057
|
+
```ts src/companies/Matcher.test.ts
|
|
3058
|
+
import { expect, test } from 'bun:test'
|
|
3059
|
+
import { createApp } from '@abide/abide/server'
|
|
3060
|
+
import { COMPANIES } from './lib/COMPANIES.server.ts'
|
|
3061
|
+
|
|
3062
|
+
test('the company page takes every id the CRM has, and no name', async () => {
|
|
3063
|
+
await using app = await createApp()
|
|
3064
|
+
for (const { id } of COMPANIES) {
|
|
3065
|
+
const answer = await app.fetch(
|
|
3066
|
+
new Request(`http://crm.invalid/companies/${id}`),
|
|
3067
|
+
)
|
|
3068
|
+
expect(answer.status).toBe(200)
|
|
3069
|
+
await answer.body?.cancel()
|
|
3070
|
+
}
|
|
3071
|
+
const pasted = await app.fetch(
|
|
3072
|
+
new Request('http://crm.invalid/companies/Hopper%20%26%20Co'),
|
|
3073
|
+
)
|
|
3074
|
+
expect(pasted.status).toBe(404)
|
|
3075
|
+
})
|
|
3076
|
+
```
|
|
3077
|
+
|
|
3078
|
+
A segment names its matcher after an `=`. `[id=SLUG]` takes a part only where the whole of it, decoded, matches the `SLUG` in `SLUG.param.ts`, found beside the page or in the nearest directory above it. A part it refuses goes to the next route that could answer the address. The companies have none, so `Hopper & Co` is a 404, painted by `error.abide`.
|
|
3079
|
+
|
|
3080
|
+
| Named | Matches |
|
|
3081
|
+
| --- | --- |
|
|
3082
|
+
| `[name=NAME]` | one segment the matcher takes |
|
|
3083
|
+
| `[[name=NAME]]` | one segment the matcher takes, or none |
|
|
3084
|
+
| `[...name=NAME]` | every remaining segment, each one the matcher takes |
|
|
3085
|
+
|
|
3086
|
+
The build reads a param file without running it, so it holds one `export const` named as the file, a regular expression with no flags, and no import. A pattern that repeats a group that itself repeats, or that refers back to a group, is refused: either can make one crafted address slow to test.
|
|
3087
|
+
|
|
3088
|
+
A segment with no matcher never takes a part with a period in it, which is why `/favicon.ico` never reaches a company page. A matcher replaces that rule for its segment, so one that allows a period lets `url` write one too. `.` and `..` are never taken. Under `abide check`, a matcher that is a choice of words, `/deals|notes/`, types the param as those words in `route.params` and in `url`.
|
|
3089
|
+
|
|
3002
3090
|
## A layout holds what the companies pages share
|
|
3003
3091
|
|
|
3004
3092
|
**`layout.abide` — the header the companies pages share, built once** — The companies header holds a note for the call you are on. Type one, then open a company and come back: the page changes, and the note stays.
|
|
@@ -3057,7 +3145,7 @@ A package can ship pages too. `import '…' with { type: 'routes' }` in a file o
|
|
|
3057
3145
|
|
|
3058
3146
|
## A link names a route, and `url` fills it in
|
|
3059
3147
|
|
|
3060
|
-
The list links each company with `url('/companies/[id]', { id: company.id })`. You write the pattern a page is filed under rather than the address, and `url` fills in the segments. Leaving out a required one is a compile error, and a pattern no page is filed under is refused by `abide check`. A query goes last: `url('/companies', {}, { city: 'London' })`.
|
|
3148
|
+
The list links each company with `url('/companies/[id=SLUG]', { id: company.id })`. You write the pattern a page is filed under, matcher and all, rather than the address, and `url` fills in the segments. Leaving out a required one is a compile error, and a pattern no page is filed under is refused by `abide check`. A query goes last: `url('/companies', {}, { city: 'London' })`.
|
|
3061
3149
|
|
|
3062
3150
|
| Name | What it does for the CRM |
|
|
3063
3151
|
| --- | --- |
|
|
@@ -3137,7 +3225,7 @@ import { url } from '@abide/abide'
|
|
|
3137
3225
|
```abide src/companies/index.abide
|
|
3138
3226
|
<script>
|
|
3139
3227
|
import { reactive, url } from '@abide/abide'
|
|
3140
|
-
import listCompanies from './listCompanies.ts'
|
|
3228
|
+
import listCompanies from './listCompanies.handler.ts'
|
|
3141
3229
|
|
|
3142
3230
|
const companies = reactive(() => listCompanies())
|
|
3143
3231
|
</script>
|
|
@@ -3145,7 +3233,7 @@ const companies = reactive(() => listCompanies())
|
|
|
3145
3233
|
<ul class="fields">
|
|
3146
3234
|
{#for company of companies by company.id}
|
|
3147
3235
|
<li><span>{company.city}</span>
|
|
3148
|
-
<a href={url('/companies/[id]', { id: company.id })}
|
|
3236
|
+
<a href={url('/companies/[id=SLUG]', { id: company.id })}
|
|
3149
3237
|
style:view-transition-name="company-{company.id}"
|
|
3150
3238
|
>{company.name}</a></li>
|
|
3151
3239
|
{/for}
|
|
@@ -3156,10 +3244,10 @@ const companies = reactive(() => listCompanies())
|
|
|
3156
3244
|
</style>
|
|
3157
3245
|
```
|
|
3158
3246
|
|
|
3159
|
-
```abide src/companies/[id].abide
|
|
3247
|
+
```abide src/companies/[id=SLUG].abide
|
|
3160
3248
|
<script>
|
|
3161
3249
|
import { reactive, route } from '@abide/abide'
|
|
3162
|
-
import getCompany from './getCompany.ts'
|
|
3250
|
+
import getCompany from './getCompany.handler.ts'
|
|
3163
3251
|
import CompanyTabs from './lib/CompanyTabs.abide'
|
|
3164
3252
|
|
|
3165
3253
|
const company = reactive(() => getCompany({ id: route.params.id }))
|
|
@@ -3209,7 +3297,7 @@ const PAGES = [
|
|
|
3209
3297
|
{ label: 'All companies', address: url('/companies') },
|
|
3210
3298
|
{
|
|
3211
3299
|
label: 'Hopper & Co',
|
|
3212
|
-
address: url('/companies/[id]', { id: 'hopper-and-co' }),
|
|
3300
|
+
address: url('/companies/[id=SLUG]', { id: 'hopper-and-co' }),
|
|
3213
3301
|
},
|
|
3214
3302
|
]
|
|
3215
3303
|
|
|
@@ -3275,10 +3363,10 @@ let note = reactive('')
|
|
|
3275
3363
|
<slot/>
|
|
3276
3364
|
```
|
|
3277
3365
|
|
|
3278
|
-
```abide src/companies/[id].abide
|
|
3366
|
+
```abide src/companies/[id=SLUG].abide
|
|
3279
3367
|
<script>
|
|
3280
3368
|
import { reactive, route } from '@abide/abide'
|
|
3281
|
-
import getCompany from './getCompany.ts'
|
|
3369
|
+
import getCompany from './getCompany.handler.ts'
|
|
3282
3370
|
import CompanyTabs from './lib/CompanyTabs.abide'
|
|
3283
3371
|
|
|
3284
3372
|
const company = reactive(() => getCompany({ id: route.params.id }))
|
|
@@ -3329,7 +3417,7 @@ A `<head>` sits at the top level of a file, never inside a block. A `<title>`, a
|
|
|
3329
3417
|
<script>
|
|
3330
3418
|
import { reactive, url } from '@abide/abide'
|
|
3331
3419
|
|
|
3332
|
-
const PAGE = url('/companies/[id]', { id: 'hopper-and-co' })
|
|
3420
|
+
const PAGE = url('/companies/[id=SLUG]', { id: 'hopper-and-co' })
|
|
3333
3421
|
|
|
3334
3422
|
let loads = reactive(1)
|
|
3335
3423
|
</script>
|
|
@@ -3341,10 +3429,10 @@ let loads = reactive(1)
|
|
|
3341
3429
|
class="h-60 w-full rounded-md border border-rule"></iframe>
|
|
3342
3430
|
```
|
|
3343
3431
|
|
|
3344
|
-
```abide src/companies/[id].abide
|
|
3432
|
+
```abide src/companies/[id=SLUG].abide
|
|
3345
3433
|
<script>
|
|
3346
3434
|
import { reactive, route } from '@abide/abide'
|
|
3347
|
-
import getCompany from './getCompany.ts'
|
|
3435
|
+
import getCompany from './getCompany.handler.ts'
|
|
3348
3436
|
import CompanyTabs from './lib/CompanyTabs.abide'
|
|
3349
3437
|
|
|
3350
3438
|
const company = reactive(() => getCompany({ id: route.params.id }))
|
|
@@ -3365,7 +3453,7 @@ const company = reactive(() => getCompany({ id: route.params.id }))
|
|
|
3365
3453
|
</style>
|
|
3366
3454
|
```
|
|
3367
3455
|
|
|
3368
|
-
```ts src/companies/getCompany.ts
|
|
3456
|
+
```ts src/companies/getCompany.handler.ts
|
|
3369
3457
|
import { GET, notFound } from '@abide/abide/server'
|
|
3370
3458
|
import { COMPANIES } from './lib/COMPANIES.server.ts'
|
|
3371
3459
|
|
|
@@ -3419,7 +3507,7 @@ Nothing holds the document for a value. The page gets one turn to start its load
|
|
|
3419
3507
|
import { url } from '@abide/abide'
|
|
3420
3508
|
</script>
|
|
3421
3509
|
|
|
3422
|
-
<iframe src={url('/companies/[id]', { id: 'initech' })}
|
|
3510
|
+
<iframe src={url('/companies/[id=SLUG]', { id: 'initech' })}
|
|
3423
3511
|
title="A company that is not there"
|
|
3424
3512
|
class="h-48 w-full rounded-md border border-rule"></iframe>
|
|
3425
3513
|
```
|
|
@@ -3440,7 +3528,7 @@ const { failure } = props<{ failure: Failed<string, unknown> }>()
|
|
|
3440
3528
|
{/if}
|
|
3441
3529
|
```
|
|
3442
3530
|
|
|
3443
|
-
```ts src/companies/getCompany.ts
|
|
3531
|
+
```ts src/companies/getCompany.handler.ts
|
|
3444
3532
|
import { GET, notFound } from '@abide/abide/server'
|
|
3445
3533
|
import { COMPANIES } from './lib/COMPANIES.server.ts'
|
|
3446
3534
|
|
|
@@ -3483,8 +3571,8 @@ The refusal has to come in the page's first turn to set the status. That is why
|
|
|
3483
3571
|
```abide src/companies/lib/ExportButton.abide
|
|
3484
3572
|
<script>
|
|
3485
3573
|
import { isFailed, reactive, refresh } from '@abide/abide'
|
|
3486
|
-
import exportCompanies from '../exportCompanies.ts'
|
|
3487
|
-
import reportedErrors from '../reportedErrors.ts'
|
|
3574
|
+
import exportCompanies from '../exportCompanies.handler.ts'
|
|
3575
|
+
import reportedErrors from '../reportedErrors.handler.ts'
|
|
3488
3576
|
|
|
3489
3577
|
let told = reactive('—')
|
|
3490
3578
|
const reported = reactive(() => reportedErrors())
|
|
@@ -3509,7 +3597,7 @@ async function runExport() {
|
|
|
3509
3597
|
</ul>
|
|
3510
3598
|
```
|
|
3511
3599
|
|
|
3512
|
-
```ts src/companies/exportCompanies.ts
|
|
3600
|
+
```ts src/companies/exportCompanies.handler.ts
|
|
3513
3601
|
import { POST } from '@abide/abide/server'
|
|
3514
3602
|
|
|
3515
3603
|
/** Answers at /companies/exportCompanies; the export button calls it. */
|
|
@@ -3576,7 +3664,7 @@ test('the caller gets a bare 500, and onError gets the reason', async () => {
|
|
|
3576
3664
|
|
|
3577
3665
|
```abide src/companies/lib/SheetPreview.abide
|
|
3578
3666
|
<script>
|
|
3579
|
-
import previewCompany from '../previewCompany.ts'
|
|
3667
|
+
import previewCompany from '../previewCompany.handler.ts'
|
|
3580
3668
|
</script>
|
|
3581
3669
|
|
|
3582
3670
|
<iframe src={previewCompany.url({ id: 'hopper-and-co' })}
|
|
@@ -3584,7 +3672,7 @@ import previewCompany from '../previewCompany.ts'
|
|
|
3584
3672
|
class="h-48 w-full rounded-md border border-rule"></iframe>
|
|
3585
3673
|
```
|
|
3586
3674
|
|
|
3587
|
-
```ts src/companies/previewCompany.ts
|
|
3675
|
+
```ts src/companies/previewCompany.handler.ts
|
|
3588
3676
|
import { GET, notFound, page, render } from '@abide/abide/server'
|
|
3589
3677
|
import CompanySheet from './lib/CompanySheet.abide'
|
|
3590
3678
|
import { COMPANIES } from './lib/COMPANIES.server.ts'
|
|
@@ -3677,8 +3765,8 @@ Everything in this chapter is in `src/account` of the chapter app, beside `src/c
|
|
|
3677
3765
|
```abide src/account/lib/SignIn.abide
|
|
3678
3766
|
<script>
|
|
3679
3767
|
import { principal, reactive } from '@abide/abide'
|
|
3680
|
-
import signIn from '../signIn.ts'
|
|
3681
|
-
import signOut from '../signOut.ts'
|
|
3768
|
+
import signIn from '../signIn.handler.ts'
|
|
3769
|
+
import signOut from '../signOut.handler.ts'
|
|
3682
3770
|
|
|
3683
3771
|
let email = reactive('maya@crm.example')
|
|
3684
3772
|
</script>
|
|
@@ -3694,7 +3782,7 @@ let email = reactive('maya@crm.example')
|
|
|
3694
3782
|
</div>
|
|
3695
3783
|
```
|
|
3696
3784
|
|
|
3697
|
-
```ts src/account/signIn.ts
|
|
3785
|
+
```ts src/account/signIn.handler.ts
|
|
3698
3786
|
import { principal, refuse } from '@abide/abide'
|
|
3699
3787
|
import { onPrincipal, POST } from '@abide/abide/server'
|
|
3700
3788
|
import { TEAM } from './lib/TEAM.server.ts'
|
|
@@ -3717,7 +3805,7 @@ onPrincipal((claims) => {
|
|
|
3717
3805
|
})
|
|
3718
3806
|
```
|
|
3719
3807
|
|
|
3720
|
-
```ts src/account/signOut.ts
|
|
3808
|
+
```ts src/account/signOut.handler.ts
|
|
3721
3809
|
import { principal } from '@abide/abide'
|
|
3722
3810
|
import { POST } from '@abide/abide/server'
|
|
3723
3811
|
|
|
@@ -3765,7 +3853,7 @@ test('a request carrying the cookie is signed in', async () => {
|
|
|
3765
3853
|
})
|
|
3766
3854
|
```
|
|
3767
3855
|
|
|
3768
|
-
`principal.set(claims)` seals the claims into an `HttpOnly` cookie, and every request that carries it is that member's. `principal.clear()` signs them out. Both run in a handler, because the cookie is written on its answer. The `onPrincipal` at the foot of `signIn.ts` is the next card's.
|
|
3856
|
+
`principal.set(claims)` seals the claims into an `HttpOnly` cookie, and every request that carries it is that member's. `principal.clear()` signs them out. Both run in a handler, because the cookie is written on its answer. The `onPrincipal` at the foot of `signIn.handler.ts` is the next card's.
|
|
3769
3857
|
|
|
3770
3858
|
The answer also tells the browser the caller changed. Every tab of the site reads `principal` again, so the form's own button flips with nothing passed to it.
|
|
3771
3859
|
|
|
@@ -3801,7 +3889,7 @@ import SignInAs from './SignInAs.abide'
|
|
|
3801
3889
|
</ul>
|
|
3802
3890
|
```
|
|
3803
3891
|
|
|
3804
|
-
```ts src/account/signIn.ts
|
|
3892
|
+
```ts src/account/signIn.handler.ts
|
|
3805
3893
|
import { principal, refuse } from '@abide/abide'
|
|
3806
3894
|
import { onPrincipal, POST } from '@abide/abide/server'
|
|
3807
3895
|
import { TEAM } from './lib/TEAM.server.ts'
|
|
@@ -3898,7 +3986,7 @@ test('the server paints the density the cookie holds', async () => {
|
|
|
3898
3986
|
```abide src/account/lib/ContactCard.abide
|
|
3899
3987
|
<script>
|
|
3900
3988
|
import { principal, reactive, watch } from '@abide/abide'
|
|
3901
|
-
import getContact from '../getContact.ts'
|
|
3989
|
+
import getContact from '../getContact.handler.ts'
|
|
3902
3990
|
import SignInAs from './SignInAs.abide'
|
|
3903
3991
|
|
|
3904
3992
|
const contact = reactive(() => getContact())
|
|
@@ -3919,7 +4007,7 @@ const dealValue = () => contact.dealValue ?? 'admins only'
|
|
|
3919
4007
|
</ul>
|
|
3920
4008
|
```
|
|
3921
4009
|
|
|
3922
|
-
```ts src/account/getContact.ts
|
|
4010
|
+
```ts src/account/getContact.handler.ts
|
|
3923
4011
|
import { principal } from '@abide/abide'
|
|
3924
4012
|
import { GET } from '@abide/abide/server'
|
|
3925
4013
|
|
|
@@ -3945,7 +4033,7 @@ export default GET(
|
|
|
3945
4033
|
```ts src/account/lib/ContactCard.test.ts
|
|
3946
4034
|
import { expect, test } from 'bun:test'
|
|
3947
4035
|
import { createApp } from '@abide/abide/server'
|
|
3948
|
-
import getContact from '../getContact.ts'
|
|
4036
|
+
import getContact from '../getContact.handler.ts'
|
|
3949
4037
|
|
|
3950
4038
|
test('an admin reads the deal value', async () => {
|
|
3951
4039
|
await using app = await createApp()
|
|
@@ -3976,7 +4064,7 @@ An answer read under one member is not read again on its own when another signs
|
|
|
3976
4064
|
```abide src/account/lib/ExportContacts.abide
|
|
3977
4065
|
<script>
|
|
3978
4066
|
import { reactive } from '@abide/abide'
|
|
3979
|
-
import exportContacts from '../exportContacts.ts'
|
|
4067
|
+
import exportContacts from '../exportContacts.handler.ts'
|
|
3980
4068
|
import SignInAs from './SignInAs.abide'
|
|
3981
4069
|
|
|
3982
4070
|
let status = reactive('')
|
|
@@ -4000,7 +4088,7 @@ async function exportAll() {
|
|
|
4000
4088
|
</div>
|
|
4001
4089
|
```
|
|
4002
4090
|
|
|
4003
|
-
```ts src/account/exportContacts.ts
|
|
4091
|
+
```ts src/account/exportContacts.handler.ts
|
|
4004
4092
|
import { principal, refuse } from '@abide/abide'
|
|
4005
4093
|
import { POST } from '@abide/abide/server'
|
|
4006
4094
|
import { CONTACTS } from '../contacts/lib/CONTACTS.server.ts'
|
|
@@ -4021,7 +4109,7 @@ export default POST(async () => CONTACTS, {
|
|
|
4021
4109
|
```ts src/account/lib/ExportContacts.test.ts
|
|
4022
4110
|
import { expect, test } from 'bun:test'
|
|
4023
4111
|
import { createApp } from '@abide/abide/server'
|
|
4024
|
-
import exportContacts from '../exportContacts.ts'
|
|
4112
|
+
import exportContacts from '../exportContacts.handler.ts'
|
|
4025
4113
|
|
|
4026
4114
|
test('an admin exports every contact', async () => {
|
|
4027
4115
|
await using app = await createApp()
|
|
@@ -4079,7 +4167,7 @@ Filed in the root `src/layout.abide`, a rung runs before routing, over every req
|
|
|
4079
4167
|
```abide src/account/lib/PartnerStatement.abide
|
|
4080
4168
|
<script server>
|
|
4081
4169
|
import { GET } from '@abide/abide/server'
|
|
4082
|
-
import getStatement from '../getStatement.ts'
|
|
4170
|
+
import getStatement from '../getStatement.handler.ts'
|
|
4083
4171
|
|
|
4084
4172
|
// Another site's browser, asking for the statement under its own origin.
|
|
4085
4173
|
const askAs = GET(
|
|
@@ -4114,7 +4202,7 @@ const allowed = reactive(() => askAs({ origin }))
|
|
|
4114
4202
|
</ul>
|
|
4115
4203
|
```
|
|
4116
4204
|
|
|
4117
|
-
```ts src/account/getStatement.ts
|
|
4205
|
+
```ts src/account/getStatement.handler.ts
|
|
4118
4206
|
import { GET } from '@abide/abide/server'
|
|
4119
4207
|
|
|
4120
4208
|
/** Read from the bookkeeping partner's own pages, and from no one else's. */
|
|
@@ -4162,7 +4250,7 @@ abide answers the `OPTIONS` preflight a cross-origin `POST` sends, outside every
|
|
|
4162
4250
|
```abide src/account/lib/SalesReport.abide
|
|
4163
4251
|
<script>
|
|
4164
4252
|
import { reactive } from '@abide/abide'
|
|
4165
|
-
import salesReport from '../salesReport.ts'
|
|
4253
|
+
import salesReport from '../salesReport.handler.ts'
|
|
4166
4254
|
|
|
4167
4255
|
const REGIONS = ['north', 'south']
|
|
4168
4256
|
|
|
@@ -4191,7 +4279,7 @@ function refusal() {
|
|
|
4191
4279
|
</ul>
|
|
4192
4280
|
```
|
|
4193
4281
|
|
|
4194
|
-
```ts src/account/salesReport.ts
|
|
4282
|
+
```ts src/account/salesReport.handler.ts
|
|
4195
4283
|
import { GET } from '@abide/abide/server'
|
|
4196
4284
|
|
|
4197
4285
|
const BY_REGION: Record<string, { week: string; total: string }[]> = {
|
|
@@ -4218,7 +4306,7 @@ export default GET(
|
|
|
4218
4306
|
|
|
4219
4307
|
```ts src/account/lib/SalesReport.test.ts
|
|
4220
4308
|
import { expect, test } from 'bun:test'
|
|
4221
|
-
import salesReport from '../salesReport.ts'
|
|
4309
|
+
import salesReport from '../salesReport.handler.ts'
|
|
4222
4310
|
|
|
4223
4311
|
test('a report slower than the timeout runs, a row at a time', async () => {
|
|
4224
4312
|
const rows = []
|
|
@@ -4261,7 +4349,7 @@ None of these is a rate limit. A caller sending many small, fast requests passes
|
|
|
4261
4349
|
```abide src/account/lib/SyncContact.abide
|
|
4262
4350
|
<script>
|
|
4263
4351
|
import { reactive } from '@abide/abide'
|
|
4264
|
-
import syncContact from '../syncContact.ts'
|
|
4352
|
+
import syncContact from '../syncContact.handler.ts'
|
|
4265
4353
|
|
|
4266
4354
|
let status = reactive('')
|
|
4267
4355
|
|
|
@@ -4283,7 +4371,7 @@ async function sync() {
|
|
|
4283
4371
|
</div>
|
|
4284
4372
|
```
|
|
4285
4373
|
|
|
4286
|
-
```ts src/account/syncContact.ts
|
|
4374
|
+
```ts src/account/syncContact.handler.ts
|
|
4287
4375
|
import { refuse } from '@abide/abide'
|
|
4288
4376
|
import { POST, reachable } from '@abide/abide/server'
|
|
4289
4377
|
import { billingOrigin } from './lib/billing.server.ts'
|
|
@@ -4345,7 +4433,7 @@ The answer can be a minute old. The call that follows a `true` can still fail, a
|
|
|
4345
4433
|
```abide src/account/lib/AddContact.abide
|
|
4346
4434
|
<script>
|
|
4347
4435
|
import { reactive } from '@abide/abide'
|
|
4348
|
-
import addContact from '../addContact.ts'
|
|
4436
|
+
import addContact from '../addContact.handler.ts'
|
|
4349
4437
|
|
|
4350
4438
|
let added = reactive<{ status: number; location: string | null }>()
|
|
4351
4439
|
|
|
@@ -4367,7 +4455,7 @@ async function add() {
|
|
|
4367
4455
|
</ul>
|
|
4368
4456
|
```
|
|
4369
4457
|
|
|
4370
|
-
```ts src/account/addContact.ts
|
|
4458
|
+
```ts src/account/addContact.handler.ts
|
|
4371
4459
|
import { POST, response } from '@abide/abide/server'
|
|
4372
4460
|
|
|
4373
4461
|
let nextId = 4031
|
|
@@ -4516,7 +4604,7 @@ Everything in this chapter lands on one page of the CRM, `/ops`, under `src/ops`
|
|
|
4516
4604
|
```abide src/ops/lib/Retries.abide
|
|
4517
4605
|
<script>
|
|
4518
4606
|
import { reactive } from '@abide/abide'
|
|
4519
|
-
import getRetries from '../getRetries.ts'
|
|
4607
|
+
import getRetries from '../getRetries.handler.ts'
|
|
4520
4608
|
|
|
4521
4609
|
const policy = reactive(() => getRetries())
|
|
4522
4610
|
</script>
|
|
@@ -4527,7 +4615,7 @@ const policy = reactive(() => getRetries())
|
|
|
4527
4615
|
</ul>
|
|
4528
4616
|
```
|
|
4529
4617
|
|
|
4530
|
-
```ts src/ops/getRetries.ts
|
|
4618
|
+
```ts src/ops/getRetries.handler.ts
|
|
4531
4619
|
import { config, GET, onConfig } from '@abide/abide/server'
|
|
4532
4620
|
|
|
4533
4621
|
// The default; a deployment's RETRIES overrides it.
|
|
@@ -4598,7 +4686,7 @@ onConfig(() => ({ RETRIES: 3 }), {
|
|
|
4598
4686
|
```abide src/ops/lib/Pool.abide
|
|
4599
4687
|
<script>
|
|
4600
4688
|
import { reactive } from '@abide/abide'
|
|
4601
|
-
import getPool from '../getPool.ts'
|
|
4689
|
+
import getPool from '../getPool.handler.ts'
|
|
4602
4690
|
|
|
4603
4691
|
const times = reactive(() => getPool())
|
|
4604
4692
|
</script>
|
|
@@ -4609,7 +4697,7 @@ const times = reactive(() => getPool())
|
|
|
4609
4697
|
</ul>
|
|
4610
4698
|
```
|
|
4611
4699
|
|
|
4612
|
-
```ts src/ops/getPool.ts
|
|
4700
|
+
```ts src/ops/getPool.handler.ts
|
|
4613
4701
|
import { GET, onStart } from '@abide/abide/server'
|
|
4614
4702
|
import { pool } from './lib/pool.server.ts'
|
|
4615
4703
|
|
|
@@ -4742,7 +4830,7 @@ abide logs under `abide:`, so `DEBUG=abide:*` turns on the framework and nothing
|
|
|
4742
4830
|
```abide src/ops/lib/Sync.abide
|
|
4743
4831
|
<script>
|
|
4744
4832
|
import { reactive } from '@abide/abide'
|
|
4745
|
-
import syncContact from '../syncContact.ts'
|
|
4833
|
+
import syncContact from '../syncContact.handler.ts'
|
|
4746
4834
|
|
|
4747
4835
|
let synced = reactive<{ trace: string; lines: string[] }>()
|
|
4748
4836
|
|
|
@@ -4765,7 +4853,7 @@ async function sync() {
|
|
|
4765
4853
|
</ul>
|
|
4766
4854
|
```
|
|
4767
4855
|
|
|
4768
|
-
```ts src/ops/syncContact.ts
|
|
4856
|
+
```ts src/ops/syncContact.handler.ts
|
|
4769
4857
|
import { log, POST, trace } from '@abide/abide/server'
|
|
4770
4858
|
import { billing } from './lib/billing.ts'
|
|
4771
4859
|
|
|
@@ -4955,8 +5043,8 @@ watch(() => { if (online) refresh() })
|
|
|
4955
5043
|
```abide src/ops/lib/CallLog.abide
|
|
4956
5044
|
<script>
|
|
4957
5045
|
import { outbox, reactive } from '@abide/abide'
|
|
4958
|
-
import listCalls from '../listCalls.ts'
|
|
4959
|
-
import logCall from '../logCall.ts'
|
|
5046
|
+
import listCalls from '../listCalls.handler.ts'
|
|
5047
|
+
import logCall from '../logCall.handler.ts'
|
|
4960
5048
|
|
|
4961
5049
|
const calls = listCalls()
|
|
4962
5050
|
let note = reactive('')
|
|
@@ -4990,7 +5078,7 @@ function log() {
|
|
|
4990
5078
|
</ul>
|
|
4991
5079
|
```
|
|
4992
5080
|
|
|
4993
|
-
```ts src/ops/logCall.ts
|
|
5081
|
+
```ts src/ops/logCall.handler.ts
|
|
4994
5082
|
import { POST } from '@abide/abide/server'
|
|
4995
5083
|
import { type Call, calls } from './lib/calls.ts'
|
|
4996
5084
|
|
|
@@ -5026,7 +5114,7 @@ test('a call logged with the network is saved, and nothing waits', async () => {
|
|
|
5026
5114
|
})
|
|
5027
5115
|
|
|
5028
5116
|
test('a call sent twice under one id is logged once', async () => {
|
|
5029
|
-
const { default: logCall } = await import('../logCall.ts')
|
|
5117
|
+
const { default: logCall } = await import('../logCall.handler.ts')
|
|
5030
5118
|
const call = { id: 'again', note: 'Asked twice' }
|
|
5031
5119
|
await logCall(call)
|
|
5032
5120
|
await logCall(call)
|
|
@@ -5067,7 +5155,7 @@ A handler file already carries an address, a method, an argument type and a desc
|
|
|
5067
5155
|
|
|
5068
5156
|
| Surface | Who reads it | Where |
|
|
5069
5157
|
| --- | --- | --- |
|
|
5070
|
-
| `ui` | a page | `import searchContacts from './searchContacts.ts'` |
|
|
5158
|
+
| `ui` | a page | `import searchContacts from './searchContacts.handler.ts'` |
|
|
5071
5159
|
| `openapi` | a generated client, an API gateway | `/__abide/openapi.json` |
|
|
5072
5160
|
| `mcp` | a model | `/__abide/mcp` |
|
|
5073
5161
|
| `cli` | a person or a script in a terminal | `abide call` |
|
|
@@ -5106,7 +5194,7 @@ async function readDocument() {
|
|
|
5106
5194
|
{/if}
|
|
5107
5195
|
```
|
|
5108
5196
|
|
|
5109
|
-
```ts src/assistant/searchContacts.ts
|
|
5197
|
+
```ts src/assistant/searchContacts.handler.ts
|
|
5110
5198
|
import { GET } from '@abide/abide/server'
|
|
5111
5199
|
import { CONTACTS } from '../contacts/lib/CONTACTS.server.ts'
|
|
5112
5200
|
|
|
@@ -5205,7 +5293,7 @@ async function callAsAModel() {
|
|
|
5205
5293
|
</ul>
|
|
5206
5294
|
```
|
|
5207
5295
|
|
|
5208
|
-
```ts src/assistant/searchContacts.ts
|
|
5296
|
+
```ts src/assistant/searchContacts.handler.ts
|
|
5209
5297
|
import { GET } from '@abide/abide/server'
|
|
5210
5298
|
import { CONTACTS } from '../contacts/lib/CONTACTS.server.ts'
|
|
5211
5299
|
|
|
@@ -5265,7 +5353,7 @@ abide mcp --url https://crm.example
|
|
|
5265
5353
|
```abide src/assistant/lib/WithheldExport.abide
|
|
5266
5354
|
<script>
|
|
5267
5355
|
import { reactive, url } from '@abide/abide'
|
|
5268
|
-
import exportContacts from '../exportContacts.ts'
|
|
5356
|
+
import exportContacts from '../exportContacts.handler.ts'
|
|
5269
5357
|
|
|
5270
5358
|
let listed = reactive('—')
|
|
5271
5359
|
let answers = reactive('—')
|
|
@@ -5295,7 +5383,7 @@ async function check() {
|
|
|
5295
5383
|
</ul>
|
|
5296
5384
|
```
|
|
5297
5385
|
|
|
5298
|
-
```ts src/assistant/exportContacts.ts
|
|
5386
|
+
```ts src/assistant/exportContacts.handler.ts
|
|
5299
5387
|
import { GET } from '@abide/abide/server'
|
|
5300
5388
|
import { CONTACTS } from '../contacts/lib/CONTACTS.server.ts'
|
|
5301
5389
|
|
|
@@ -5340,7 +5428,7 @@ The export is not a tool, and its address still answers. `clients` decides what
|
|
|
5340
5428
|
|
|
5341
5429
|
| Key | Set to `false` |
|
|
5342
5430
|
| --- | --- |
|
|
5343
|
-
| `ui` | No page can import the handler: the import is a compile error naming it. The stand-in in `v1/messages.ts` sets it. |
|
|
5431
|
+
| `ui` | No page can import the handler: the import is a compile error naming it. The stand-in in `v1/messages.handler.ts` sets it. |
|
|
5344
5432
|
| `openapi` | No operation in `/__abide/openapi.json`. |
|
|
5345
5433
|
| `mcp` | No tool on `/__abide/mcp`, and none for the assistant. |
|
|
5346
5434
|
| `cli` | Not callable with `abide call`. |
|
|
@@ -5354,7 +5442,7 @@ A handler that returns a whole `Response` starts with `mcp` and `cli` off, havin
|
|
|
5354
5442
|
```abide src/assistant/lib/AssistantChat.abide
|
|
5355
5443
|
<script>
|
|
5356
5444
|
import { reactive } from '@abide/abide'
|
|
5357
|
-
import askAssistant from '../askAssistant.ts'
|
|
5445
|
+
import askAssistant from '../askAssistant.handler.ts'
|
|
5358
5446
|
|
|
5359
5447
|
const QUESTION = 'Which company is Ada at?'
|
|
5360
5448
|
|
|
@@ -5391,7 +5479,7 @@ const events = reactive(() =>
|
|
|
5391
5479
|
</div>
|
|
5392
5480
|
```
|
|
5393
5481
|
|
|
5394
|
-
```ts src/assistant/askAssistant.ts
|
|
5482
|
+
```ts src/assistant/askAssistant.handler.ts
|
|
5395
5483
|
import {
|
|
5396
5484
|
type AssistantMessage,
|
|
5397
5485
|
assistant,
|
|
@@ -5483,7 +5571,7 @@ test('the model calls the CRM search, through the app', async () => {
|
|
|
5483
5571
|
```abide src/assistant/lib/ClaudeApiRequest.abide
|
|
5484
5572
|
<script>
|
|
5485
5573
|
import { reactive } from '@abide/abide'
|
|
5486
|
-
import askClaude from '../askClaude.ts'
|
|
5574
|
+
import askClaude from '../askClaude.handler.ts'
|
|
5487
5575
|
|
|
5488
5576
|
const QUESTION = 'Which company is Ada at?'
|
|
5489
5577
|
|
|
@@ -5508,7 +5596,7 @@ const events = reactive(() =>
|
|
|
5508
5596
|
{/for}
|
|
5509
5597
|
```
|
|
5510
5598
|
|
|
5511
|
-
```ts src/assistant/askClaude.ts
|
|
5599
|
+
```ts src/assistant/askClaude.handler.ts
|
|
5512
5600
|
import {
|
|
5513
5601
|
type AssistantMessage,
|
|
5514
5602
|
assistant,
|
|
@@ -5516,7 +5604,7 @@ import {
|
|
|
5516
5604
|
POST,
|
|
5517
5605
|
request,
|
|
5518
5606
|
} from '@abide/abide/server'
|
|
5519
|
-
import standIn from './v1/messages.ts'
|
|
5607
|
+
import standIn from './v1/messages.handler.ts'
|
|
5520
5608
|
|
|
5521
5609
|
export default POST(
|
|
5522
5610
|
async function* ({ messages }: { messages: AssistantMessage[] }) {
|
|
@@ -5537,7 +5625,7 @@ export default POST(
|
|
|
5537
5625
|
)
|
|
5538
5626
|
```
|
|
5539
5627
|
|
|
5540
|
-
```ts src/assistant/v1/messages.ts
|
|
5628
|
+
```ts src/assistant/v1/messages.handler.ts
|
|
5541
5629
|
import { POST, sse } from '@abide/abide/server'
|
|
5542
5630
|
|
|
5543
5631
|
// A stand-in for the Messages API, streaming back the body it was sent.
|
|
@@ -5626,8 +5714,8 @@ Leave `baseURL` out and the request goes to Anthropic. `claudeApi` reads no key
|
|
|
5626
5714
|
```abide src/assistant/lib/ClaudeCodeBridge.abide
|
|
5627
5715
|
<script>
|
|
5628
5716
|
import { reactive } from '@abide/abide'
|
|
5629
|
-
import askClaudeCode from '../askClaudeCode.ts'
|
|
5630
|
-
import bridge from '../bridge.ts'
|
|
5717
|
+
import askClaudeCode from '../askClaudeCode.handler.ts'
|
|
5718
|
+
import bridge from '../bridge.handler.ts'
|
|
5631
5719
|
|
|
5632
5720
|
const QUESTION = 'Which company is Grace at?'
|
|
5633
5721
|
|
|
@@ -5674,7 +5762,7 @@ const answer = reactive(() => {
|
|
|
5674
5762
|
</div>
|
|
5675
5763
|
```
|
|
5676
5764
|
|
|
5677
|
-
```ts src/assistant/bridge.ts
|
|
5765
|
+
```ts src/assistant/bridge.handler.ts
|
|
5678
5766
|
import { claudeCode, GET } from '@abide/abide/server'
|
|
5679
5767
|
|
|
5680
5768
|
// This browser's pairing, for as long as the page watches it.
|
|
@@ -5686,7 +5774,7 @@ export default GET(() => claudeCode.bridge(), {
|
|
|
5686
5774
|
})
|
|
5687
5775
|
```
|
|
5688
5776
|
|
|
5689
|
-
```ts src/assistant/askClaudeCode.ts
|
|
5777
|
+
```ts src/assistant/askClaudeCode.handler.ts
|
|
5690
5778
|
import {
|
|
5691
5779
|
type AssistantMessage,
|
|
5692
5780
|
assistant,
|
|
@@ -5821,7 +5909,7 @@ Each card is a file of the reports page, beside the test or the bench that prove
|
|
|
5821
5909
|
```abide src/reports/lib/PipelineSummary.abide
|
|
5822
5910
|
<script>
|
|
5823
5911
|
import { reactive } from '@abide/abide'
|
|
5824
|
-
import getSummary from '../getSummary.ts'
|
|
5912
|
+
import getSummary from '../getSummary.handler.ts'
|
|
5825
5913
|
|
|
5826
5914
|
const summary = reactive(() => getSummary())
|
|
5827
5915
|
</script>
|
|
@@ -5832,7 +5920,7 @@ const summary = reactive(() => getSummary())
|
|
|
5832
5920
|
</ul>
|
|
5833
5921
|
```
|
|
5834
5922
|
|
|
5835
|
-
```ts src/reports/getSummary.ts
|
|
5923
|
+
```ts src/reports/getSummary.handler.ts
|
|
5836
5924
|
import { GET } from '@abide/abide/server'
|
|
5837
5925
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
5838
5926
|
|
|
@@ -7696,9 +7784,9 @@ Transports: what is written in place rather than imported.
|
|
|
7696
7784
|
|
|
7697
7785
|
| File | Declares | Served at | Rules |
|
|
7698
7786
|
| --- | --- | --- | --- |
|
|
7699
|
-
| `users/getUser.ts` | `export default GET(…)` | `/users/getUser` | [16.2](/rulebook#16.2), [16.73](/rulebook#16.73), [19.1](/rulebook#19.1), [19.20](/rulebook#19.20), [23.14](/rulebook#23.14) |
|
|
7700
|
-
| `health.ts` | `export default GET(…)` | `/health` | [16.2](/rulebook#16.2), [23.14](/rulebook#23.14) |
|
|
7701
|
-
| `media/[id]/save.ts` | `export default POST(…)` | `/media/[id]/save`, `id` reaching its `Args` | [16.2](/rulebook#16.2), [16.65](/rulebook#16.65), [16.66](/rulebook#16.66), [16.67](/rulebook#16.67), [16.69](/rulebook#16.69) |
|
|
7787
|
+
| `users/getUser.handler.ts` | `export default GET(…)` | `/users/getUser` | [16.2](/rulebook#16.2), [16.73](/rulebook#16.73), [19.1](/rulebook#19.1), [19.20](/rulebook#19.20), [23.14](/rulebook#23.14) |
|
|
7788
|
+
| `health.handler.ts` | `export default GET(…)` | `/health` | [16.2](/rulebook#16.2), [23.14](/rulebook#23.14) |
|
|
7789
|
+
| `media/[id]/save.handler.ts` | `export default POST(…)` | `/media/[id]/save`, `id` reaching its `Args` | [16.2](/rulebook#16.2), [16.65](/rulebook#16.65), [16.66](/rulebook#16.66), [16.67](/rulebook#16.67), [16.69](/rulebook#16.69) |
|
|
7702
7790
|
| `media/[id]/lib/Comments.abide` | `const addComment = POST(…)` in its `<script server>` | `/__abide/handlers/media/id/lib/Comments/addComment` | [16.70](/rulebook#16.70), [16.71](/rulebook#16.71), [16.72](/rulebook#16.72) |
|
|
7703
7791
|
|
|
7704
7792
|
## Headers abide generates
|
|
@@ -7872,14 +7960,14 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
7872
7960
|
|
|
7873
7961
|
| Name | Signature | Meaning | Rules |
|
|
7874
7962
|
| --- | --- | --- | --- |
|
|
7875
|
-
| `/__abide/openapi.json` | `GET` | The generated OpenAPI document. | [19.21](/rulebook#19.21), [19.22](/rulebook#19.22), [20.5](/rulebook#20.5), [20.6](/rulebook#20.6), [20.7](/rulebook#20.7), [20.8](/rulebook#20.8), [20.11](/rulebook#20.11), [20.13](/rulebook#20.13), [20.15](/rulebook#20.15), [36.7](/rulebook#36.7) |
|
|
7963
|
+
| `/__abide/openapi.json` | `GET` | The generated OpenAPI document. | [19.21](/rulebook#19.21), [19.22](/rulebook#19.22), [20.5](/rulebook#20.5), [20.6](/rulebook#20.6), [20.7](/rulebook#20.7), [20.8](/rulebook#20.8), [20.11](/rulebook#20.11), [20.13](/rulebook#20.13), [20.15](/rulebook#20.15), [20.23](/rulebook#20.23), [36.7](/rulebook#36.7) |
|
|
7876
7964
|
| `OpenApiDocument` | `JsonValue` | OpenAPI 3.1, whose schema dialect is `JsonSchema`. | [20.6](/rulebook#20.6) |
|
|
7877
7965
|
|
|
7878
7966
|
## MCP
|
|
7879
7967
|
|
|
7880
7968
|
| Name | Signature | Meaning | Rules |
|
|
7881
7969
|
| --- | --- | --- | --- |
|
|
7882
|
-
| `/__abide/mcp` | `POST` | The streamable-http MCP endpoint. | [19.21](/rulebook#19.21), [19.22](/rulebook#19.22), [20.5](/rulebook#20.5), [20.7](/rulebook#20.7), [20.8](/rulebook#20.8), [20.9](/rulebook#20.9), [20.10](/rulebook#20.10), [20.12](/rulebook#20.12), [20.13](/rulebook#20.13), [20.14](/rulebook#20.14), [20.16](/rulebook#20.16), [20.17](/rulebook#20.17), [20.19](/rulebook#20.19), [20.20](/rulebook#20.20), [20.21](/rulebook#20.21), [20.22](/rulebook#20.22), [36.7](/rulebook#36.7), [16.78](/rulebook#16.78) |
|
|
7970
|
+
| `/__abide/mcp` | `POST` | The streamable-http MCP endpoint. | [19.21](/rulebook#19.21), [19.22](/rulebook#19.22), [20.5](/rulebook#20.5), [20.7](/rulebook#20.7), [20.8](/rulebook#20.8), [20.9](/rulebook#20.9), [20.10](/rulebook#20.10), [20.12](/rulebook#20.12), [20.13](/rulebook#20.13), [20.14](/rulebook#20.14), [20.16](/rulebook#20.16), [20.17](/rulebook#20.17), [20.19](/rulebook#20.19), [20.20](/rulebook#20.20), [20.21](/rulebook#20.21), [20.22](/rulebook#20.22), [20.23](/rulebook#20.23), [36.7](/rulebook#36.7), [16.78](/rulebook#16.78) |
|
|
7883
7971
|
|
|
7884
7972
|
## Taught in
|
|
7885
7973
|
|
|
@@ -8799,7 +8887,7 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
8799
8887
|
|
|
8800
8888
|
| Name | Signature | Meaning | Rules |
|
|
8801
8889
|
| --- | --- | --- | --- |
|
|
8802
|
-
| `ParamsOf<P>` | `& { [K in RequiredNames<P>]: string \| number } & { [K in OptionalNames<P>]?: string \| number } & { [K in RestNames<P>]?: string \| number \| (string \| number)[] }` | What a route literal's segments accept. | [30.3](/rulebook#30.3), [30.4](/rulebook#30.4), [30.5](/rulebook#30.5) |
|
|
8890
|
+
| `ParamsOf<P>` | `& { [K in RequiredNames<P>]: string \| number } & { [K in OptionalNames<P>]?: string \| number } & { [K in RestNames<P>]?: string \| number \| (string \| number)[] }` | What a route literal's segments accept, narrowed where `abide check` declares a matcher's literals. | [30.3](/rulebook#30.3), [30.4](/rulebook#30.4), [30.5](/rulebook#30.5) |
|
|
8803
8891
|
|
|
8804
8892
|
## Taught in
|
|
8805
8893
|
|
|
@@ -8817,7 +8905,7 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
8817
8905
|
|
|
8818
8906
|
| Name | Signature | Meaning | Rules |
|
|
8819
8907
|
| --- | --- | --- | --- |
|
|
8820
|
-
| `RouteParams<P>` | `{ [K in RequiredNames<P>]: string } & { [K in OptionalNames<P>]?: string } & { [K in RestNames<P>]: string[] }` | What a page filed under `P` reads its params as. | [23.9](/rulebook#23.9) |
|
|
8908
|
+
| `RouteParams<P>` | `{ [K in RequiredNames<P>]: string } & { [K in OptionalNames<P>]?: string } & { [K in RestNames<P>]: string[] }` | What a page filed under `P` reads its params as, narrowed where `abide check` declares a matcher's literals. | [23.9](/rulebook#23.9) |
|
|
8821
8909
|
|
|
8822
8910
|
## Taught in
|
|
8823
8911
|
|
|
@@ -9046,9 +9134,10 @@ Pages and routing: what is written in place rather than imported.
|
|
|
9046
9134
|
| --- | --- | --- |
|
|
9047
9135
|
| `<head>` | A page's, layout's or component's contribution to the document head. | [39.1](/rulebook#39.1), [39.2](/rulebook#39.2), [39.3](/rulebook#39.3), [39.4](/rulebook#39.4), [39.5](/rulebook#39.5), [39.6](/rulebook#39.6), [39.11](/rulebook#39.11), [39.12](/rulebook#39.12), [39.13](/rulebook#39.13), [39.14](/rulebook#39.14), [39.15](/rulebook#39.15), [39.16](/rulebook#39.16), [39.17](/rulebook#39.17) |
|
|
9048
9136
|
| `view-transition-name` | What turns view transitions on, in the app's own stylesheet. | [39.7](/rulebook#39.7), [39.8](/rulebook#39.8), [39.9](/rulebook#39.9), [39.10](/rulebook#39.10), [39.18](/rulebook#39.18), [39.19](/rulebook#39.19) |
|
|
9049
|
-
| `[name]` | A required dynamic segment. | [16.65](/rulebook#16.65), [23.5](/rulebook#23.5), [30.3](/rulebook#30.3) |
|
|
9050
|
-
| `[[name]]` | An optional segment. | [23.5](/rulebook#23.5), [30.4](/rulebook#30.4) |
|
|
9051
|
-
| `[...name]` | A rest segment, terminal. | [23.5](/rulebook#23.5), [30.5](/rulebook#30.5) |
|
|
9137
|
+
| `[name]` | A required dynamic segment. | [16.65](/rulebook#16.65), [23.5](/rulebook#23.5), [30.3](/rulebook#30.3), [30.34](/rulebook#30.34), [30.36](/rulebook#30.36) |
|
|
9138
|
+
| `[[name]]` | An optional segment. | [23.5](/rulebook#23.5), [30.4](/rulebook#30.4), [30.34](/rulebook#30.34), [30.36](/rulebook#30.36) |
|
|
9139
|
+
| `[...name]` | A rest segment, terminal. | [23.5](/rulebook#23.5), [30.5](/rulebook#30.5), [30.34](/rulebook#30.34), [30.36](/rulebook#30.36) |
|
|
9140
|
+
| `[name=NAME]` · `[[name=NAME]]` · `[...name=NAME]` | A dynamic segment a param file's matcher decides. | [19.1](/rulebook#19.1), [23.26](/rulebook#23.26), [23.27](/rulebook#23.27), [30.19](/rulebook#30.19), [30.35](/rulebook#30.35), [30.36](/rulebook#30.36), [30.37](/rulebook#30.37) |
|
|
9052
9141
|
| `/__abide/**` | Every endpoint abide controls. | [20.8](/rulebook#20.8) |
|
|
9053
9142
|
|
|
9054
9143
|
## Sinks
|
|
@@ -9150,9 +9239,10 @@ Configuration: what is written in place rather than imported.
|
|
|
9150
9239
|
| `src/public/robots.txt` | The file answered at `/robots.txt`. | [36.11](/rulebook#36.11) |
|
|
9151
9240
|
| `src/public/icon.png` · `src/public/icon.icns` | The files answered at `/icon.png` and `/icon.icns`, and the desktop app's icon. | [36.11](/rulebook#36.11), [38.79](/rulebook#38.79), [45.15](/rulebook#45.15) |
|
|
9152
9241
|
| `src/**/` | A directory of the app's, a segment of the route of every file directly in it. | [23.14](/rulebook#23.14), [23.17](/rulebook#23.17), [23.22](/rulebook#23.22) |
|
|
9153
|
-
| `**/index.abide` | The page at its directory's route. | [19.1](/rulebook#19.1), [23.3](/rulebook#23.3), [23.4](/rulebook#23.4), [23.14](/rulebook#23.14), [23.18](/rulebook#23.18) |
|
|
9154
|
-
| `**/<name>.abide` | The page at its directory's route and then `<name>`. | [19.1](/rulebook#19.1), [23.3](/rulebook#23.3), [23.4](/rulebook#23.4), [23.14](/rulebook#23.14), [23.21](/rulebook#23.21) |
|
|
9155
|
-
| `**/<
|
|
9242
|
+
| `**/index.abide` | The page at its directory's route. | [19.1](/rulebook#19.1), [23.3](/rulebook#23.3), [23.4](/rulebook#23.4), [23.14](/rulebook#23.14), [23.18](/rulebook#23.18), [35.19](/rulebook#35.19) |
|
|
9243
|
+
| `**/<name>.abide` | The page at its directory's route and then `<name>`. | [19.1](/rulebook#19.1), [23.3](/rulebook#23.3), [23.4](/rulebook#23.4), [23.14](/rulebook#23.14), [23.21](/rulebook#23.21), [35.19](/rulebook#35.19) |
|
|
9244
|
+
| `**/<NAME>.param.ts` | A param file: the matcher a segment naming `NAME` matches by. | [23.22](/rulebook#23.22), [23.24](/rulebook#23.24), [23.25](/rulebook#23.25), [23.26](/rulebook#23.26) |
|
|
9245
|
+
| `**/<name>.handler.ts` | A handler file: the handler its default export declares, at its directory's route and then `<name>`. | [16.2](/rulebook#16.2), [16.28](/rulebook#16.28), [16.73](/rulebook#16.73), [16.74](/rulebook#16.74), [19.1](/rulebook#19.1), [23.14](/rulebook#23.14), [23.22](/rulebook#23.22) |
|
|
9156
9246
|
| `**/layout.abide` | A layout, rendering every page below its route through a slot, and the hooks over that route. | [35.1](/rulebook#35.1), [35.12](/rulebook#35.12), [35.13](/rulebook#35.13), [37.2](/rulebook#37.2), [41.11](/rulebook#41.11), [41.19](/rulebook#41.19), [41.23](/rulebook#41.23) |
|
|
9157
9247
|
| `src/launcher.abide` | The desktop launcher, beside the root layout: the `onLauncher` it calls, and its page in place of the launcher's own where it has markup. | [38.80](/rulebook#38.80), [38.82](/rulebook#38.82), [38.84](/rulebook#38.84), [38.85](/rulebook#38.85), [38.107](/rulebook#38.107) |
|
|
9158
9248
|
| `src/layout.abide` | The root layout, whose `<script server>` is the app's own, and whose `<html>` is the document its pages are served in. | [35.15](/rulebook#35.15), [35.16](/rulebook#35.16), [35.17](/rulebook#35.17), [37.1](/rulebook#37.1), [37.3](/rulebook#37.3), [39.4](/rulebook#39.4) |
|
|
@@ -10414,7 +10504,7 @@ CLI: what is written in place rather than imported.
|
|
|
10414
10504
|
| `abide scaffold <name>` | Writes a starter project. | [38.1](/rulebook#38.1), [38.42](/rulebook#38.42), [38.103](/rulebook#38.103), [38.104](/rulebook#38.104), [38.106](/rulebook#38.106), [38.112](/rulebook#38.112), [38.122](/rulebook#38.122), [38.124](/rulebook#38.124) |
|
|
10415
10505
|
| `abide run <file> [args…]` | Runs a script under the abide runtime. | [38.2](/rulebook#38.2), [38.57](/rulebook#38.57) |
|
|
10416
10506
|
| `abide check [dir…]` | The type-checker over a `.abide` tree. | [38.3](/rulebook#38.3), [38.31](/rulebook#38.31), [38.32](/rulebook#38.32), [38.33](/rulebook#38.33), [38.40](/rulebook#38.40), [38.105](/rulebook#38.105), [38.123](/rulebook#38.123) |
|
|
10417
|
-
| `abide routes [--json]` | Every address the app answers, the file answering it, and where each hook is filed. | [38.108](/rulebook#38.108), [38.123](/rulebook#38.123) |
|
|
10507
|
+
| `abide routes [--json]` | Every address the app answers, the file answering it, what each matcher takes, and where each hook is filed. | [38.108](/rulebook#38.108), [38.123](/rulebook#38.123) |
|
|
10418
10508
|
| `abide dev [--port <n>]` | Watches the project and keeps the app up. | [16.23](/rulebook#16.23), [16.56](/rulebook#16.56), [37.8](/rulebook#37.8), [37.9](/rulebook#37.9), [38.4](/rulebook#38.4), [38.14](/rulebook#38.14), [38.15](/rulebook#38.15), [38.16](/rulebook#38.16), [38.17](/rulebook#38.17), [38.19](/rulebook#38.19), [38.24](/rulebook#38.24), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.28](/rulebook#38.28), [38.30](/rulebook#38.30), [38.35](/rulebook#38.35), [38.36](/rulebook#38.36), [38.37](/rulebook#38.37), [38.38](/rulebook#38.38), [38.41](/rulebook#38.41), [38.43](/rulebook#38.43), [38.58](/rulebook#38.58), [38.63](/rulebook#38.63), [38.64](/rulebook#38.64), [38.65](/rulebook#38.65), [38.94](/rulebook#38.94), [38.96](/rulebook#38.96), [38.97](/rulebook#38.97), [38.123](/rulebook#38.123) |
|
|
10419
10509
|
| `abide build` | Builds the client into content-hashed chunks and a manifest. | [38.43](/rulebook#38.43), [38.5](/rulebook#38.5), [38.18](/rulebook#38.18), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.27](/rulebook#38.27), [38.30](/rulebook#38.30), [38.66](/rulebook#38.66), [38.94](/rulebook#38.94), [38.123](/rulebook#38.123) |
|
|
10420
10510
|
| `abide start [--port <n>]` | Boots the built app. | [37.8](/rulebook#37.8), [37.10](/rulebook#37.10), [37.11](/rulebook#37.11), [37.12](/rulebook#37.12), [38.6](/rulebook#38.6), [38.100](/rulebook#38.100), [38.20](/rulebook#38.20), [38.21](/rulebook#38.21), [38.22](/rulebook#38.22), [38.23](/rulebook#38.23), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.27](/rulebook#38.27), [38.28](/rulebook#38.28), [38.29](/rulebook#38.29), [38.58](/rulebook#38.58), [38.67](/rulebook#38.67), [38.68](/rulebook#38.68), [38.95](/rulebook#38.95), [38.97](/rulebook#38.97), [38.110](/rulebook#38.110), [38.123](/rulebook#38.123) |
|