@abide/abide 0.117.2 → 0.119.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 +22 -0
- package/bin/abide.ts +74 -11
- package/docs/REGISTRY.md +18 -15
- package/docs/RULEBOOK.md +71 -10
- package/docs/abide.md +584 -274
- package/package.json +1 -1
- package/scaffold/AGENTS.md +1 -1
- package/scaffold/src/getGreeting.test.ts +2 -1
- package/scaffold/src/index.abide +5 -2
- package/scaffold/src/index.test.ts +2 -0
- package/scaffold/src/layout.abide +6 -6
- package/src/compiler/Lowering.ts +20 -2
- package/src/compiler/PositionTable.ts +1 -0
- package/src/compiler/Refusals.ts +1 -0
- package/src/compiler/TypeSession.ts +49 -13
- package/src/compiler/aliasedSymbol.ts +1 -1
- package/src/compiler/ambientReadsIn.ts +1 -0
- package/src/compiler/appLayout.ts +55 -3
- package/src/compiler/appMap.ts +15 -0
- package/src/compiler/argsDefaultsIn.ts +1 -0
- package/src/compiler/argsSchemaOf.ts +1 -0
- package/src/compiler/checkApp.ts +3 -1
- package/src/compiler/checkedShadow.ts +2 -1
- package/src/compiler/classifyRead.ts +1 -0
- package/src/compiler/classifyTemplate.ts +1 -0
- package/src/compiler/compileApp.ts +5 -3
- package/src/compiler/compiledEntry.ts +2 -1
- package/src/compiler/componentGraph.ts +1 -0
- package/src/compiler/declaredExports.ts +4 -2
- package/src/compiler/emitClient.ts +19 -14
- package/src/compiler/emitComponent.ts +1 -0
- package/src/compiler/emitSource.ts +2 -1
- package/src/compiler/formatAbide.ts +857 -0
- package/src/compiler/formatDiagnosis.ts +1 -0
- package/src/compiler/handlersUnder.ts +7 -3
- package/src/compiler/hookCalls.ts +1 -0
- package/src/compiler/lineOf.ts +17 -0
- package/src/compiler/matchersOf.ts +157 -0
- package/src/compiler/mountsOf.ts +5 -0
- package/src/compiler/paramFile.ts +125 -0
- package/src/compiler/parseAbide.ts +48 -28
- package/src/compiler/projectShadow.ts +11 -4
- package/src/compiler/reactiveOptions.ts +1 -0
- package/src/compiler/reactiveTypes.ts +2 -1
- package/src/compiler/readsIn.ts +3 -2
- package/src/compiler/renderMarkup.ts +3 -2
- package/src/compiler/resolverIn.ts +2 -0
- package/src/compiler/sameTemplate.ts +150 -0
- package/src/compiler/scaffold.ts +4 -11
- package/src/compiler/serveLanguage.ts +48 -21
- package/src/compiler/sharedModuleWrites.ts +2 -1
- package/src/compiler/sourceMapOf.ts +1 -0
- package/src/compiler/splitImports.ts +2 -0
- package/src/compiler/templateWarnings.ts +2 -2
- package/src/compiler/trackingWarnings.ts +1 -0
- package/src/compiler/writeDefaultProject.ts +5 -1
- package/src/server/BRIDGE_VERSION.ts +1 -1
- package/src/server/abideModules.ts +2 -0
- package/src/server/answerFailure.ts +1 -0
- package/src/server/answerRequest.ts +57 -19
- package/src/server/answerRpc.ts +6 -2
- package/src/server/answerSocket.ts +2 -1
- package/src/server/answerValue.ts +1 -0
- package/src/server/answerWorker.ts +1 -0
- package/src/server/appTree.ts +18 -9
- package/src/server/argsFor.ts +1 -0
- package/src/server/assistant.ts +2 -1
- package/src/server/banner.ts +2 -0
- package/src/server/bridgeRelay.ts +1 -0
- package/src/server/callerOf.ts +1 -0
- package/src/server/claudeApi.ts +1 -0
- package/src/server/claudeCode.ts +1 -0
- package/src/server/clientBundle.ts +1 -0
- package/src/server/coerceArgs.ts +1 -2
- package/src/server/commands.ts +9 -0
- package/src/server/compressAnswer.ts +2 -1
- package/src/server/config.ts +2 -0
- package/src/server/createApp.ts +4 -2
- package/src/server/crossingOrigin.ts +1 -0
- package/src/server/csp.ts +1 -0
- package/src/server/deferredBuilds.ts +1 -0
- package/src/server/derivedArgs.ts +1 -0
- package/src/server/devWorker.ts +2 -1
- package/src/server/faceOf.ts +2 -7
- package/src/server/generatedSurfaces.ts +79 -7
- package/src/server/hooks.ts +1 -0
- package/src/server/iconAccent.ts +1 -1
- package/src/server/installServer.ts +1 -0
- package/src/server/installSourceMaps.ts +1 -0
- package/src/server/launcherPage.ts +2 -2
- package/src/server/manifestOf.ts +1 -0
- package/src/server/muxUpgrade.ts +1 -0
- package/src/server/nativeMenu.ts +2 -0
- package/src/server/placement.ts +12 -2
- package/src/server/principal.ts +2 -1
- package/src/server/remote.ts +7 -2
- package/src/server/render.ts +3 -3
- package/src/server/renderComponent.ts +1 -0
- package/src/server/reportHealth.ts +1 -0
- package/src/server/requestScope.ts +3 -1
- package/src/server/runDesktop.ts +3 -2
- package/src/server/seedBuffer.ts +1 -0
- package/src/server/serveDev.ts +3 -2
- package/src/server/shell.ts +2 -0
- package/src/server/streamEvent.ts +1 -0
- package/src/server/surfaceOf.ts +6 -1
- package/src/server/testBuild.ts +1 -0
- package/src/server/testMount.ts +1 -0
- package/src/server/trace.ts +2 -0
- package/src/server/withAbideSources.ts +1 -0
- package/src/shared/Attributes.ts +12 -8
- package/src/shared/cookies.ts +1 -1
- package/src/shared/escapeText.ts +1 -1
- package/src/shared/filledPath.ts +16 -10
- package/src/shared/index.ts +1 -5
- package/src/shared/jsonSchema.ts +1 -3
- package/src/shared/matchRoute.ts +16 -4
- package/src/shared/menuBridge.ts +1 -2
- package/src/shared/outbox.ts +1 -1
- package/src/shared/reactive/Keyed.ts +2 -2
- package/src/shared/reactive/Reactive.ts +12 -16
- package/src/shared/reactive/index.ts +3 -4
- package/src/shared/reactive/isReactive.ts +1 -1
- package/src/shared/reactive/scoped.ts +2 -2
- package/src/shared/reactive/share.ts +1 -1
- package/src/shared/reactive/watch.ts +4 -4
- package/src/shared/refuse.ts +1 -1
- package/src/shared/routePattern.ts +66 -14
- package/src/shared/rpc.ts +8 -11
- package/src/shared/url.ts +24 -7
- package/src/shared/validateJson.ts +1 -1
- package/src/ui/ArmRegion.ts +1 -0
- package/src/ui/Level.ts +1 -0
- package/src/ui/List.ts +1 -0
- package/src/ui/RawRun.ts +1 -0
- package/src/ui/Router.ts +1 -0
- package/src/ui/TryRegion.ts +1 -0
- package/src/ui/adoptWalk.ts +2 -1
- package/src/ui/bindElement.ts +1 -0
- package/src/ui/bindFiles.ts +1 -0
- package/src/ui/bindGroup.ts +1 -0
- package/src/ui/bindProperty.ts +1 -0
- package/src/ui/bindSpread.ts +1 -0
- package/src/ui/index.ts +1 -6
- package/src/ui/outboxQueue.ts +1 -1
- package/src/ui/parseMarkup.ts +2 -1
- package/src/ui/reconcile.ts +2 -1
- package/src/ui/reconcileHead.ts +1 -0
- package/src/ui/regions.ts +2 -1
- package/src/ui/registerWorker.ts +1 -0
- package/src/ui/rows.ts +1 -0
- package/src/ui/setMarkup.ts +1 -0
- package/src/ui/setText.ts +1 -0
- package/src/ui/socketWire.ts +4 -3
- package/src/ui/wire.ts +2 -1
- /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,8 @@ 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
|
-
|
|
338
|
+
|
|
339
|
+
import listContacts from '../listContacts.handler.ts'
|
|
339
340
|
|
|
340
341
|
const contacts = reactive(() => listContacts())
|
|
341
342
|
</script>
|
|
@@ -347,8 +348,9 @@ const contacts = reactive(() => listContacts())
|
|
|
347
348
|
</ul>
|
|
348
349
|
```
|
|
349
350
|
|
|
350
|
-
```ts src/contacts/listContacts.ts
|
|
351
|
+
```ts src/contacts/listContacts.handler.ts
|
|
351
352
|
import { GET } from '@abide/abide/server'
|
|
353
|
+
|
|
352
354
|
import { CONTACTS } from './lib/CONTACTS.server.ts'
|
|
353
355
|
|
|
354
356
|
export default GET(async () => {
|
|
@@ -360,7 +362,8 @@ export default GET(async () => {
|
|
|
360
362
|
|
|
361
363
|
```ts src/contacts/lib/ContactList.test.ts
|
|
362
364
|
import { expect, test } from 'bun:test'
|
|
363
|
-
|
|
365
|
+
|
|
366
|
+
import listContacts from '../listContacts.handler.ts'
|
|
364
367
|
|
|
365
368
|
test('the handler the list reads answers with every contact', async () => {
|
|
366
369
|
const contacts = await listContacts()
|
|
@@ -372,7 +375,7 @@ test('the handler the list reads answers with every contact', async () => {
|
|
|
372
375
|
})
|
|
373
376
|
```
|
|
374
377
|
|
|
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.
|
|
378
|
+
`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
379
|
|
|
377
380
|
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
381
|
|
|
@@ -383,7 +386,8 @@ The test imports the same handler and calls it, in process. A handler only one p
|
|
|
383
386
|
```abide src/contacts/lib/PageTiming.abide
|
|
384
387
|
<script>
|
|
385
388
|
import { reactive } from '@abide/abide'
|
|
386
|
-
|
|
389
|
+
|
|
390
|
+
import listContacts from '../listContacts.handler.ts'
|
|
387
391
|
|
|
388
392
|
let pageComplete = reactive('—')
|
|
389
393
|
let contactsAnswered = reactive('—')
|
|
@@ -411,6 +415,7 @@ async function requestPage() {
|
|
|
411
415
|
|
|
412
416
|
```ts src/contacts/lib/PageTiming.test.ts
|
|
413
417
|
import { expect, test } from 'bun:test'
|
|
418
|
+
|
|
414
419
|
import { createApp } from '@abide/abide/server'
|
|
415
420
|
|
|
416
421
|
test('the contacts page is complete before the contacts land', async () => {
|
|
@@ -450,8 +455,10 @@ let starred = reactive(false)
|
|
|
450
455
|
|
|
451
456
|
```ts src/contacts/lib/StarButton.test.ts
|
|
452
457
|
import { expect, test } from 'bun:test'
|
|
458
|
+
|
|
453
459
|
import { flushSync } from '@abide/abide'
|
|
454
460
|
import { mount } from '@abide/harness/abide'
|
|
461
|
+
|
|
455
462
|
import StarButton from './StarButton.abide'
|
|
456
463
|
|
|
457
464
|
test('the click writes starred and the button follows', async () => {
|
|
@@ -473,7 +480,8 @@ Naming `starred` in the markup reads it and follows it from then on. Assigning t
|
|
|
473
480
|
```abide src/contacts/lib/ContactNote.abide
|
|
474
481
|
<script>
|
|
475
482
|
import { reactive } from '@abide/abide'
|
|
476
|
-
|
|
483
|
+
|
|
484
|
+
import listContacts from '../listContacts.handler.ts'
|
|
477
485
|
|
|
478
486
|
// Owned, derived, loaded, and waiting for its first write.
|
|
479
487
|
let draft = reactive('')
|
|
@@ -502,8 +510,10 @@ let saved = reactive<string>()
|
|
|
502
510
|
|
|
503
511
|
```ts src/contacts/lib/ContactNote.test.ts
|
|
504
512
|
import { expect, test } from 'bun:test'
|
|
513
|
+
|
|
505
514
|
import { flushSync } from '@abide/abide'
|
|
506
515
|
import { mount } from '@abide/harness/abide'
|
|
516
|
+
|
|
507
517
|
import ContactNote from './ContactNote.abide'
|
|
508
518
|
|
|
509
519
|
const savedNote = () => document.querySelectorAll('strong')[3]?.textContent
|
|
@@ -574,8 +584,10 @@ let email = reactive('ada@example.com')
|
|
|
574
584
|
|
|
575
585
|
```ts src/profile/lib/ContactFields.test.ts
|
|
576
586
|
import { expect, test } from 'bun:test'
|
|
587
|
+
|
|
577
588
|
import { flushSync } from '@abide/abide'
|
|
578
589
|
import { mount } from '@abide/harness/abide'
|
|
590
|
+
|
|
579
591
|
import ContactFields from './ContactFields.abide'
|
|
580
592
|
|
|
581
593
|
test('typing writes the value, and the value fills the field', async () => {
|
|
@@ -638,8 +650,10 @@ let phone = reactive('', {
|
|
|
638
650
|
|
|
639
651
|
```ts src/profile/lib/PhoneField.test.ts
|
|
640
652
|
import { expect, test } from 'bun:test'
|
|
653
|
+
|
|
641
654
|
import { flushSync } from '@abide/abide'
|
|
642
655
|
import { mount } from '@abide/harness/abide'
|
|
656
|
+
|
|
643
657
|
import PhoneField from './PhoneField.abide'
|
|
644
658
|
|
|
645
659
|
test('the record keeps the digits, and the field shows them back', async () => {
|
|
@@ -687,8 +701,10 @@ const filled = reactive(() => [name, phone].filter(Boolean).length)
|
|
|
687
701
|
|
|
688
702
|
```ts src/profile/lib/ProfileSummary.test.ts
|
|
689
703
|
import { expect, test } from 'bun:test'
|
|
704
|
+
|
|
690
705
|
import { flushSync } from '@abide/abide'
|
|
691
706
|
import { measure, mount } from '@abide/harness/abide'
|
|
707
|
+
|
|
692
708
|
import ProfileSummary from './ProfileSummary.abide'
|
|
693
709
|
|
|
694
710
|
const shown = () =>
|
|
@@ -747,8 +763,10 @@ let contact = reactive({
|
|
|
747
763
|
|
|
748
764
|
```ts src/profile/lib/AddressField.test.ts
|
|
749
765
|
import { expect, test } from 'bun:test'
|
|
766
|
+
|
|
750
767
|
import { flushSync } from '@abide/abide'
|
|
751
768
|
import { mount } from '@abide/harness/abide'
|
|
769
|
+
|
|
752
770
|
import AddressField from './AddressField.abide'
|
|
753
771
|
|
|
754
772
|
test('a write to the city reaches the record it sits in', async () => {
|
|
@@ -809,8 +827,10 @@ let tags = reactive(['investor', 'speaker', 'mentor'])
|
|
|
809
827
|
|
|
810
828
|
```ts src/profile/lib/Tags.test.ts
|
|
811
829
|
import { expect, test } from 'bun:test'
|
|
830
|
+
|
|
812
831
|
import { flushSync } from '@abide/abide'
|
|
813
832
|
import { measure, mount } from '@abide/harness/abide'
|
|
833
|
+
|
|
814
834
|
import Tags from './Tags.abide'
|
|
815
835
|
|
|
816
836
|
test('removing a tag removes its row and rewrites no other', async () => {
|
|
@@ -852,8 +872,10 @@ let followUp = reactive(false)
|
|
|
852
872
|
|
|
853
873
|
```ts src/profile/lib/FollowUp.test.ts
|
|
854
874
|
import { expect, test } from 'bun:test'
|
|
875
|
+
|
|
855
876
|
import { flushSync } from '@abide/abide'
|
|
856
877
|
import { mount } from '@abide/harness/abide'
|
|
878
|
+
|
|
857
879
|
import FollowUp from './FollowUp.abide'
|
|
858
880
|
|
|
859
881
|
const dateField = () => document.querySelector('input[type="date"]')
|
|
@@ -882,6 +904,7 @@ test('the date field is in the page only while the box is ticked', async () => {
|
|
|
882
904
|
```abide src/profile/lib/ContactCard.abide
|
|
883
905
|
<script>
|
|
884
906
|
import { reactive } from '@abide/abide'
|
|
907
|
+
|
|
885
908
|
import Avatar from './Avatar.abide'
|
|
886
909
|
|
|
887
910
|
let name = reactive('Grace Hopper')
|
|
@@ -901,14 +924,19 @@ const { name } = props<{ name: string }>()
|
|
|
901
924
|
</script>
|
|
902
925
|
|
|
903
926
|
<span class="rounded-full border border-rule bg-raised px-2 py-1 font-semibold">
|
|
904
|
-
{name
|
|
927
|
+
{ name
|
|
928
|
+
.split(' ')
|
|
929
|
+
.map((word) => word.charAt(0))
|
|
930
|
+
.join('')}
|
|
905
931
|
</span>
|
|
906
932
|
```
|
|
907
933
|
|
|
908
934
|
```ts src/profile/lib/ContactCard.test.ts
|
|
909
935
|
import { expect, test } from 'bun:test'
|
|
936
|
+
|
|
910
937
|
import { flushSync } from '@abide/abide'
|
|
911
938
|
import { measure, mount } from '@abide/harness/abide'
|
|
939
|
+
|
|
912
940
|
import ContactCard from './ContactCard.abide'
|
|
913
941
|
|
|
914
942
|
test('the avatar follows its name prop, and is not rebuilt', async () => {
|
|
@@ -976,7 +1004,9 @@ import LeadBadge from './LeadBadge.abide'
|
|
|
976
1004
|
|
|
977
1005
|
```ts src/profile/lib/StageBadges.test.ts
|
|
978
1006
|
import { expect, test } from 'bun:test'
|
|
1007
|
+
|
|
979
1008
|
import { mount } from '@abide/harness/abide'
|
|
1009
|
+
|
|
980
1010
|
import StageBadges from './StageBadges.abide'
|
|
981
1011
|
|
|
982
1012
|
// The scope token: an attribute every rule in a `<style>` requires.
|
|
@@ -1045,8 +1075,10 @@ let calls = reactive(0)
|
|
|
1045
1075
|
|
|
1046
1076
|
```ts src/profile/lib/CallLog.test.ts
|
|
1047
1077
|
import { expect, test } from 'bun:test'
|
|
1078
|
+
|
|
1048
1079
|
import { flushSync } from '@abide/abide'
|
|
1049
1080
|
import { mount } from '@abide/harness/abide'
|
|
1081
|
+
|
|
1050
1082
|
import CallLog from './CallLog.abide'
|
|
1051
1083
|
|
|
1052
1084
|
const rows = () =>
|
|
@@ -1085,15 +1117,15 @@ Every server call in this chapter is a handler: a function wrapped in `GET`, `PO
|
|
|
1085
1117
|
|
|
1086
1118
|
## A handler file answers at its own path
|
|
1087
1119
|
|
|
1088
|
-
A
|
|
1120
|
+
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
1121
|
|
|
1090
1122
|
| File below `src` | Answers on |
|
|
1091
1123
|
| --- | --- |
|
|
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 |
|
|
1124
|
+
| `**/<name>.handler.ts` | its directory's route, then `<name>` |
|
|
1125
|
+
| `deals/getDeal.handler.ts` | `GET /deals/getDeal` |
|
|
1126
|
+
| `users/getUser.handler.ts` | `GET /users/getUser` |
|
|
1127
|
+
| `health.handler.ts` | `GET /health` |
|
|
1128
|
+
| `media/[id]/save.handler.ts` | `POST /media/42/save`, with `id` in its args |
|
|
1097
1129
|
| `media/[id]/lib/Comments.abide` | `/__abide/handlers/media/id/lib/Comments/addComment`, for the `addComment` its `<script server>` declares |
|
|
1098
1130
|
|
|
1099
1131
|
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 +1137,8 @@ A handler file exports its handler and nothing else. A page imports it, a script
|
|
|
1105
1137
|
```abide src/deals/lib/DealDetail.abide
|
|
1106
1138
|
<script>
|
|
1107
1139
|
import { reactive } from '@abide/abide'
|
|
1108
|
-
|
|
1140
|
+
|
|
1141
|
+
import getDeal from '../getDeal.handler.ts'
|
|
1109
1142
|
|
|
1110
1143
|
let id = reactive('d1')
|
|
1111
1144
|
|
|
@@ -1113,10 +1146,12 @@ const deal = reactive(() => getDeal({ id }))
|
|
|
1113
1146
|
</script>
|
|
1114
1147
|
|
|
1115
1148
|
<div class="tabs">
|
|
1116
|
-
{#for one of ['d1', 'd2']}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1149
|
+
{#for one of ['d1', 'd2']}
|
|
1150
|
+
<button aria-selected={id === one}
|
|
1151
|
+
onclick={() => {
|
|
1152
|
+
id = one
|
|
1153
|
+
}}>{one}</button>
|
|
1154
|
+
{/for}
|
|
1120
1155
|
</div>
|
|
1121
1156
|
|
|
1122
1157
|
<ul class="fields stacked">
|
|
@@ -1125,9 +1160,10 @@ const deal = reactive(() => getDeal({ id }))
|
|
|
1125
1160
|
</ul>
|
|
1126
1161
|
```
|
|
1127
1162
|
|
|
1128
|
-
```ts src/deals/getDeal.ts
|
|
1163
|
+
```ts src/deals/getDeal.handler.ts
|
|
1129
1164
|
import { refuse } from '@abide/abide'
|
|
1130
1165
|
import { GET } from '@abide/abide/server'
|
|
1166
|
+
|
|
1131
1167
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
1132
1168
|
|
|
1133
1169
|
/** Answers at /deals/getDeal?id=…; the argument type is the schema. */
|
|
@@ -1143,7 +1179,8 @@ export default GET(
|
|
|
1143
1179
|
|
|
1144
1180
|
```ts src/deals/lib/DealDetail.test.ts
|
|
1145
1181
|
import { expect, test } from 'bun:test'
|
|
1146
|
-
|
|
1182
|
+
|
|
1183
|
+
import getDeal from '../getDeal.handler.ts'
|
|
1147
1184
|
|
|
1148
1185
|
test('the handler answers the deal its args name', async () => {
|
|
1149
1186
|
const deal = await getDeal({ id: 'd2' })
|
|
@@ -1182,14 +1219,15 @@ const notes = reactive(() => getNotes({ id: 'd1' }))
|
|
|
1182
1219
|
</script>
|
|
1183
1220
|
|
|
1184
1221
|
<ul class="fields stacked">
|
|
1185
|
-
{#for note of notes}
|
|
1186
|
-
|
|
1187
|
-
{/for}
|
|
1222
|
+
{#for note of notes}
|
|
1223
|
+
<li><span>note</span><strong>{note}</strong></li>
|
|
1224
|
+
{/for}
|
|
1188
1225
|
</ul>
|
|
1189
1226
|
```
|
|
1190
1227
|
|
|
1191
1228
|
```ts src/deals/lib/DealNotes.test.ts
|
|
1192
1229
|
import { expect, test } from 'bun:test'
|
|
1230
|
+
|
|
1193
1231
|
import { createApp } from '@abide/abide/server'
|
|
1194
1232
|
|
|
1195
1233
|
// The file's path, then the handler's name.
|
|
@@ -1226,7 +1264,8 @@ It still answers over http, below `/__abide/handlers` at its file's path and the
|
|
|
1226
1264
|
```abide src/deals/lib/NewDeal.abide
|
|
1227
1265
|
<script>
|
|
1228
1266
|
import { type Issues, reactive } from '@abide/abide'
|
|
1229
|
-
|
|
1267
|
+
|
|
1268
|
+
import createDeal from '../createDeal.handler.ts'
|
|
1230
1269
|
|
|
1231
1270
|
type NewDeal = { name: string; company: string; amount: number }
|
|
1232
1271
|
|
|
@@ -1255,15 +1294,16 @@ async function add() {
|
|
|
1255
1294
|
</div>
|
|
1256
1295
|
|
|
1257
1296
|
<ul class="fields">
|
|
1258
|
-
{#for [field, messages] of Object.entries(issues)}
|
|
1259
|
-
|
|
1260
|
-
{/for}
|
|
1297
|
+
{#for [field, messages] of Object.entries(issues)}
|
|
1298
|
+
<li><span>{field}</span><strong>{messages?.[0]}</strong></li>
|
|
1299
|
+
{/for}
|
|
1261
1300
|
</ul>
|
|
1262
1301
|
```
|
|
1263
1302
|
|
|
1264
|
-
```ts src/deals/createDeal.ts
|
|
1303
|
+
```ts src/deals/createDeal.handler.ts
|
|
1265
1304
|
import { jsonSchema } from '@abide/abide'
|
|
1266
1305
|
import { POST } from '@abide/abide/server'
|
|
1306
|
+
|
|
1267
1307
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
1268
1308
|
|
|
1269
1309
|
type NewDeal = { name: string; company: string; amount: number }
|
|
@@ -1297,6 +1337,7 @@ export default POST(
|
|
|
1297
1337
|
|
|
1298
1338
|
```ts src/deals/lib/NewDeal.test.ts
|
|
1299
1339
|
import { expect, test } from 'bun:test'
|
|
1340
|
+
|
|
1300
1341
|
import { createApp } from '@abide/abide/server'
|
|
1301
1342
|
|
|
1302
1343
|
test('a deal that does not fit is refused, field by field', async () => {
|
|
@@ -1341,8 +1382,9 @@ Two presses of Add inside one flight send one request and write one deal, and bo
|
|
|
1341
1382
|
```abide src/deals/lib/DealStage.abide
|
|
1342
1383
|
<script>
|
|
1343
1384
|
import { reactive } from '@abide/abide'
|
|
1344
|
-
|
|
1345
|
-
import
|
|
1385
|
+
|
|
1386
|
+
import getDeal from '../getDeal.handler.ts'
|
|
1387
|
+
import moveDeal from '../moveDeal.handler.ts'
|
|
1346
1388
|
|
|
1347
1389
|
let id = reactive('d2')
|
|
1348
1390
|
let refusal = reactive('')
|
|
@@ -1362,10 +1404,13 @@ async function move(stage: string) {
|
|
|
1362
1404
|
</script>
|
|
1363
1405
|
|
|
1364
1406
|
<div class="tabs">
|
|
1365
|
-
{#for one of ['d2', 'd3']}
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1407
|
+
{#for one of ['d2', 'd3']}
|
|
1408
|
+
<button aria-selected={id === one}
|
|
1409
|
+
onclick={() => {
|
|
1410
|
+
id = one
|
|
1411
|
+
refusal = ''
|
|
1412
|
+
}}>{one}</button>
|
|
1413
|
+
{/for}
|
|
1369
1414
|
</div>
|
|
1370
1415
|
|
|
1371
1416
|
<ul class="fields">
|
|
@@ -1373,17 +1418,18 @@ async function move(stage: string) {
|
|
|
1373
1418
|
</ul>
|
|
1374
1419
|
|
|
1375
1420
|
<div class="row">
|
|
1376
|
-
{#for stage of ['lead', 'qualified', 'proposal']}
|
|
1377
|
-
|
|
1378
|
-
{/for}
|
|
1421
|
+
{#for stage of ['lead', 'qualified', 'proposal']}
|
|
1422
|
+
<button onclick={() => move(stage)}>{stage}</button>
|
|
1423
|
+
{/for}
|
|
1379
1424
|
</div>
|
|
1380
1425
|
|
|
1381
1426
|
<p class="status">{refusal}</p>
|
|
1382
1427
|
```
|
|
1383
1428
|
|
|
1384
|
-
```ts src/deals/moveDeal.ts
|
|
1429
|
+
```ts src/deals/moveDeal.handler.ts
|
|
1385
1430
|
import { jsonSchema, refuse } from '@abide/abide'
|
|
1386
1431
|
import { PATCH } from '@abide/abide/server'
|
|
1432
|
+
|
|
1387
1433
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
1388
1434
|
|
|
1389
1435
|
// A refusal with a name and typed data, which the page can match.
|
|
@@ -1409,7 +1455,8 @@ export default PATCH(
|
|
|
1409
1455
|
|
|
1410
1456
|
```ts src/deals/lib/DealStage.test.ts
|
|
1411
1457
|
import { expect, test } from 'bun:test'
|
|
1412
|
-
|
|
1458
|
+
|
|
1459
|
+
import moveDeal from '../moveDeal.handler.ts'
|
|
1413
1460
|
|
|
1414
1461
|
test('a closed deal is refused by name, with its stage as data', async () => {
|
|
1415
1462
|
try {
|
|
@@ -1440,7 +1487,7 @@ Every refusal is a `Failed<Name, Data>`, in process and over the wire alike. The
|
|
|
1440
1487
|
|
|
1441
1488
|
```abide src/deals/lib/ExportDeals.abide
|
|
1442
1489
|
<script>
|
|
1443
|
-
import exportDeals from '../exportDeals.ts'
|
|
1490
|
+
import exportDeals from '../exportDeals.handler.ts'
|
|
1444
1491
|
</script>
|
|
1445
1492
|
|
|
1446
1493
|
<div class="row">
|
|
@@ -1448,8 +1495,9 @@ import exportDeals from '../exportDeals.ts'
|
|
|
1448
1495
|
</div>
|
|
1449
1496
|
```
|
|
1450
1497
|
|
|
1451
|
-
```ts src/deals/exportDeals.ts
|
|
1498
|
+
```ts src/deals/exportDeals.handler.ts
|
|
1452
1499
|
import { GET } from '@abide/abide/server'
|
|
1500
|
+
|
|
1453
1501
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
1454
1502
|
|
|
1455
1503
|
/** A Blob answers with its own type: this one is a CSV file. */
|
|
@@ -1466,7 +1514,8 @@ export default GET(
|
|
|
1466
1514
|
|
|
1467
1515
|
```ts src/deals/lib/ExportDeals.test.ts
|
|
1468
1516
|
import { expect, test } from 'bun:test'
|
|
1469
|
-
|
|
1517
|
+
|
|
1518
|
+
import exportDeals from '../exportDeals.handler.ts'
|
|
1470
1519
|
|
|
1471
1520
|
test('the export answers a CSV file', async () => {
|
|
1472
1521
|
const file = await exportDeals()
|
|
@@ -1504,11 +1553,11 @@ Every answer carries headers you did not write:
|
|
|
1504
1553
|
```
|
|
1505
1554
|
abide routes
|
|
1506
1555
|
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
|
|
1556
|
+
POST /deals/createDeal deals/createDeal.handler.ts
|
|
1557
|
+
GET /deals/exportDeals deals/exportDeals.handler.ts
|
|
1558
|
+
GET /deals/getDeal deals/getDeal.handler.ts
|
|
1559
|
+
GET /deals/listDeals deals/listDeals.handler.ts
|
|
1560
|
+
PATCH /deals/moveDeal deals/moveDeal.handler.ts
|
|
1512
1561
|
```
|
|
1513
1562
|
|
|
1514
1563
|
`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 +1569,8 @@ PATCH /deals/moveDeal deals/moveDeal.ts
|
|
|
1520
1569
|
```abide src/deals/lib/Pipeline.abide
|
|
1521
1570
|
<script>
|
|
1522
1571
|
import { reactive, refresh } from '@abide/abide'
|
|
1523
|
-
|
|
1572
|
+
|
|
1573
|
+
import listDeals from '../listDeals.handler.ts'
|
|
1524
1574
|
|
|
1525
1575
|
const deals = reactive(() => listDeals())
|
|
1526
1576
|
|
|
@@ -1544,8 +1594,9 @@ const total = reactive(() => {
|
|
|
1544
1594
|
</ul>
|
|
1545
1595
|
```
|
|
1546
1596
|
|
|
1547
|
-
```ts src/deals/listDeals.ts
|
|
1597
|
+
```ts src/deals/listDeals.handler.ts
|
|
1548
1598
|
import { GET } from '@abide/abide/server'
|
|
1599
|
+
|
|
1549
1600
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
1550
1601
|
|
|
1551
1602
|
export default GET(
|
|
@@ -1560,8 +1611,10 @@ export default GET(
|
|
|
1560
1611
|
|
|
1561
1612
|
```ts src/deals/lib/Pipeline.test.ts
|
|
1562
1613
|
import { expect, test } from 'bun:test'
|
|
1614
|
+
|
|
1563
1615
|
import { reactive, refresh } from '@abide/abide'
|
|
1564
|
-
|
|
1616
|
+
|
|
1617
|
+
import listDeals from '../listDeals.handler.ts'
|
|
1565
1618
|
|
|
1566
1619
|
test('a refresh is busy and still ready; a first load is not', async () => {
|
|
1567
1620
|
const deals = reactive(() => listDeals())
|
|
@@ -1612,15 +1665,16 @@ import StageValue from './StageValue.abide'
|
|
|
1612
1665
|
</script>
|
|
1613
1666
|
|
|
1614
1667
|
<div class="row">
|
|
1615
|
-
<StageCount/>
|
|
1616
|
-
<StageValue/>
|
|
1668
|
+
<StageCount />
|
|
1669
|
+
<StageValue />
|
|
1617
1670
|
</div>
|
|
1618
1671
|
```
|
|
1619
1672
|
|
|
1620
1673
|
```abide src/pipeline/lib/StageCount.abide
|
|
1621
1674
|
<script>
|
|
1622
1675
|
import { reactive } from '@abide/abide'
|
|
1623
|
-
|
|
1676
|
+
|
|
1677
|
+
import getStage from '../getStage.handler.ts'
|
|
1624
1678
|
import { STAGES } from './STAGES.ts'
|
|
1625
1679
|
|
|
1626
1680
|
let stage = reactive('lead')
|
|
@@ -1630,9 +1684,9 @@ const totals = reactive(() => getStage({ stage }))
|
|
|
1630
1684
|
<ul class="fields stacked">
|
|
1631
1685
|
<li><span>stage</span>
|
|
1632
1686
|
<select bind:value={stage}>
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1687
|
+
{#for one of STAGES}
|
|
1688
|
+
<option value={one}>{one}</option>
|
|
1689
|
+
{/for}
|
|
1636
1690
|
</select></li>
|
|
1637
1691
|
<li><span>deals</span>
|
|
1638
1692
|
<strong>{totals.ready() ? totals.count : '—'}</strong></li>
|
|
@@ -1642,7 +1696,8 @@ const totals = reactive(() => getStage({ stage }))
|
|
|
1642
1696
|
```abide src/pipeline/lib/StageValue.abide
|
|
1643
1697
|
<script>
|
|
1644
1698
|
import { reactive } from '@abide/abide'
|
|
1645
|
-
|
|
1699
|
+
|
|
1700
|
+
import getStage from '../getStage.handler.ts'
|
|
1646
1701
|
import { dollars } from './dollars.ts'
|
|
1647
1702
|
import { STAGES } from './STAGES.ts'
|
|
1648
1703
|
|
|
@@ -1654,17 +1709,18 @@ const totals = reactive(() => getStage({ stage }))
|
|
|
1654
1709
|
<ul class="fields stacked">
|
|
1655
1710
|
<li><span>stage</span>
|
|
1656
1711
|
<select bind:value={stage}>
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1712
|
+
{#for one of STAGES}
|
|
1713
|
+
<option value={one}>{one}</option>
|
|
1714
|
+
{/for}
|
|
1660
1715
|
</select></li>
|
|
1661
1716
|
<li><span>value</span>
|
|
1662
1717
|
<strong>{totals.ready() ? dollars(totals.value) : '—'}</strong></li>
|
|
1663
1718
|
</ul>
|
|
1664
1719
|
```
|
|
1665
1720
|
|
|
1666
|
-
```ts src/pipeline/getStage.ts
|
|
1721
|
+
```ts src/pipeline/getStage.handler.ts
|
|
1667
1722
|
import { GET } from '@abide/abide/server'
|
|
1723
|
+
|
|
1668
1724
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
1669
1725
|
import { LOADS } from './lib/LOADS.server.ts'
|
|
1670
1726
|
|
|
@@ -1689,10 +1745,12 @@ export default GET(
|
|
|
1689
1745
|
|
|
1690
1746
|
```ts src/pipeline/lib/StageTiles.test.ts
|
|
1691
1747
|
import { expect, test } from 'bun:test'
|
|
1748
|
+
|
|
1692
1749
|
import { mount } from '@abide/harness/abide'
|
|
1693
|
-
|
|
1694
|
-
import
|
|
1750
|
+
|
|
1751
|
+
import getStage from '../getStage.handler.ts'
|
|
1695
1752
|
import { LOADS } from './LOADS.server.ts'
|
|
1753
|
+
import StageTiles from './StageTiles.abide'
|
|
1696
1754
|
|
|
1697
1755
|
function pick(index: number, stage: string): void {
|
|
1698
1756
|
const select = document.querySelectorAll('select')[index]
|
|
@@ -1732,7 +1790,8 @@ A body with a parameter is a keyed value: one entry per set of arguments, held.
|
|
|
1732
1790
|
```abide src/pipeline/lib/Forecast.abide
|
|
1733
1791
|
<script>
|
|
1734
1792
|
import { reactive } from '@abide/abide'
|
|
1735
|
-
|
|
1793
|
+
|
|
1794
|
+
import getForecast from '../getForecast.handler.ts'
|
|
1736
1795
|
import { dollars } from './dollars.ts'
|
|
1737
1796
|
|
|
1738
1797
|
const forecast = reactive(() => getForecast())
|
|
@@ -1754,8 +1813,9 @@ const reloading = reactive(() => forecast.busy() && forecast.ready())
|
|
|
1754
1813
|
</div>
|
|
1755
1814
|
```
|
|
1756
1815
|
|
|
1757
|
-
```ts src/pipeline/getForecast.ts
|
|
1816
|
+
```ts src/pipeline/getForecast.handler.ts
|
|
1758
1817
|
import { GET } from '@abide/abide/server'
|
|
1818
|
+
|
|
1759
1819
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
1760
1820
|
import { LOADS } from './lib/LOADS.server.ts'
|
|
1761
1821
|
|
|
@@ -1784,8 +1844,10 @@ export default GET(
|
|
|
1784
1844
|
|
|
1785
1845
|
```ts src/pipeline/lib/Forecast.test.ts
|
|
1786
1846
|
import { expect, test } from 'bun:test'
|
|
1847
|
+
|
|
1787
1848
|
import { flushSync } from '@abide/abide'
|
|
1788
1849
|
import { mount } from '@abide/harness/abide'
|
|
1850
|
+
|
|
1789
1851
|
import Forecast from './Forecast.abide'
|
|
1790
1852
|
import { LOADS } from './LOADS.server.ts'
|
|
1791
1853
|
|
|
@@ -1836,10 +1898,10 @@ Press Mark stale and then Reload now, and the forecast blanks between the two. T
|
|
|
1836
1898
|
|
|
1837
1899
|
```abide src/pipeline/lib/StageBoard.abide
|
|
1838
1900
|
<script>
|
|
1839
|
-
import getStage from '../getStage.ts'
|
|
1840
|
-
import winDeal from '../winDeal.ts'
|
|
1841
|
-
import StageTile from './StageTile.abide'
|
|
1901
|
+
import getStage from '../getStage.handler.ts'
|
|
1902
|
+
import winDeal from '../winDeal.handler.ts'
|
|
1842
1903
|
import { STAGES } from './STAGES.ts'
|
|
1904
|
+
import StageTile from './StageTile.abide'
|
|
1843
1905
|
|
|
1844
1906
|
async function win() {
|
|
1845
1907
|
await winDeal()
|
|
@@ -1850,9 +1912,9 @@ async function win() {
|
|
|
1850
1912
|
</script>
|
|
1851
1913
|
|
|
1852
1914
|
<ul class="tiles">
|
|
1853
|
-
{#for stage of STAGES}
|
|
1854
|
-
|
|
1855
|
-
{/for}
|
|
1915
|
+
{#for stage of STAGES}
|
|
1916
|
+
<StageTile stage={stage} />
|
|
1917
|
+
{/for}
|
|
1856
1918
|
</ul>
|
|
1857
1919
|
|
|
1858
1920
|
<div class="row">
|
|
@@ -1863,7 +1925,8 @@ async function win() {
|
|
|
1863
1925
|
```abide src/pipeline/lib/StageTile.abide
|
|
1864
1926
|
<script>
|
|
1865
1927
|
import { props, reactive } from '@abide/abide'
|
|
1866
|
-
|
|
1928
|
+
|
|
1929
|
+
import getStage from '../getStage.handler.ts'
|
|
1867
1930
|
|
|
1868
1931
|
const { stage } = props<{ stage: string }>()
|
|
1869
1932
|
const totals = reactive(() => getStage({ stage }))
|
|
@@ -1875,8 +1938,9 @@ const totals = reactive(() => getStage({ stage }))
|
|
|
1875
1938
|
</li>
|
|
1876
1939
|
```
|
|
1877
1940
|
|
|
1878
|
-
```ts src/pipeline/winDeal.ts
|
|
1941
|
+
```ts src/pipeline/winDeal.handler.ts
|
|
1879
1942
|
import { POST } from '@abide/abide/server'
|
|
1943
|
+
|
|
1880
1944
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
1881
1945
|
|
|
1882
1946
|
/** Moves the first open lead to won, and answers with its id. */
|
|
@@ -1892,10 +1956,12 @@ export default POST(
|
|
|
1892
1956
|
|
|
1893
1957
|
```ts src/pipeline/lib/StageBoard.test.ts
|
|
1894
1958
|
import { expect, test } from 'bun:test'
|
|
1959
|
+
|
|
1895
1960
|
import { mount } from '@abide/harness/abide'
|
|
1896
|
-
|
|
1897
|
-
import StageBoard from './StageBoard.abide'
|
|
1961
|
+
|
|
1898
1962
|
import { LOADS } from './LOADS.server.ts'
|
|
1963
|
+
import StageBoard from './StageBoard.abide'
|
|
1964
|
+
import { STAGES } from './STAGES.ts'
|
|
1899
1965
|
|
|
1900
1966
|
function counted(): number[] {
|
|
1901
1967
|
return STAGES.map((stage) => LOADS[stage] ?? 0)
|
|
@@ -1934,7 +2000,8 @@ The triggers also come as free functions, and those take a `Selection`: the set
|
|
|
1934
2000
|
```abide src/pipeline/lib/DealSearch.abide
|
|
1935
2001
|
<script>
|
|
1936
2002
|
import { reactive } from '@abide/abide'
|
|
1937
|
-
|
|
2003
|
+
|
|
2004
|
+
import searchDeals from '../searchDeals.handler.ts'
|
|
1938
2005
|
|
|
1939
2006
|
let q = reactive('')
|
|
1940
2007
|
const found = reactive(() => searchDeals({ q }), { debounce: 300 })
|
|
@@ -1945,14 +2012,15 @@ const found = reactive(() => searchDeals({ q }), { debounce: 300 })
|
|
|
1945
2012
|
</label>
|
|
1946
2013
|
|
|
1947
2014
|
<ul class="fields">
|
|
1948
|
-
{#for name of found.ready() ? found : []}
|
|
1949
|
-
|
|
1950
|
-
{/for}
|
|
2015
|
+
{#for name of found.ready() ? found : []}
|
|
2016
|
+
<li><strong>{name}</strong></li>
|
|
2017
|
+
{/for}
|
|
1951
2018
|
</ul>
|
|
1952
2019
|
```
|
|
1953
2020
|
|
|
1954
|
-
```ts src/pipeline/searchDeals.ts
|
|
2021
|
+
```ts src/pipeline/searchDeals.handler.ts
|
|
1955
2022
|
import { GET } from '@abide/abide/server'
|
|
2023
|
+
|
|
1956
2024
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
1957
2025
|
import { LOADS } from './lib/LOADS.server.ts'
|
|
1958
2026
|
|
|
@@ -1974,7 +2042,9 @@ export default GET(
|
|
|
1974
2042
|
|
|
1975
2043
|
```ts src/pipeline/lib/DealSearch.test.ts
|
|
1976
2044
|
import { expect, test } from 'bun:test'
|
|
2045
|
+
|
|
1977
2046
|
import { mount } from '@abide/harness/abide'
|
|
2047
|
+
|
|
1978
2048
|
import DealSearch from './DealSearch.abide'
|
|
1979
2049
|
import { LOADS } from './LOADS.server.ts'
|
|
1980
2050
|
|
|
@@ -2020,7 +2090,8 @@ Every keystroke is a new `q`, and so a new key. `debounce: 300` holds the search
|
|
|
2020
2090
|
```abide src/pipeline/lib/DealFeed.abide
|
|
2021
2091
|
<script>
|
|
2022
2092
|
import { reactive } from '@abide/abide'
|
|
2023
|
-
|
|
2093
|
+
|
|
2094
|
+
import listDeals from '../listDeals.handler.ts'
|
|
2024
2095
|
import { dollars } from './dollars.ts'
|
|
2025
2096
|
|
|
2026
2097
|
const deals = reactive(() => listDeals())
|
|
@@ -2044,10 +2115,10 @@ function applyNextUpdate() {
|
|
|
2044
2115
|
</script>
|
|
2045
2116
|
|
|
2046
2117
|
<ul class="fields">
|
|
2047
|
-
{#for deal of deals.ready() ? deals : [] by deal.id}
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
{/for}
|
|
2118
|
+
{#for deal of deals.ready() ? deals : [] by deal.id}
|
|
2119
|
+
<li><span>{deal.stage}</span><strong>{dollars(deal.amount)}</strong>
|
|
2120
|
+
{deal.name}</li>
|
|
2121
|
+
{/for}
|
|
2051
2122
|
</ul>
|
|
2052
2123
|
|
|
2053
2124
|
<div class="row">
|
|
@@ -2055,8 +2126,9 @@ function applyNextUpdate() {
|
|
|
2055
2126
|
</div>
|
|
2056
2127
|
```
|
|
2057
2128
|
|
|
2058
|
-
```ts src/pipeline/listDeals.ts
|
|
2129
|
+
```ts src/pipeline/listDeals.handler.ts
|
|
2059
2130
|
import { GET } from '@abide/abide/server'
|
|
2131
|
+
|
|
2060
2132
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
2061
2133
|
|
|
2062
2134
|
/** Every open deal, as a list. */
|
|
@@ -2072,8 +2144,10 @@ export default GET(
|
|
|
2072
2144
|
|
|
2073
2145
|
```ts src/pipeline/lib/DealFeed.test.ts
|
|
2074
2146
|
import { expect, test } from 'bun:test'
|
|
2147
|
+
|
|
2075
2148
|
import { flushSync } from '@abide/abide'
|
|
2076
2149
|
import { measure, mount } from '@abide/harness/abide'
|
|
2150
|
+
|
|
2077
2151
|
import DealFeed from './DealFeed.abide'
|
|
2078
2152
|
|
|
2079
2153
|
test('an update rewrites one amount and builds nothing', async () => {
|
|
@@ -2109,7 +2183,8 @@ Change the held value without `patch` and nothing on the page moves, because no
|
|
|
2109
2183
|
```abide src/pipeline/lib/DealPicker.abide
|
|
2110
2184
|
<script>
|
|
2111
2185
|
import { reactive } from '@abide/abide'
|
|
2112
|
-
|
|
2186
|
+
|
|
2187
|
+
import listDeals from '../listDeals.handler.ts'
|
|
2113
2188
|
import DealDetail from './DealDetail.abide'
|
|
2114
2189
|
import DealRow from './DealRow.abide'
|
|
2115
2190
|
|
|
@@ -2119,12 +2194,12 @@ const deals = reactive(() => listDeals())
|
|
|
2119
2194
|
reactive.share('selectedDeal', () => reactive('d1'))
|
|
2120
2195
|
</script>
|
|
2121
2196
|
|
|
2122
|
-
<DealDetail/>
|
|
2197
|
+
<DealDetail />
|
|
2123
2198
|
|
|
2124
2199
|
<div class="row">
|
|
2125
|
-
{#for deal of deals.ready() ? deals : [] by deal.id}
|
|
2126
|
-
|
|
2127
|
-
{/for}
|
|
2200
|
+
{#for deal of deals.ready() ? deals : [] by deal.id}
|
|
2201
|
+
<DealRow id={deal.id} name={deal.name} />
|
|
2202
|
+
{/for}
|
|
2128
2203
|
</div>
|
|
2129
2204
|
```
|
|
2130
2205
|
|
|
@@ -2145,7 +2220,8 @@ let selected = reactive.share('selectedDeal', () => reactive(''))
|
|
|
2145
2220
|
```abide src/pipeline/lib/DealDetail.abide
|
|
2146
2221
|
<script>
|
|
2147
2222
|
import { reactive } from '@abide/abide'
|
|
2148
|
-
|
|
2223
|
+
|
|
2224
|
+
import listDeals from '../listDeals.handler.ts'
|
|
2149
2225
|
|
|
2150
2226
|
const deals = reactive(() => listDeals())
|
|
2151
2227
|
const selected = reactive.share('selectedDeal', () => reactive(''))
|
|
@@ -2162,8 +2238,10 @@ const deal = reactive(() =>
|
|
|
2162
2238
|
|
|
2163
2239
|
```ts src/pipeline/lib/DealPicker.test.ts
|
|
2164
2240
|
import { expect, test } from 'bun:test'
|
|
2241
|
+
|
|
2165
2242
|
import { flushSync } from '@abide/abide'
|
|
2166
2243
|
import { mount } from '@abide/harness/abide'
|
|
2244
|
+
|
|
2167
2245
|
import DealPicker from './DealPicker.abide'
|
|
2168
2246
|
|
|
2169
2247
|
test('picking a deal in the list shows it in the detail panel', async () => {
|
|
@@ -2193,7 +2271,8 @@ Use the value `reactive.share` returns, and no other name. A value lives in the
|
|
|
2193
2271
|
```abide src/pipeline/lib/DealNote.abide
|
|
2194
2272
|
<script>
|
|
2195
2273
|
import { reactive, watch } from '@abide/abide'
|
|
2196
|
-
|
|
2274
|
+
|
|
2275
|
+
import saveNote from '../saveNote.handler.ts'
|
|
2197
2276
|
|
|
2198
2277
|
let note = reactive('Wants a quote by Friday.')
|
|
2199
2278
|
let saved = reactive('Wants a quote by Friday.')
|
|
@@ -2220,8 +2299,9 @@ watch(() => {
|
|
|
2220
2299
|
</ul>
|
|
2221
2300
|
```
|
|
2222
2301
|
|
|
2223
|
-
```ts src/pipeline/saveNote.ts
|
|
2302
|
+
```ts src/pipeline/saveNote.handler.ts
|
|
2224
2303
|
import { POST } from '@abide/abide/server'
|
|
2304
|
+
|
|
2225
2305
|
import { LOADS } from './lib/LOADS.server.ts'
|
|
2226
2306
|
import { NOTES } from './lib/NOTES.server.ts'
|
|
2227
2307
|
|
|
@@ -2237,7 +2317,9 @@ export default POST(
|
|
|
2237
2317
|
|
|
2238
2318
|
```ts src/pipeline/lib/DealNote.test.ts
|
|
2239
2319
|
import { expect, test } from 'bun:test'
|
|
2320
|
+
|
|
2240
2321
|
import { mount } from '@abide/harness/abide'
|
|
2322
|
+
|
|
2241
2323
|
import DealNote from './DealNote.abide'
|
|
2242
2324
|
import { LOADS } from './LOADS.server.ts'
|
|
2243
2325
|
import { NOTES } from './NOTES.server.ts'
|
|
@@ -2287,9 +2369,10 @@ Where a read subscribes depends on where it runs:
|
|
|
2287
2369
|
```abide src/pipeline/lib/PipelineView.abide
|
|
2288
2370
|
<script>
|
|
2289
2371
|
import { reactive } from '@abide/abide'
|
|
2372
|
+
|
|
2290
2373
|
import { preferenceStore } from './preferenceStore.ts'
|
|
2291
|
-
import StageTile from './StageTile.abide'
|
|
2292
2374
|
import { STAGES } from './STAGES.ts'
|
|
2375
|
+
import StageTile from './StageTile.abide'
|
|
2293
2376
|
|
|
2294
2377
|
const VIEWS = ['board', 'list']
|
|
2295
2378
|
|
|
@@ -2298,23 +2381,24 @@ let view = reactive('board', { store: preferenceStore('view') })
|
|
|
2298
2381
|
</script>
|
|
2299
2382
|
|
|
2300
2383
|
<div class="tabs">
|
|
2301
|
-
{#for one of VIEWS}
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
{/for}
|
|
2384
|
+
{#for one of VIEWS}
|
|
2385
|
+
<button aria-selected={view === one} onclick={() => (view = one)}>
|
|
2386
|
+
{one}</button>
|
|
2387
|
+
{/for}
|
|
2305
2388
|
</div>
|
|
2306
2389
|
|
|
2307
2390
|
<ul class={view === 'board' ? 'tiles' : 'fields'}>
|
|
2308
|
-
{#for stage of STAGES}
|
|
2309
|
-
|
|
2310
|
-
{/for}
|
|
2391
|
+
{#for stage of STAGES}
|
|
2392
|
+
<StageTile stage={stage} />
|
|
2393
|
+
{/for}
|
|
2311
2394
|
</ul>
|
|
2312
2395
|
```
|
|
2313
2396
|
|
|
2314
2397
|
```ts src/pipeline/lib/preferenceStore.ts
|
|
2315
2398
|
import type { Store } from '@abide/abide'
|
|
2316
|
-
|
|
2317
|
-
import
|
|
2399
|
+
|
|
2400
|
+
import getPreference from '../getPreference.handler.ts'
|
|
2401
|
+
import setPreference from '../setPreference.handler.ts'
|
|
2318
2402
|
|
|
2319
2403
|
/** Where a preference lives between visits: both halves, or neither. */
|
|
2320
2404
|
export function preferenceStore(name: string): Store<string> {
|
|
@@ -2330,8 +2414,9 @@ export function preferenceStore(name: string): Store<string> {
|
|
|
2330
2414
|
}
|
|
2331
2415
|
```
|
|
2332
2416
|
|
|
2333
|
-
```ts src/pipeline/getPreference.ts
|
|
2417
|
+
```ts src/pipeline/getPreference.handler.ts
|
|
2334
2418
|
import { GET } from '@abide/abide/server'
|
|
2419
|
+
|
|
2335
2420
|
import { PREFERENCES } from './lib/PREFERENCES.server.ts'
|
|
2336
2421
|
|
|
2337
2422
|
/** A saved preference, or `null` where none was saved. */
|
|
@@ -2345,8 +2430,9 @@ export default GET(
|
|
|
2345
2430
|
)
|
|
2346
2431
|
```
|
|
2347
2432
|
|
|
2348
|
-
```ts src/pipeline/setPreference.ts
|
|
2433
|
+
```ts src/pipeline/setPreference.handler.ts
|
|
2349
2434
|
import { POST } from '@abide/abide/server'
|
|
2435
|
+
|
|
2350
2436
|
import { PREFERENCES } from './lib/PREFERENCES.server.ts'
|
|
2351
2437
|
|
|
2352
2438
|
/** Saves a preference. */
|
|
@@ -2360,8 +2446,10 @@ export default POST(
|
|
|
2360
2446
|
|
|
2361
2447
|
```ts src/pipeline/lib/PipelineView.test.ts
|
|
2362
2448
|
import { expect, test } from 'bun:test'
|
|
2449
|
+
|
|
2363
2450
|
import { flushSync } from '@abide/abide'
|
|
2364
2451
|
import { mount } from '@abide/harness/abide'
|
|
2452
|
+
|
|
2365
2453
|
import PipelineView from './PipelineView.abide'
|
|
2366
2454
|
import { PREFERENCES } from './PREFERENCES.server.ts'
|
|
2367
2455
|
|
|
@@ -2454,6 +2542,7 @@ import LastActivity from './LastActivity.abide'
|
|
|
2454
2542
|
```abide src/activity/lib/LastActivity.abide
|
|
2455
2543
|
<script>
|
|
2456
2544
|
import { reactive } from '@abide/abide'
|
|
2545
|
+
|
|
2457
2546
|
// The same import reaches the same channel: no prop, no provider.
|
|
2458
2547
|
import { activity } from './activity.ts'
|
|
2459
2548
|
|
|
@@ -2471,8 +2560,10 @@ const last = reactive(() => activity.at(-1))
|
|
|
2471
2560
|
|
|
2472
2561
|
```ts src/activity/lib/QuickLog.test.ts
|
|
2473
2562
|
import { expect, test } from 'bun:test'
|
|
2563
|
+
|
|
2474
2564
|
import { flushSync } from '@abide/abide'
|
|
2475
2565
|
import { mount } from '@abide/harness/abide'
|
|
2566
|
+
|
|
2476
2567
|
import QuickLog from './QuickLog.abide'
|
|
2477
2568
|
|
|
2478
2569
|
test('a reader the logger never names follows what it logs', async () => {
|
|
@@ -2537,8 +2628,10 @@ export function logActivity(contact: string, kind: string): void {
|
|
|
2537
2628
|
|
|
2538
2629
|
```ts src/activity/lib/RecentActivity.test.ts
|
|
2539
2630
|
import { expect, test } from 'bun:test'
|
|
2631
|
+
|
|
2540
2632
|
import { flushSync } from '@abide/abide'
|
|
2541
2633
|
import { mount } from '@abide/harness/abide'
|
|
2634
|
+
|
|
2542
2635
|
import RecentActivity from './RecentActivity.abide'
|
|
2543
2636
|
|
|
2544
2637
|
test('logging past three adds one entry and drops the oldest', async () => {
|
|
@@ -2556,7 +2649,7 @@ test('logging past three adds one entry and drops the oldest', async () => {
|
|
|
2556
2649
|
```
|
|
2557
2650
|
|
|
2558
2651
|
The trim is one line in the patch that appends: `if (entries.length > 3)
|
|
2559
|
-
|
|
2652
|
+
entries.shift()`. `patch` hands you the array the channel holds and changes it in place, so the push and the trim are one write. abide puts no cap on the array. The condition is yours: a count here, an age or a byte total where that is what bounds it. Keep the push and the trim in one function every writer calls, and the cap belongs to the channel rather than to whichever caller remembered it.
|
|
2560
2653
|
|
|
2561
2654
|
A value keeps no past of its own. Where you want what came before, make the value the array and append where you write, as here. An undo stack is the same: an array the app pushes into.
|
|
2562
2655
|
|
|
@@ -2567,8 +2660,9 @@ A value keeps no past of its own. Where you want what came before, make the valu
|
|
|
2567
2660
|
```abide src/activity/lib/AccountFeed.abide
|
|
2568
2661
|
<script>
|
|
2569
2662
|
import { reactive } from '@abide/abide'
|
|
2570
|
-
|
|
2571
|
-
import
|
|
2663
|
+
|
|
2664
|
+
import feed from '../feed.handler.ts'
|
|
2665
|
+
import logToFeed from '../logToFeed.handler.ts'
|
|
2572
2666
|
|
|
2573
2667
|
const account = 'hopper-co'
|
|
2574
2668
|
const entries = reactive(() => feed({ account }))
|
|
@@ -2600,16 +2694,18 @@ export const feeds = reactive((_account: { account: string }): Entry[] => [], {
|
|
|
2600
2694
|
})
|
|
2601
2695
|
```
|
|
2602
2696
|
|
|
2603
|
-
```ts src/activity/feed.ts
|
|
2697
|
+
```ts src/activity/feed.handler.ts
|
|
2604
2698
|
import { GET } from '@abide/abide/server'
|
|
2699
|
+
|
|
2605
2700
|
import { feeds } from './lib/feeds.ts'
|
|
2606
2701
|
|
|
2607
2702
|
/** Answers at /activity/feed; every tab reading it follows it. */
|
|
2608
2703
|
export default GET(feeds, { description: "An account's activity feed." })
|
|
2609
2704
|
```
|
|
2610
2705
|
|
|
2611
|
-
```ts src/activity/logToFeed.ts
|
|
2706
|
+
```ts src/activity/logToFeed.handler.ts
|
|
2612
2707
|
import { POST } from '@abide/abide/server'
|
|
2708
|
+
|
|
2613
2709
|
import { feeds } from './lib/feeds.ts'
|
|
2614
2710
|
|
|
2615
2711
|
const KINDS = ['Call', 'Email', 'Meeting']
|
|
@@ -2633,6 +2729,7 @@ export default POST(
|
|
|
2633
2729
|
|
|
2634
2730
|
```ts src/activity/lib/AccountFeed.test.ts
|
|
2635
2731
|
import { expect, test } from 'bun:test'
|
|
2732
|
+
|
|
2636
2733
|
import { createApp } from '@abide/abide/server'
|
|
2637
2734
|
|
|
2638
2735
|
// Each request is a caller of its own, as each open tab is.
|
|
@@ -2674,7 +2771,8 @@ The feed lives in memory. A restart starts it empty, and each instance of the ap
|
|
|
2674
2771
|
```abide src/activity/lib/ActivityHistory.abide
|
|
2675
2772
|
<script>
|
|
2676
2773
|
import { reactive, refresh } from '@abide/abide'
|
|
2677
|
-
|
|
2774
|
+
|
|
2775
|
+
import olderActivity from '../olderActivity.handler.ts'
|
|
2678
2776
|
|
|
2679
2777
|
const entries = reactive(() => olderActivity())
|
|
2680
2778
|
</script>
|
|
@@ -2691,7 +2789,7 @@ const entries = reactive(() => olderActivity())
|
|
|
2691
2789
|
</div>
|
|
2692
2790
|
```
|
|
2693
2791
|
|
|
2694
|
-
```ts src/activity/olderActivity.ts
|
|
2792
|
+
```ts src/activity/olderActivity.handler.ts
|
|
2695
2793
|
import { GET } from '@abide/abide/server'
|
|
2696
2794
|
|
|
2697
2795
|
const OLDER = [
|
|
@@ -2715,7 +2813,8 @@ export default GET(
|
|
|
2715
2813
|
|
|
2716
2814
|
```ts src/activity/lib/ActivityHistory.test.ts
|
|
2717
2815
|
import { expect, test } from 'bun:test'
|
|
2718
|
-
|
|
2816
|
+
|
|
2817
|
+
import olderActivity from '../olderActivity.handler.ts'
|
|
2719
2818
|
|
|
2720
2819
|
test('the first entry is in hand while the rest are still read', async () => {
|
|
2721
2820
|
const entries = olderActivity()
|
|
@@ -2746,7 +2845,8 @@ A page rendered on the server is not held for an unfinished stream. What the str
|
|
|
2746
2845
|
```abide src/activity/lib/CsvExport.abide
|
|
2747
2846
|
<script>
|
|
2748
2847
|
import { reactive } from '@abide/abide'
|
|
2749
|
-
|
|
2848
|
+
|
|
2849
|
+
import exportCsv from '../exportCsv.handler.ts'
|
|
2750
2850
|
|
|
2751
2851
|
const csv = reactive(async () => (await exportCsv.raw()).text())
|
|
2752
2852
|
</script>
|
|
@@ -2759,7 +2859,7 @@ const csv = reactive(async () => (await exportCsv.raw()).text())
|
|
|
2759
2859
|
</div>
|
|
2760
2860
|
```
|
|
2761
2861
|
|
|
2762
|
-
```ts src/activity/exportCsv.ts
|
|
2862
|
+
```ts src/activity/exportCsv.handler.ts
|
|
2763
2863
|
import { bytes, GET } from '@abide/abide/server'
|
|
2764
2864
|
|
|
2765
2865
|
const ENCODER = new TextEncoder()
|
|
@@ -2787,6 +2887,7 @@ export default GET(() => bytes(csvOf()), {
|
|
|
2787
2887
|
|
|
2788
2888
|
```ts src/activity/lib/CsvExport.test.ts
|
|
2789
2889
|
import { expect, test } from 'bun:test'
|
|
2890
|
+
|
|
2790
2891
|
import { createApp } from '@abide/abide/server'
|
|
2791
2892
|
|
|
2792
2893
|
test('the export is a file, written a row at a time', async () => {
|
|
@@ -2819,7 +2920,8 @@ A stream whose first chunk is binary is sent as bytes without the helper. `bytes
|
|
|
2819
2920
|
```abide src/activity/lib/Wallboard.abide
|
|
2820
2921
|
<script>
|
|
2821
2922
|
import { reactive } from '@abide/abide'
|
|
2822
|
-
|
|
2923
|
+
|
|
2924
|
+
import ticker from '../ticker.handler.ts'
|
|
2823
2925
|
|
|
2824
2926
|
// A plain request, as a wallboard sends it: no `Accept` asks for events.
|
|
2825
2927
|
const answer = reactive(async () => {
|
|
@@ -2835,7 +2937,7 @@ const answer = reactive(async () => {
|
|
|
2835
2937
|
<pre class="rounded-md border border-rule bg-raised p-3">{answer.body}</pre>
|
|
2836
2938
|
```
|
|
2837
2939
|
|
|
2838
|
-
```ts src/activity/ticker.ts
|
|
2940
|
+
```ts src/activity/ticker.handler.ts
|
|
2839
2941
|
import { GET, sse } from '@abide/abide/server'
|
|
2840
2942
|
|
|
2841
2943
|
const MOVES = [
|
|
@@ -2852,6 +2954,7 @@ export default GET(() => sse(MOVES), {
|
|
|
2852
2954
|
|
|
2853
2955
|
```ts src/activity/lib/Wallboard.test.ts
|
|
2854
2956
|
import { expect, test } from 'bun:test'
|
|
2957
|
+
|
|
2855
2958
|
import { createApp } from '@abide/abide/server'
|
|
2856
2959
|
|
|
2857
2960
|
test('a caller asking for nothing in particular is sent events', async () => {
|
|
@@ -2912,10 +3015,11 @@ The CRM outgrows one page. This chapter adds a page per company, the chrome they
|
|
|
2912
3015
|
| File | Answers on |
|
|
2913
3016
|
| --- | --- |
|
|
2914
3017
|
| `src/companies/index.abide` | `/companies` |
|
|
2915
|
-
| `src/companies/[id].abide` | `/companies/hopper-and-co`, and every other id |
|
|
3018
|
+
| `src/companies/[id=SLUG].abide` | `/companies/hopper-and-co`, and every other id |
|
|
3019
|
+
| `src/companies/SLUG.param.ts` | no address: it says what a segment naming `SLUG` takes |
|
|
2916
3020
|
| `src/companies/layout.abide` | no address: it wraps every page at `/companies` and below |
|
|
2917
3021
|
| `src/companies/error.abide` | no address: it answers when a page below it fails |
|
|
2918
|
-
| `src/companies/getCompany.ts` | `GET /companies/getCompany` |
|
|
3022
|
+
| `src/companies/getCompany.handler.ts` | `GET /companies/getCompany` |
|
|
2919
3023
|
|
|
2920
3024
|
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
3025
|
|
|
@@ -2925,9 +3029,9 @@ An `**/index.abide` answers its directory's own address, and any other `**/<name
|
|
|
2925
3029
|
| `[[name]]` | one segment or none | text, absent when nothing matched |
|
|
2926
3030
|
| `[...name]` | every remaining segment | a list, empty when nothing matched |
|
|
2927
3031
|
|
|
2928
|
-
## `[id].abide` answers for every company
|
|
3032
|
+
## `[id=SLUG].abide` answers for every company
|
|
2929
3033
|
|
|
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.
|
|
3034
|
+
**`[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
3035
|
|
|
2932
3036
|
```abide src/companies/lib/CompanyPages.abide
|
|
2933
3037
|
<script>
|
|
@@ -2936,7 +3040,7 @@ import { reactive, url } from '@abide/abide'
|
|
|
2936
3040
|
const IDS = ['analytical-engines', 'hopper-and-co']
|
|
2937
3041
|
|
|
2938
3042
|
let open = reactive('hopper-and-co')
|
|
2939
|
-
const address = reactive(() => url('/companies/[id]', { id: open }))
|
|
3043
|
+
const address = reactive(() => url('/companies/[id=SLUG]', { id: open }))
|
|
2940
3044
|
</script>
|
|
2941
3045
|
|
|
2942
3046
|
<div class="tabs">
|
|
@@ -2950,10 +3054,11 @@ const address = reactive(() => url('/companies/[id]', { id: open }))
|
|
|
2950
3054
|
class="h-60 w-full rounded-md border border-rule"></iframe>
|
|
2951
3055
|
```
|
|
2952
3056
|
|
|
2953
|
-
```abide src/companies/[id].abide
|
|
3057
|
+
```abide src/companies/[id=SLUG].abide
|
|
2954
3058
|
<script>
|
|
2955
3059
|
import { reactive, route } from '@abide/abide'
|
|
2956
|
-
|
|
3060
|
+
|
|
3061
|
+
import getCompany from './getCompany.handler.ts'
|
|
2957
3062
|
import CompanyTabs from './lib/CompanyTabs.abide'
|
|
2958
3063
|
|
|
2959
3064
|
const company = reactive(() => getCompany({ id: route.params.id }))
|
|
@@ -2976,6 +3081,7 @@ const company = reactive(() => getCompany({ id: route.params.id }))
|
|
|
2976
3081
|
|
|
2977
3082
|
```ts src/companies/CompanyPage.test.ts
|
|
2978
3083
|
import { expect, test } from 'bun:test'
|
|
3084
|
+
|
|
2979
3085
|
import { createApp } from '@abide/abide/server'
|
|
2980
3086
|
|
|
2981
3087
|
test('one file answers for every company, reading its id', async () => {
|
|
@@ -2995,10 +3101,100 @@ test('one file answers for every company, reading its id', async () => {
|
|
|
2995
3101
|
|
|
2996
3102
|
| Name | What it does for the CRM |
|
|
2997
3103
|
| --- | --- |
|
|
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 |
|
|
3104
|
+
| `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
3105
|
| `Params` | what the segments matched; a query string is never in it |
|
|
3000
3106
|
| `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
3107
|
|
|
3108
|
+
## A param file says what an id looks like
|
|
3109
|
+
|
|
3110
|
+
**`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.
|
|
3111
|
+
|
|
3112
|
+
```abide src/companies/lib/PastedName.abide
|
|
3113
|
+
<script>
|
|
3114
|
+
import { reactive, url } from '@abide/abide'
|
|
3115
|
+
|
|
3116
|
+
const PASTED = ['hopper-and-co', 'Hopper & Co']
|
|
3117
|
+
|
|
3118
|
+
let open = reactive('Hopper & Co')
|
|
3119
|
+
const address = reactive(() => url('/companies/[id=SLUG]', { id: open }))
|
|
3120
|
+
</script>
|
|
3121
|
+
|
|
3122
|
+
<div class="tabs">
|
|
3123
|
+
{#for id of PASTED}
|
|
3124
|
+
<button aria-selected={open === id}
|
|
3125
|
+
onclick={() => (open = id)}>{id}</button>
|
|
3126
|
+
{/for}
|
|
3127
|
+
</div>
|
|
3128
|
+
<p class="status">{address}</p>
|
|
3129
|
+
<iframe src={address} title="An address the company page may not take"
|
|
3130
|
+
class="h-48 w-full rounded-md border border-rule"></iframe>
|
|
3131
|
+
```
|
|
3132
|
+
|
|
3133
|
+
```ts src/companies/SLUG.param.ts
|
|
3134
|
+
// A company's id: lowercase words and digits, joined by hyphens.
|
|
3135
|
+
export const SLUG = /[a-z0-9-]+/
|
|
3136
|
+
```
|
|
3137
|
+
|
|
3138
|
+
```abide src/companies/[id=SLUG].abide
|
|
3139
|
+
<script>
|
|
3140
|
+
import { reactive, route } from '@abide/abide'
|
|
3141
|
+
|
|
3142
|
+
import getCompany from './getCompany.handler.ts'
|
|
3143
|
+
import CompanyTabs from './lib/CompanyTabs.abide'
|
|
3144
|
+
|
|
3145
|
+
const company = reactive(() => getCompany({ id: route.params.id }))
|
|
3146
|
+
</script>
|
|
3147
|
+
|
|
3148
|
+
<head><title>{company.name} — CRM</title></head>
|
|
3149
|
+
|
|
3150
|
+
<h2 class="text-heading font-semibold"
|
|
3151
|
+
style:view-transition-name="company-{route.params.id}">{company.name}</h2>
|
|
3152
|
+
<ul class="fields">
|
|
3153
|
+
<li><span>contact</span><strong>{company.contact}</strong></li>
|
|
3154
|
+
<li><span>pipeline</span><strong>{company.pipeline}</strong></li>
|
|
3155
|
+
</ul>
|
|
3156
|
+
<CompanyTabs />
|
|
3157
|
+
|
|
3158
|
+
<style>
|
|
3159
|
+
::view-transition-group(*) { animation-duration: 0.5s; }
|
|
3160
|
+
</style>
|
|
3161
|
+
```
|
|
3162
|
+
|
|
3163
|
+
```ts src/companies/Matcher.test.ts
|
|
3164
|
+
import { expect, test } from 'bun:test'
|
|
3165
|
+
|
|
3166
|
+
import { createApp } from '@abide/abide/server'
|
|
3167
|
+
|
|
3168
|
+
import { COMPANIES } from './lib/COMPANIES.server.ts'
|
|
3169
|
+
|
|
3170
|
+
test('the company page takes every id the CRM has, and no name', async () => {
|
|
3171
|
+
await using app = await createApp()
|
|
3172
|
+
for (const { id } of COMPANIES) {
|
|
3173
|
+
const answer = await app.fetch(
|
|
3174
|
+
new Request(`http://crm.invalid/companies/${id}`),
|
|
3175
|
+
)
|
|
3176
|
+
expect(answer.status).toBe(200)
|
|
3177
|
+
await answer.body?.cancel()
|
|
3178
|
+
}
|
|
3179
|
+
const pasted = await app.fetch(
|
|
3180
|
+
new Request('http://crm.invalid/companies/Hopper%20%26%20Co'),
|
|
3181
|
+
)
|
|
3182
|
+
expect(pasted.status).toBe(404)
|
|
3183
|
+
})
|
|
3184
|
+
```
|
|
3185
|
+
|
|
3186
|
+
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`.
|
|
3187
|
+
|
|
3188
|
+
| Named | Matches |
|
|
3189
|
+
| --- | --- |
|
|
3190
|
+
| `[name=NAME]` | one segment the matcher takes |
|
|
3191
|
+
| `[[name=NAME]]` | one segment the matcher takes, or none |
|
|
3192
|
+
| `[...name=NAME]` | every remaining segment, each one the matcher takes |
|
|
3193
|
+
|
|
3194
|
+
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.
|
|
3195
|
+
|
|
3196
|
+
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`.
|
|
3197
|
+
|
|
3002
3198
|
## A layout holds what the companies pages share
|
|
3003
3199
|
|
|
3004
3200
|
**`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.
|
|
@@ -3015,6 +3211,7 @@ import { url } from '@abide/abide'
|
|
|
3015
3211
|
```abide src/companies/layout.abide
|
|
3016
3212
|
<script server>
|
|
3017
3213
|
import { onError } from '@abide/abide/server'
|
|
3214
|
+
|
|
3018
3215
|
import { tracker } from './lib/tracker.server.ts'
|
|
3019
3216
|
|
|
3020
3217
|
onError((error) => tracker.capture(error))
|
|
@@ -3033,11 +3230,12 @@ let note = reactive('')
|
|
|
3033
3230
|
<input bind:value={note} placeholder="Note for the call">
|
|
3034
3231
|
</header>
|
|
3035
3232
|
|
|
3036
|
-
<slot/>
|
|
3233
|
+
<slot />
|
|
3037
3234
|
```
|
|
3038
3235
|
|
|
3039
3236
|
```ts src/companies/Layout.test.ts
|
|
3040
3237
|
import { expect, test } from 'bun:test'
|
|
3238
|
+
|
|
3041
3239
|
import { createApp } from '@abide/abide/server'
|
|
3042
3240
|
|
|
3043
3241
|
test('the list and a company render inside one layout', async () => {
|
|
@@ -3057,7 +3255,7 @@ A package can ship pages too. `import '…' with { type: 'routes' }` in a file o
|
|
|
3057
3255
|
|
|
3058
3256
|
## A link names a route, and `url` fills it in
|
|
3059
3257
|
|
|
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' })`.
|
|
3258
|
+
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
3259
|
|
|
3062
3260
|
| Name | What it does for the CRM |
|
|
3063
3261
|
| --- | --- |
|
|
@@ -3106,6 +3304,7 @@ let draft = reactive('')
|
|
|
3106
3304
|
|
|
3107
3305
|
```ts src/companies/lib/CompanyTabs.test.ts
|
|
3108
3306
|
import { expect, test } from 'bun:test'
|
|
3307
|
+
|
|
3109
3308
|
import { createApp } from '@abide/abide/server'
|
|
3110
3309
|
|
|
3111
3310
|
test('the open tab is read from the address', async () => {
|
|
@@ -3137,7 +3336,8 @@ import { url } from '@abide/abide'
|
|
|
3137
3336
|
```abide src/companies/index.abide
|
|
3138
3337
|
<script>
|
|
3139
3338
|
import { reactive, url } from '@abide/abide'
|
|
3140
|
-
|
|
3339
|
+
|
|
3340
|
+
import listCompanies from './listCompanies.handler.ts'
|
|
3141
3341
|
|
|
3142
3342
|
const companies = reactive(() => listCompanies())
|
|
3143
3343
|
</script>
|
|
@@ -3145,7 +3345,7 @@ const companies = reactive(() => listCompanies())
|
|
|
3145
3345
|
<ul class="fields">
|
|
3146
3346
|
{#for company of companies by company.id}
|
|
3147
3347
|
<li><span>{company.city}</span>
|
|
3148
|
-
<a href={url('/companies/[id]', { id: company.id })}
|
|
3348
|
+
<a href={url('/companies/[id=SLUG]', { id: company.id })}
|
|
3149
3349
|
style:view-transition-name="company-{company.id}"
|
|
3150
3350
|
>{company.name}</a></li>
|
|
3151
3351
|
{/for}
|
|
@@ -3156,10 +3356,11 @@ const companies = reactive(() => listCompanies())
|
|
|
3156
3356
|
</style>
|
|
3157
3357
|
```
|
|
3158
3358
|
|
|
3159
|
-
```abide src/companies/[id].abide
|
|
3359
|
+
```abide src/companies/[id=SLUG].abide
|
|
3160
3360
|
<script>
|
|
3161
3361
|
import { reactive, route } from '@abide/abide'
|
|
3162
|
-
|
|
3362
|
+
|
|
3363
|
+
import getCompany from './getCompany.handler.ts'
|
|
3163
3364
|
import CompanyTabs from './lib/CompanyTabs.abide'
|
|
3164
3365
|
|
|
3165
3366
|
const company = reactive(() => getCompany({ id: route.params.id }))
|
|
@@ -3182,6 +3383,7 @@ const company = reactive(() => getCompany({ id: route.params.id }))
|
|
|
3182
3383
|
|
|
3183
3384
|
```ts src/companies/ViewTransition.test.ts
|
|
3184
3385
|
import { expect, test } from 'bun:test'
|
|
3386
|
+
|
|
3185
3387
|
import { createApp } from '@abide/abide/server'
|
|
3186
3388
|
|
|
3187
3389
|
test("the list row and the company's heading share one name", async () => {
|
|
@@ -3209,7 +3411,7 @@ const PAGES = [
|
|
|
3209
3411
|
{ label: 'All companies', address: url('/companies') },
|
|
3210
3412
|
{
|
|
3211
3413
|
label: 'Hopper & Co',
|
|
3212
|
-
address: url('/companies/[id]', { id: 'hopper-and-co' }),
|
|
3414
|
+
address: url('/companies/[id=SLUG]', { id: 'hopper-and-co' }),
|
|
3213
3415
|
},
|
|
3214
3416
|
]
|
|
3215
3417
|
|
|
@@ -3254,6 +3456,7 @@ function followTitle(element: Element) {
|
|
|
3254
3456
|
```abide src/companies/layout.abide
|
|
3255
3457
|
<script server>
|
|
3256
3458
|
import { onError } from '@abide/abide/server'
|
|
3459
|
+
|
|
3257
3460
|
import { tracker } from './lib/tracker.server.ts'
|
|
3258
3461
|
|
|
3259
3462
|
onError((error) => tracker.capture(error))
|
|
@@ -3272,13 +3475,14 @@ let note = reactive('')
|
|
|
3272
3475
|
<input bind:value={note} placeholder="Note for the call">
|
|
3273
3476
|
</header>
|
|
3274
3477
|
|
|
3275
|
-
<slot/>
|
|
3478
|
+
<slot />
|
|
3276
3479
|
```
|
|
3277
3480
|
|
|
3278
|
-
```abide src/companies/[id].abide
|
|
3481
|
+
```abide src/companies/[id=SLUG].abide
|
|
3279
3482
|
<script>
|
|
3280
3483
|
import { reactive, route } from '@abide/abide'
|
|
3281
|
-
|
|
3484
|
+
|
|
3485
|
+
import getCompany from './getCompany.handler.ts'
|
|
3282
3486
|
import CompanyTabs from './lib/CompanyTabs.abide'
|
|
3283
3487
|
|
|
3284
3488
|
const company = reactive(() => getCompany({ id: route.params.id }))
|
|
@@ -3301,6 +3505,7 @@ const company = reactive(() => getCompany({ id: route.params.id }))
|
|
|
3301
3505
|
|
|
3302
3506
|
```ts src/companies/Head.test.ts
|
|
3303
3507
|
import { expect, test } from 'bun:test'
|
|
3508
|
+
|
|
3304
3509
|
import { createApp } from '@abide/abide/server'
|
|
3305
3510
|
|
|
3306
3511
|
const titlesOf = (html: string) => html.match(/<title>[^<]*<\/title>/g)
|
|
@@ -3329,7 +3534,7 @@ A `<head>` sits at the top level of a file, never inside a block. A `<title>`, a
|
|
|
3329
3534
|
<script>
|
|
3330
3535
|
import { reactive, url } from '@abide/abide'
|
|
3331
3536
|
|
|
3332
|
-
const PAGE = url('/companies/[id]', { id: 'hopper-and-co' })
|
|
3537
|
+
const PAGE = url('/companies/[id=SLUG]', { id: 'hopper-and-co' })
|
|
3333
3538
|
|
|
3334
3539
|
let loads = reactive(1)
|
|
3335
3540
|
</script>
|
|
@@ -3341,10 +3546,11 @@ let loads = reactive(1)
|
|
|
3341
3546
|
class="h-60 w-full rounded-md border border-rule"></iframe>
|
|
3342
3547
|
```
|
|
3343
3548
|
|
|
3344
|
-
```abide src/companies/[id].abide
|
|
3549
|
+
```abide src/companies/[id=SLUG].abide
|
|
3345
3550
|
<script>
|
|
3346
3551
|
import { reactive, route } from '@abide/abide'
|
|
3347
|
-
|
|
3552
|
+
|
|
3553
|
+
import getCompany from './getCompany.handler.ts'
|
|
3348
3554
|
import CompanyTabs from './lib/CompanyTabs.abide'
|
|
3349
3555
|
|
|
3350
3556
|
const company = reactive(() => getCompany({ id: route.params.id }))
|
|
@@ -3365,8 +3571,9 @@ const company = reactive(() => getCompany({ id: route.params.id }))
|
|
|
3365
3571
|
</style>
|
|
3366
3572
|
```
|
|
3367
3573
|
|
|
3368
|
-
```ts src/companies/getCompany.ts
|
|
3574
|
+
```ts src/companies/getCompany.handler.ts
|
|
3369
3575
|
import { GET, notFound } from '@abide/abide/server'
|
|
3576
|
+
|
|
3370
3577
|
import { COMPANIES } from './lib/COMPANIES.server.ts'
|
|
3371
3578
|
|
|
3372
3579
|
/** Answers at /companies/getCompany; a company's page reads it. */
|
|
@@ -3385,6 +3592,7 @@ export default GET(
|
|
|
3385
3592
|
|
|
3386
3593
|
```ts src/companies/Streaming.test.ts
|
|
3387
3594
|
import { expect, test } from 'bun:test'
|
|
3595
|
+
|
|
3388
3596
|
import { createApp } from '@abide/abide/server'
|
|
3389
3597
|
|
|
3390
3598
|
test('the page goes out without the company, and asks for it', async () => {
|
|
@@ -3419,7 +3627,7 @@ Nothing holds the document for a value. The page gets one turn to start its load
|
|
|
3419
3627
|
import { url } from '@abide/abide'
|
|
3420
3628
|
</script>
|
|
3421
3629
|
|
|
3422
|
-
<iframe src={url('/companies/[id]', { id: 'initech' })}
|
|
3630
|
+
<iframe src={url('/companies/[id=SLUG]', { id: 'initech' })}
|
|
3423
3631
|
title="A company that is not there"
|
|
3424
3632
|
class="h-48 w-full rounded-md border border-rule"></iframe>
|
|
3425
3633
|
```
|
|
@@ -3440,8 +3648,9 @@ const { failure } = props<{ failure: Failed<string, unknown> }>()
|
|
|
3440
3648
|
{/if}
|
|
3441
3649
|
```
|
|
3442
3650
|
|
|
3443
|
-
```ts src/companies/getCompany.ts
|
|
3651
|
+
```ts src/companies/getCompany.handler.ts
|
|
3444
3652
|
import { GET, notFound } from '@abide/abide/server'
|
|
3653
|
+
|
|
3445
3654
|
import { COMPANIES } from './lib/COMPANIES.server.ts'
|
|
3446
3655
|
|
|
3447
3656
|
/** Answers at /companies/getCompany; a company's page reads it. */
|
|
@@ -3460,6 +3669,7 @@ export default GET(
|
|
|
3460
3669
|
|
|
3461
3670
|
```ts src/companies/ErrorPage.test.ts
|
|
3462
3671
|
import { expect, test } from 'bun:test'
|
|
3672
|
+
|
|
3463
3673
|
import { createApp } from '@abide/abide/server'
|
|
3464
3674
|
|
|
3465
3675
|
test('a missing company is a 404, painted by error.abide', async () => {
|
|
@@ -3483,8 +3693,9 @@ The refusal has to come in the page's first turn to set the status. That is why
|
|
|
3483
3693
|
```abide src/companies/lib/ExportButton.abide
|
|
3484
3694
|
<script>
|
|
3485
3695
|
import { isFailed, reactive, refresh } from '@abide/abide'
|
|
3486
|
-
|
|
3487
|
-
import
|
|
3696
|
+
|
|
3697
|
+
import exportCompanies from '../exportCompanies.handler.ts'
|
|
3698
|
+
import reportedErrors from '../reportedErrors.handler.ts'
|
|
3488
3699
|
|
|
3489
3700
|
let told = reactive('—')
|
|
3490
3701
|
const reported = reactive(() => reportedErrors())
|
|
@@ -3509,7 +3720,7 @@ async function runExport() {
|
|
|
3509
3720
|
</ul>
|
|
3510
3721
|
```
|
|
3511
3722
|
|
|
3512
|
-
```ts src/companies/exportCompanies.ts
|
|
3723
|
+
```ts src/companies/exportCompanies.handler.ts
|
|
3513
3724
|
import { POST } from '@abide/abide/server'
|
|
3514
3725
|
|
|
3515
3726
|
/** Answers at /companies/exportCompanies; the export button calls it. */
|
|
@@ -3525,6 +3736,7 @@ export default POST(
|
|
|
3525
3736
|
```abide src/companies/layout.abide
|
|
3526
3737
|
<script server>
|
|
3527
3738
|
import { onError } from '@abide/abide/server'
|
|
3739
|
+
|
|
3528
3740
|
import { tracker } from './lib/tracker.server.ts'
|
|
3529
3741
|
|
|
3530
3742
|
onError((error) => tracker.capture(error))
|
|
@@ -3543,11 +3755,12 @@ let note = reactive('')
|
|
|
3543
3755
|
<input bind:value={note} placeholder="Note for the call">
|
|
3544
3756
|
</header>
|
|
3545
3757
|
|
|
3546
|
-
<slot/>
|
|
3758
|
+
<slot />
|
|
3547
3759
|
```
|
|
3548
3760
|
|
|
3549
3761
|
```ts src/companies/lib/ExportButton.test.ts
|
|
3550
3762
|
import { expect, test } from 'bun:test'
|
|
3763
|
+
|
|
3551
3764
|
import { createApp } from '@abide/abide/server'
|
|
3552
3765
|
|
|
3553
3766
|
test('the caller gets a bare 500, and onError gets the reason', async () => {
|
|
@@ -3576,7 +3789,7 @@ test('the caller gets a bare 500, and onError gets the reason', async () => {
|
|
|
3576
3789
|
|
|
3577
3790
|
```abide src/companies/lib/SheetPreview.abide
|
|
3578
3791
|
<script>
|
|
3579
|
-
import previewCompany from '../previewCompany.ts'
|
|
3792
|
+
import previewCompany from '../previewCompany.handler.ts'
|
|
3580
3793
|
</script>
|
|
3581
3794
|
|
|
3582
3795
|
<iframe src={previewCompany.url({ id: 'hopper-and-co' })}
|
|
@@ -3584,10 +3797,11 @@ import previewCompany from '../previewCompany.ts'
|
|
|
3584
3797
|
class="h-48 w-full rounded-md border border-rule"></iframe>
|
|
3585
3798
|
```
|
|
3586
3799
|
|
|
3587
|
-
```ts src/companies/previewCompany.ts
|
|
3800
|
+
```ts src/companies/previewCompany.handler.ts
|
|
3588
3801
|
import { GET, notFound, page, render } from '@abide/abide/server'
|
|
3589
|
-
|
|
3802
|
+
|
|
3590
3803
|
import { COMPANIES } from './lib/COMPANIES.server.ts'
|
|
3804
|
+
import CompanySheet from './lib/CompanySheet.abide'
|
|
3591
3805
|
|
|
3592
3806
|
/** Answers at /companies/previewCompany: an account sheet, as its own page. */
|
|
3593
3807
|
export default GET(
|
|
@@ -3610,19 +3824,20 @@ const { company } = props<{
|
|
|
3610
3824
|
</script>
|
|
3611
3825
|
|
|
3612
3826
|
<html lang="en">
|
|
3613
|
-
<body style="margin: 0; padding: 1.25rem; background: #fff; color: #1d2a28;
|
|
3827
|
+
<body style="margin: 0; padding: 1.25rem; background: #fff; color: #1d2a28;
|
|
3614
3828
|
font: 14px/1.5 system-ui, sans-serif">
|
|
3615
|
-
<h1 style="margin: 0 0 0.5rem; font-size: 1.25rem">{company.name}</h1>
|
|
3829
|
+
<h1 style="margin: 0 0 0.5rem; font-size: 1.25rem">{company.name}</h1>
|
|
3616
3830
|
<table>
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
</table>
|
|
3620
|
-
</body>
|
|
3831
|
+
<tr><th align="left">Contact</th><td>{company.contact}</td></tr>
|
|
3832
|
+
<tr><th align="left">Pipeline</th><td>{company.pipeline}</td></tr>
|
|
3833
|
+
</table>
|
|
3834
|
+
</body>
|
|
3621
3835
|
</html>
|
|
3622
3836
|
```
|
|
3623
3837
|
|
|
3624
3838
|
```ts src/companies/lib/SheetPreview.test.ts
|
|
3625
3839
|
import { expect, test } from 'bun:test'
|
|
3840
|
+
|
|
3626
3841
|
import { createApp } from '@abide/abide/server'
|
|
3627
3842
|
|
|
3628
3843
|
test('the sheet is a document of its own', async () => {
|
|
@@ -3677,8 +3892,9 @@ Everything in this chapter is in `src/account` of the chapter app, beside `src/c
|
|
|
3677
3892
|
```abide src/account/lib/SignIn.abide
|
|
3678
3893
|
<script>
|
|
3679
3894
|
import { principal, reactive } from '@abide/abide'
|
|
3680
|
-
|
|
3681
|
-
import
|
|
3895
|
+
|
|
3896
|
+
import signIn from '../signIn.handler.ts'
|
|
3897
|
+
import signOut from '../signOut.handler.ts'
|
|
3682
3898
|
|
|
3683
3899
|
let email = reactive('maya@crm.example')
|
|
3684
3900
|
</script>
|
|
@@ -3694,9 +3910,10 @@ let email = reactive('maya@crm.example')
|
|
|
3694
3910
|
</div>
|
|
3695
3911
|
```
|
|
3696
3912
|
|
|
3697
|
-
```ts src/account/signIn.ts
|
|
3913
|
+
```ts src/account/signIn.handler.ts
|
|
3698
3914
|
import { principal, refuse } from '@abide/abide'
|
|
3699
3915
|
import { onPrincipal, POST } from '@abide/abide/server'
|
|
3916
|
+
|
|
3700
3917
|
import { TEAM } from './lib/TEAM.server.ts'
|
|
3701
3918
|
|
|
3702
3919
|
/** Seals the member's id into the cookie, and nothing else. */
|
|
@@ -3717,7 +3934,7 @@ onPrincipal((claims) => {
|
|
|
3717
3934
|
})
|
|
3718
3935
|
```
|
|
3719
3936
|
|
|
3720
|
-
```ts src/account/signOut.ts
|
|
3937
|
+
```ts src/account/signOut.handler.ts
|
|
3721
3938
|
import { principal } from '@abide/abide'
|
|
3722
3939
|
import { POST } from '@abide/abide/server'
|
|
3723
3940
|
|
|
@@ -3726,6 +3943,7 @@ export default POST(() => principal.clear(), { description: 'Signs out.' })
|
|
|
3726
3943
|
|
|
3727
3944
|
```ts src/account/lib/SignIn.test.ts
|
|
3728
3945
|
import { expect, test } from 'bun:test'
|
|
3946
|
+
|
|
3729
3947
|
import { principal } from '@abide/abide'
|
|
3730
3948
|
import { createApp } from '@abide/abide/server'
|
|
3731
3949
|
|
|
@@ -3765,7 +3983,7 @@ test('a request carrying the cookie is signed in', async () => {
|
|
|
3765
3983
|
})
|
|
3766
3984
|
```
|
|
3767
3985
|
|
|
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.
|
|
3986
|
+
`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
3987
|
|
|
3770
3988
|
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
3989
|
|
|
@@ -3788,6 +4006,7 @@ Each is a reactive value. A template reading one follows it, and on the server e
|
|
|
3788
4006
|
```abide src/account/lib/Badge.abide
|
|
3789
4007
|
<script>
|
|
3790
4008
|
import { principal } from '@abide/abide'
|
|
4009
|
+
|
|
3791
4010
|
import SignInAs from './SignInAs.abide'
|
|
3792
4011
|
</script>
|
|
3793
4012
|
|
|
@@ -3801,9 +4020,10 @@ import SignInAs from './SignInAs.abide'
|
|
|
3801
4020
|
</ul>
|
|
3802
4021
|
```
|
|
3803
4022
|
|
|
3804
|
-
```ts src/account/signIn.ts
|
|
4023
|
+
```ts src/account/signIn.handler.ts
|
|
3805
4024
|
import { principal, refuse } from '@abide/abide'
|
|
3806
4025
|
import { onPrincipal, POST } from '@abide/abide/server'
|
|
4026
|
+
|
|
3807
4027
|
import { TEAM } from './lib/TEAM.server.ts'
|
|
3808
4028
|
|
|
3809
4029
|
/** Seals the member's id into the cookie, and nothing else. */
|
|
@@ -3826,6 +4046,7 @@ onPrincipal((claims) => {
|
|
|
3826
4046
|
|
|
3827
4047
|
```ts src/account/lib/Badge.test.ts
|
|
3828
4048
|
import { expect, test } from 'bun:test'
|
|
4049
|
+
|
|
3829
4050
|
import { principal } from '@abide/abide'
|
|
3830
4051
|
import { createApp } from '@abide/abide/server'
|
|
3831
4052
|
|
|
@@ -3861,10 +4082,10 @@ const density = reactive(() => cookies().get('density') ?? 'comfortable')
|
|
|
3861
4082
|
</script>
|
|
3862
4083
|
|
|
3863
4084
|
<div class="tabs">
|
|
3864
|
-
{#for one of DENSITIES}
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
{/for}
|
|
4085
|
+
{#for one of DENSITIES}
|
|
4086
|
+
<button aria-selected={density === one}
|
|
4087
|
+
onclick={() => cookies().set('density', one)}>{one}</button>
|
|
4088
|
+
{/for}
|
|
3868
4089
|
</div>
|
|
3869
4090
|
|
|
3870
4091
|
<ul class={density === 'compact' ? 'fields gap-0' : 'fields gap-flow'}>
|
|
@@ -3875,6 +4096,7 @@ const density = reactive(() => cookies().get('density') ?? 'comfortable')
|
|
|
3875
4096
|
|
|
3876
4097
|
```ts src/account/lib/Density.test.ts
|
|
3877
4098
|
import { expect, test } from 'bun:test'
|
|
4099
|
+
|
|
3878
4100
|
import { createApp } from '@abide/abide/server'
|
|
3879
4101
|
|
|
3880
4102
|
test('the server paints the density the cookie holds', async () => {
|
|
@@ -3898,7 +4120,8 @@ test('the server paints the density the cookie holds', async () => {
|
|
|
3898
4120
|
```abide src/account/lib/ContactCard.abide
|
|
3899
4121
|
<script>
|
|
3900
4122
|
import { principal, reactive, watch } from '@abide/abide'
|
|
3901
|
-
|
|
4123
|
+
|
|
4124
|
+
import getContact from '../getContact.handler.ts'
|
|
3902
4125
|
import SignInAs from './SignInAs.abide'
|
|
3903
4126
|
|
|
3904
4127
|
const contact = reactive(() => getContact())
|
|
@@ -3919,7 +4142,7 @@ const dealValue = () => contact.dealValue ?? 'admins only'
|
|
|
3919
4142
|
</ul>
|
|
3920
4143
|
```
|
|
3921
4144
|
|
|
3922
|
-
```ts src/account/getContact.ts
|
|
4145
|
+
```ts src/account/getContact.handler.ts
|
|
3923
4146
|
import { principal } from '@abide/abide'
|
|
3924
4147
|
import { GET } from '@abide/abide/server'
|
|
3925
4148
|
|
|
@@ -3944,8 +4167,10 @@ export default GET(
|
|
|
3944
4167
|
|
|
3945
4168
|
```ts src/account/lib/ContactCard.test.ts
|
|
3946
4169
|
import { expect, test } from 'bun:test'
|
|
4170
|
+
|
|
3947
4171
|
import { createApp } from '@abide/abide/server'
|
|
3948
|
-
|
|
4172
|
+
|
|
4173
|
+
import getContact from '../getContact.handler.ts'
|
|
3949
4174
|
|
|
3950
4175
|
test('an admin reads the deal value', async () => {
|
|
3951
4176
|
await using app = await createApp()
|
|
@@ -3976,7 +4201,8 @@ An answer read under one member is not read again on its own when another signs
|
|
|
3976
4201
|
```abide src/account/lib/ExportContacts.abide
|
|
3977
4202
|
<script>
|
|
3978
4203
|
import { reactive } from '@abide/abide'
|
|
3979
|
-
|
|
4204
|
+
|
|
4205
|
+
import exportContacts from '../exportContacts.handler.ts'
|
|
3980
4206
|
import SignInAs from './SignInAs.abide'
|
|
3981
4207
|
|
|
3982
4208
|
let status = reactive('')
|
|
@@ -4000,9 +4226,10 @@ async function exportAll() {
|
|
|
4000
4226
|
</div>
|
|
4001
4227
|
```
|
|
4002
4228
|
|
|
4003
|
-
```ts src/account/exportContacts.ts
|
|
4229
|
+
```ts src/account/exportContacts.handler.ts
|
|
4004
4230
|
import { principal, refuse } from '@abide/abide'
|
|
4005
4231
|
import { POST } from '@abide/abide/server'
|
|
4232
|
+
|
|
4006
4233
|
import { CONTACTS } from '../contacts/lib/CONTACTS.server.ts'
|
|
4007
4234
|
|
|
4008
4235
|
export default POST(async () => CONTACTS, {
|
|
@@ -4020,8 +4247,10 @@ export default POST(async () => CONTACTS, {
|
|
|
4020
4247
|
|
|
4021
4248
|
```ts src/account/lib/ExportContacts.test.ts
|
|
4022
4249
|
import { expect, test } from 'bun:test'
|
|
4250
|
+
|
|
4023
4251
|
import { createApp } from '@abide/abide/server'
|
|
4024
|
-
|
|
4252
|
+
|
|
4253
|
+
import exportContacts from '../exportContacts.handler.ts'
|
|
4025
4254
|
|
|
4026
4255
|
test('an admin exports every contact', async () => {
|
|
4027
4256
|
await using app = await createApp()
|
|
@@ -4079,7 +4308,8 @@ Filed in the root `src/layout.abide`, a rung runs before routing, over every req
|
|
|
4079
4308
|
```abide src/account/lib/PartnerStatement.abide
|
|
4080
4309
|
<script server>
|
|
4081
4310
|
import { GET } from '@abide/abide/server'
|
|
4082
|
-
|
|
4311
|
+
|
|
4312
|
+
import getStatement from '../getStatement.handler.ts'
|
|
4083
4313
|
|
|
4084
4314
|
// Another site's browser, asking for the statement under its own origin.
|
|
4085
4315
|
const askAs = GET(
|
|
@@ -4114,7 +4344,7 @@ const allowed = reactive(() => askAs({ origin }))
|
|
|
4114
4344
|
</ul>
|
|
4115
4345
|
```
|
|
4116
4346
|
|
|
4117
|
-
```ts src/account/getStatement.ts
|
|
4347
|
+
```ts src/account/getStatement.handler.ts
|
|
4118
4348
|
import { GET } from '@abide/abide/server'
|
|
4119
4349
|
|
|
4120
4350
|
/** Read from the bookkeeping partner's own pages, and from no one else's. */
|
|
@@ -4126,6 +4356,7 @@ export default GET(() => ({ account: 'A-1042', balance: '$2,140.00' }), {
|
|
|
4126
4356
|
|
|
4127
4357
|
```ts src/account/lib/PartnerStatement.test.ts
|
|
4128
4358
|
import { expect, test } from 'bun:test'
|
|
4359
|
+
|
|
4129
4360
|
import { createApp } from '@abide/abide/server'
|
|
4130
4361
|
|
|
4131
4362
|
function askAs(origin: string): Request {
|
|
@@ -4162,7 +4393,8 @@ abide answers the `OPTIONS` preflight a cross-origin `POST` sends, outside every
|
|
|
4162
4393
|
```abide src/account/lib/SalesReport.abide
|
|
4163
4394
|
<script>
|
|
4164
4395
|
import { reactive } from '@abide/abide'
|
|
4165
|
-
|
|
4396
|
+
|
|
4397
|
+
import salesReport from '../salesReport.handler.ts'
|
|
4166
4398
|
|
|
4167
4399
|
const REGIONS = ['north', 'south']
|
|
4168
4400
|
|
|
@@ -4177,10 +4409,12 @@ function refusal() {
|
|
|
4177
4409
|
</script>
|
|
4178
4410
|
|
|
4179
4411
|
<div class="tabs">
|
|
4180
|
-
{#for one of REGIONS}
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4412
|
+
{#for one of REGIONS}
|
|
4413
|
+
<button aria-selected={region === one}
|
|
4414
|
+
onclick={() => {
|
|
4415
|
+
region = one
|
|
4416
|
+
}}>{one}</button>
|
|
4417
|
+
{/for}
|
|
4184
4418
|
</div>
|
|
4185
4419
|
|
|
4186
4420
|
<p class="status">{refusal()}</p>
|
|
@@ -4191,7 +4425,7 @@ function refusal() {
|
|
|
4191
4425
|
</ul>
|
|
4192
4426
|
```
|
|
4193
4427
|
|
|
4194
|
-
```ts src/account/salesReport.ts
|
|
4428
|
+
```ts src/account/salesReport.handler.ts
|
|
4195
4429
|
import { GET } from '@abide/abide/server'
|
|
4196
4430
|
|
|
4197
4431
|
const BY_REGION: Record<string, { week: string; total: string }[]> = {
|
|
@@ -4218,7 +4452,8 @@ export default GET(
|
|
|
4218
4452
|
|
|
4219
4453
|
```ts src/account/lib/SalesReport.test.ts
|
|
4220
4454
|
import { expect, test } from 'bun:test'
|
|
4221
|
-
|
|
4455
|
+
|
|
4456
|
+
import salesReport from '../salesReport.handler.ts'
|
|
4222
4457
|
|
|
4223
4458
|
test('a report slower than the timeout runs, a row at a time', async () => {
|
|
4224
4459
|
const rows = []
|
|
@@ -4261,7 +4496,8 @@ None of these is a rate limit. A caller sending many small, fast requests passes
|
|
|
4261
4496
|
```abide src/account/lib/SyncContact.abide
|
|
4262
4497
|
<script>
|
|
4263
4498
|
import { reactive } from '@abide/abide'
|
|
4264
|
-
|
|
4499
|
+
|
|
4500
|
+
import syncContact from '../syncContact.handler.ts'
|
|
4265
4501
|
|
|
4266
4502
|
let status = reactive('')
|
|
4267
4503
|
|
|
@@ -4283,9 +4519,10 @@ async function sync() {
|
|
|
4283
4519
|
</div>
|
|
4284
4520
|
```
|
|
4285
4521
|
|
|
4286
|
-
```ts src/account/syncContact.ts
|
|
4522
|
+
```ts src/account/syncContact.handler.ts
|
|
4287
4523
|
import { refuse } from '@abide/abide'
|
|
4288
4524
|
import { POST, reachable } from '@abide/abide/server'
|
|
4525
|
+
|
|
4289
4526
|
import { billingOrigin } from './lib/billing.server.ts'
|
|
4290
4527
|
|
|
4291
4528
|
/** Refuses at once while billing is down, rather than waiting on every call. */
|
|
@@ -4308,6 +4545,7 @@ export default POST(
|
|
|
4308
4545
|
|
|
4309
4546
|
```ts src/account/lib/SyncContact.test.ts
|
|
4310
4547
|
import { expect, test } from 'bun:test'
|
|
4548
|
+
|
|
4311
4549
|
import { reachable } from '@abide/abide/server'
|
|
4312
4550
|
|
|
4313
4551
|
test('a host is asked once, however many calls ask after it', async () => {
|
|
@@ -4345,7 +4583,8 @@ The answer can be a minute old. The call that follows a `true` can still fail, a
|
|
|
4345
4583
|
```abide src/account/lib/AddContact.abide
|
|
4346
4584
|
<script>
|
|
4347
4585
|
import { reactive } from '@abide/abide'
|
|
4348
|
-
|
|
4586
|
+
|
|
4587
|
+
import addContact from '../addContact.handler.ts'
|
|
4349
4588
|
|
|
4350
4589
|
let added = reactive<{ status: number; location: string | null }>()
|
|
4351
4590
|
|
|
@@ -4356,7 +4595,9 @@ async function add() {
|
|
|
4356
4595
|
</script>
|
|
4357
4596
|
|
|
4358
4597
|
<div class="row">
|
|
4359
|
-
<button onclick={() => {
|
|
4598
|
+
<button onclick={() => {
|
|
4599
|
+
added = add()
|
|
4600
|
+
}}>Add Katherine Johnson</button>
|
|
4360
4601
|
</div>
|
|
4361
4602
|
|
|
4362
4603
|
<ul class="fields">
|
|
@@ -4367,7 +4608,7 @@ async function add() {
|
|
|
4367
4608
|
</ul>
|
|
4368
4609
|
```
|
|
4369
4610
|
|
|
4370
|
-
```ts src/account/addContact.ts
|
|
4611
|
+
```ts src/account/addContact.handler.ts
|
|
4371
4612
|
import { POST, response } from '@abide/abide/server'
|
|
4372
4613
|
|
|
4373
4614
|
let nextId = 4031
|
|
@@ -4387,6 +4628,7 @@ export default POST(
|
|
|
4387
4628
|
|
|
4388
4629
|
```ts src/account/lib/AddContact.test.ts
|
|
4389
4630
|
import { expect, test } from 'bun:test'
|
|
4631
|
+
|
|
4390
4632
|
import { createApp } from '@abide/abide/server'
|
|
4391
4633
|
|
|
4392
4634
|
test('adding a contact answers 201, and where it lives', async () => {
|
|
@@ -4427,9 +4669,13 @@ let refused = reactive('—')
|
|
|
4427
4669
|
function addWidget() {
|
|
4428
4670
|
const widget = document.createElement('script')
|
|
4429
4671
|
widget.src = 'https://widget.example/chat.js'
|
|
4430
|
-
document.addEventListener(
|
|
4431
|
-
|
|
4432
|
-
|
|
4672
|
+
document.addEventListener(
|
|
4673
|
+
'securitypolicyviolation',
|
|
4674
|
+
(event) => {
|
|
4675
|
+
refused = `${event.blockedURI} by ${event.violatedDirective}`
|
|
4676
|
+
},
|
|
4677
|
+
{ once: true },
|
|
4678
|
+
)
|
|
4433
4679
|
document.head.append(widget)
|
|
4434
4680
|
}
|
|
4435
4681
|
</script>
|
|
@@ -4452,18 +4698,19 @@ middleware(csp())
|
|
|
4452
4698
|
</script>
|
|
4453
4699
|
|
|
4454
4700
|
<html lang="en">
|
|
4455
|
-
<head>
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
</head>
|
|
4701
|
+
<head>
|
|
4702
|
+
<meta charset="utf-8" />
|
|
4703
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
4704
|
+
</head>
|
|
4459
4705
|
<body>
|
|
4460
|
-
|
|
4461
|
-
</body>
|
|
4706
|
+
<slot />
|
|
4707
|
+
</body>
|
|
4462
4708
|
</html>
|
|
4463
4709
|
```
|
|
4464
4710
|
|
|
4465
4711
|
```ts src/account/lib/Widget.test.ts
|
|
4466
4712
|
import { expect, test } from 'bun:test'
|
|
4713
|
+
|
|
4467
4714
|
import { createApp } from '@abide/abide/server'
|
|
4468
4715
|
|
|
4469
4716
|
test("a page admits the app's own scripts, and no other origin's", async () => {
|
|
@@ -4516,7 +4763,8 @@ Everything in this chapter lands on one page of the CRM, `/ops`, under `src/ops`
|
|
|
4516
4763
|
```abide src/ops/lib/Retries.abide
|
|
4517
4764
|
<script>
|
|
4518
4765
|
import { reactive } from '@abide/abide'
|
|
4519
|
-
|
|
4766
|
+
|
|
4767
|
+
import getRetries from '../getRetries.handler.ts'
|
|
4520
4768
|
|
|
4521
4769
|
const policy = reactive(() => getRetries())
|
|
4522
4770
|
</script>
|
|
@@ -4527,7 +4775,7 @@ const policy = reactive(() => getRetries())
|
|
|
4527
4775
|
</ul>
|
|
4528
4776
|
```
|
|
4529
4777
|
|
|
4530
|
-
```ts src/ops/getRetries.ts
|
|
4778
|
+
```ts src/ops/getRetries.handler.ts
|
|
4531
4779
|
import { config, GET, onConfig } from '@abide/abide/server'
|
|
4532
4780
|
|
|
4533
4781
|
// The default; a deployment's RETRIES overrides it.
|
|
@@ -4545,6 +4793,7 @@ RETRIES=5
|
|
|
4545
4793
|
|
|
4546
4794
|
```ts src/ops/lib/Retries.test.ts
|
|
4547
4795
|
import { expect, test } from 'bun:test'
|
|
4796
|
+
|
|
4548
4797
|
import { createApp } from '@abide/abide/server'
|
|
4549
4798
|
|
|
4550
4799
|
test('RETRIES=5 in .env overrides the default of 3, as a number', async () => {
|
|
@@ -4598,7 +4847,8 @@ onConfig(() => ({ RETRIES: 3 }), {
|
|
|
4598
4847
|
```abide src/ops/lib/Pool.abide
|
|
4599
4848
|
<script>
|
|
4600
4849
|
import { reactive } from '@abide/abide'
|
|
4601
|
-
|
|
4850
|
+
|
|
4851
|
+
import getPool from '../getPool.handler.ts'
|
|
4602
4852
|
|
|
4603
4853
|
const times = reactive(() => getPool())
|
|
4604
4854
|
</script>
|
|
@@ -4609,8 +4859,9 @@ const times = reactive(() => getPool())
|
|
|
4609
4859
|
</ul>
|
|
4610
4860
|
```
|
|
4611
4861
|
|
|
4612
|
-
```ts src/ops/getPool.ts
|
|
4862
|
+
```ts src/ops/getPool.handler.ts
|
|
4613
4863
|
import { GET, onStart } from '@abide/abide/server'
|
|
4864
|
+
|
|
4614
4865
|
import { pool } from './lib/pool.server.ts'
|
|
4615
4866
|
|
|
4616
4867
|
// Connect before the app answers anything; note when it came up.
|
|
@@ -4628,6 +4879,7 @@ export default GET(() => ({ connectedAt: pool.connectedAt, upAt: pool.upAt }), {
|
|
|
4628
4879
|
|
|
4629
4880
|
```ts src/ops/lib/Pool.test.ts
|
|
4630
4881
|
import { expect, test } from 'bun:test'
|
|
4882
|
+
|
|
4631
4883
|
import { createApp } from '@abide/abide/server'
|
|
4632
4884
|
|
|
4633
4885
|
test('the pool is connected before the first request is answered', async () => {
|
|
@@ -4667,21 +4919,23 @@ const sync = log.named('sync')
|
|
|
4667
4919
|
</div>
|
|
4668
4920
|
|
|
4669
4921
|
<ul class="fields stacked">
|
|
4670
|
-
{#for record of log.records().slice(-20)}
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
{/for}
|
|
4922
|
+
{#for record of log.records().slice(-20)}
|
|
4923
|
+
{#if /:(billing|sync)$/.test(record.namespace)}
|
|
4924
|
+
<li>
|
|
4925
|
+
<span>{record.namespace}</span><strong>{record.message}</strong>
|
|
4926
|
+
</li>
|
|
4927
|
+
{/if}
|
|
4928
|
+
{/for}
|
|
4677
4929
|
</ul>
|
|
4678
4930
|
```
|
|
4679
4931
|
|
|
4680
4932
|
```ts src/ops/lib/Console.test.ts
|
|
4681
4933
|
import { expect, test } from 'bun:test'
|
|
4934
|
+
|
|
4682
4935
|
import { flushSync } from '@abide/abide'
|
|
4683
4936
|
import { createApp } from '@abide/abide/server'
|
|
4684
4937
|
import { mount } from '@abide/harness/abide'
|
|
4938
|
+
|
|
4685
4939
|
import manifest from '../../../package.json' with { type: 'json' }
|
|
4686
4940
|
import Console from './Console.abide'
|
|
4687
4941
|
|
|
@@ -4742,7 +4996,8 @@ abide logs under `abide:`, so `DEBUG=abide:*` turns on the framework and nothing
|
|
|
4742
4996
|
```abide src/ops/lib/Sync.abide
|
|
4743
4997
|
<script>
|
|
4744
4998
|
import { reactive } from '@abide/abide'
|
|
4745
|
-
|
|
4999
|
+
|
|
5000
|
+
import syncContact from '../syncContact.handler.ts'
|
|
4746
5001
|
|
|
4747
5002
|
let synced = reactive<{ trace: string; lines: string[] }>()
|
|
4748
5003
|
|
|
@@ -4765,8 +5020,9 @@ async function sync() {
|
|
|
4765
5020
|
</ul>
|
|
4766
5021
|
```
|
|
4767
5022
|
|
|
4768
|
-
```ts src/ops/syncContact.ts
|
|
5023
|
+
```ts src/ops/syncContact.handler.ts
|
|
4769
5024
|
import { log, POST, trace } from '@abide/abide/server'
|
|
5025
|
+
|
|
4770
5026
|
import { billing } from './lib/billing.ts'
|
|
4771
5027
|
|
|
4772
5028
|
/** Answers at /ops/syncContact: syncs a contact, with its trace's lines. */
|
|
@@ -4797,6 +5053,7 @@ export const billing = {
|
|
|
4797
5053
|
|
|
4798
5054
|
```ts src/ops/lib/Sync.test.ts
|
|
4799
5055
|
import { expect, test } from 'bun:test'
|
|
5056
|
+
|
|
4800
5057
|
import { createApp } from '@abide/abide/server'
|
|
4801
5058
|
|
|
4802
5059
|
const sync = () =>
|
|
@@ -4846,6 +5103,7 @@ With `timing`, a span joins `server-timing` under its name, so the name has to b
|
|
|
4846
5103
|
```abide src/ops/lib/Health.abide
|
|
4847
5104
|
<script server>
|
|
4848
5105
|
import { onHealth } from '@abide/abide/server'
|
|
5106
|
+
|
|
4849
5107
|
import { pool } from './pool.server.ts'
|
|
4850
5108
|
|
|
4851
5109
|
// Lets a page read `account.database` as a string.
|
|
@@ -4872,6 +5130,7 @@ const account = health()
|
|
|
4872
5130
|
|
|
4873
5131
|
```ts src/ops/lib/Health.test.ts
|
|
4874
5132
|
import { expect, test } from 'bun:test'
|
|
5133
|
+
|
|
4875
5134
|
import { createApp } from '@abide/abide/server'
|
|
4876
5135
|
|
|
4877
5136
|
test('the health check carries the database beside the baseline', async () => {
|
|
@@ -4918,8 +5177,10 @@ import { online } from '@abide/abide'
|
|
|
4918
5177
|
|
|
4919
5178
|
```ts src/ops/lib/OfflineBanner.test.ts
|
|
4920
5179
|
import { expect, test } from 'bun:test'
|
|
5180
|
+
|
|
4921
5181
|
import { flushSync } from '@abide/abide'
|
|
4922
5182
|
import { mount } from '@abide/harness/abide'
|
|
5183
|
+
|
|
4923
5184
|
import OfflineBanner from './OfflineBanner.abide'
|
|
4924
5185
|
|
|
4925
5186
|
test('the banner shows while the browser is offline, and goes', async () => {
|
|
@@ -4955,8 +5216,9 @@ watch(() => { if (online) refresh() })
|
|
|
4955
5216
|
```abide src/ops/lib/CallLog.abide
|
|
4956
5217
|
<script>
|
|
4957
5218
|
import { outbox, reactive } from '@abide/abide'
|
|
4958
|
-
|
|
4959
|
-
import
|
|
5219
|
+
|
|
5220
|
+
import listCalls from '../listCalls.handler.ts'
|
|
5221
|
+
import logCall from '../logCall.handler.ts'
|
|
4960
5222
|
|
|
4961
5223
|
const calls = listCalls()
|
|
4962
5224
|
let note = reactive('')
|
|
@@ -4990,8 +5252,9 @@ function log() {
|
|
|
4990
5252
|
</ul>
|
|
4991
5253
|
```
|
|
4992
5254
|
|
|
4993
|
-
```ts src/ops/logCall.ts
|
|
5255
|
+
```ts src/ops/logCall.handler.ts
|
|
4994
5256
|
import { POST } from '@abide/abide/server'
|
|
5257
|
+
|
|
4995
5258
|
import { type Call, calls } from './lib/calls.ts'
|
|
4996
5259
|
|
|
4997
5260
|
/** Logs a call once, however often it is sent: the browser picks its id. */
|
|
@@ -5007,10 +5270,12 @@ export default POST(
|
|
|
5007
5270
|
|
|
5008
5271
|
```ts src/ops/lib/CallLog.test.ts
|
|
5009
5272
|
import { expect, test } from 'bun:test'
|
|
5273
|
+
|
|
5010
5274
|
import { flushSync } from '@abide/abide'
|
|
5011
5275
|
import { mount } from '@abide/harness/abide'
|
|
5012
|
-
|
|
5276
|
+
|
|
5013
5277
|
import CallLog from './CallLog.abide'
|
|
5278
|
+
import { calls } from './calls.ts'
|
|
5014
5279
|
|
|
5015
5280
|
test('a call logged with the network is saved, and nothing waits', async () => {
|
|
5016
5281
|
using _log = await mount(CallLog({}))
|
|
@@ -5026,7 +5291,7 @@ test('a call logged with the network is saved, and nothing waits', async () => {
|
|
|
5026
5291
|
})
|
|
5027
5292
|
|
|
5028
5293
|
test('a call sent twice under one id is logged once', async () => {
|
|
5029
|
-
const { default: logCall } = await import('../logCall.ts')
|
|
5294
|
+
const { default: logCall } = await import('../logCall.handler.ts')
|
|
5030
5295
|
const call = { id: 'again', note: 'Asked twice' }
|
|
5031
5296
|
await logCall(call)
|
|
5032
5297
|
await logCall(call)
|
|
@@ -5067,7 +5332,7 @@ A handler file already carries an address, a method, an argument type and a desc
|
|
|
5067
5332
|
|
|
5068
5333
|
| Surface | Who reads it | Where |
|
|
5069
5334
|
| --- | --- | --- |
|
|
5070
|
-
| `ui` | a page | `import searchContacts from './searchContacts.ts'` |
|
|
5335
|
+
| `ui` | a page | `import searchContacts from './searchContacts.handler.ts'` |
|
|
5071
5336
|
| `openapi` | a generated client, an API gateway | `/__abide/openapi.json` |
|
|
5072
5337
|
| `mcp` | a model | `/__abide/mcp` |
|
|
5073
5338
|
| `cli` | a person or a script in a terminal | `abide call` |
|
|
@@ -5106,8 +5371,9 @@ async function readDocument() {
|
|
|
5106
5371
|
{/if}
|
|
5107
5372
|
```
|
|
5108
5373
|
|
|
5109
|
-
```ts src/assistant/searchContacts.ts
|
|
5374
|
+
```ts src/assistant/searchContacts.handler.ts
|
|
5110
5375
|
import { GET } from '@abide/abide/server'
|
|
5376
|
+
|
|
5111
5377
|
import { CONTACTS } from '../contacts/lib/CONTACTS.server.ts'
|
|
5112
5378
|
|
|
5113
5379
|
/** One handler: an OpenAPI operation, an MCP tool and a command. */
|
|
@@ -5120,6 +5386,7 @@ export default GET(
|
|
|
5120
5386
|
|
|
5121
5387
|
```ts src/assistant/lib/OpenApiOperation.test.ts
|
|
5122
5388
|
import { expect, test } from 'bun:test'
|
|
5389
|
+
|
|
5123
5390
|
import { createApp } from '@abide/abide/server'
|
|
5124
5391
|
|
|
5125
5392
|
test('the handler is an operation, with its sentence and type', async () => {
|
|
@@ -5205,8 +5472,9 @@ async function callAsAModel() {
|
|
|
5205
5472
|
</ul>
|
|
5206
5473
|
```
|
|
5207
5474
|
|
|
5208
|
-
```ts src/assistant/searchContacts.ts
|
|
5475
|
+
```ts src/assistant/searchContacts.handler.ts
|
|
5209
5476
|
import { GET } from '@abide/abide/server'
|
|
5477
|
+
|
|
5210
5478
|
import { CONTACTS } from '../contacts/lib/CONTACTS.server.ts'
|
|
5211
5479
|
|
|
5212
5480
|
/** One handler: an OpenAPI operation, an MCP tool and a command. */
|
|
@@ -5219,6 +5487,7 @@ export default GET(
|
|
|
5219
5487
|
|
|
5220
5488
|
```ts src/assistant/lib/ModelToolCall.test.ts
|
|
5221
5489
|
import { expect, test } from 'bun:test'
|
|
5490
|
+
|
|
5222
5491
|
import { createApp } from '@abide/abide/server'
|
|
5223
5492
|
|
|
5224
5493
|
const TOOL = 'get_assistant_searchContacts'
|
|
@@ -5265,7 +5534,8 @@ abide mcp --url https://crm.example
|
|
|
5265
5534
|
```abide src/assistant/lib/WithheldExport.abide
|
|
5266
5535
|
<script>
|
|
5267
5536
|
import { reactive, url } from '@abide/abide'
|
|
5268
|
-
|
|
5537
|
+
|
|
5538
|
+
import exportContacts from '../exportContacts.handler.ts'
|
|
5269
5539
|
|
|
5270
5540
|
let listed = reactive('—')
|
|
5271
5541
|
let answers = reactive('—')
|
|
@@ -5295,8 +5565,9 @@ async function check() {
|
|
|
5295
5565
|
</ul>
|
|
5296
5566
|
```
|
|
5297
5567
|
|
|
5298
|
-
```ts src/assistant/exportContacts.ts
|
|
5568
|
+
```ts src/assistant/exportContacts.handler.ts
|
|
5299
5569
|
import { GET } from '@abide/abide/server'
|
|
5570
|
+
|
|
5300
5571
|
import { CONTACTS } from '../contacts/lib/CONTACTS.server.ts'
|
|
5301
5572
|
|
|
5302
5573
|
// Every contact at once: for the export button, not for a model.
|
|
@@ -5308,6 +5579,7 @@ export default GET(() => CONTACTS, {
|
|
|
5308
5579
|
|
|
5309
5580
|
```ts src/assistant/lib/WithheldExport.test.ts
|
|
5310
5581
|
import { expect, test } from 'bun:test'
|
|
5582
|
+
|
|
5311
5583
|
import { createApp } from '@abide/abide/server'
|
|
5312
5584
|
|
|
5313
5585
|
test('off the tool list, and still answering on its address', async () => {
|
|
@@ -5340,7 +5612,7 @@ The export is not a tool, and its address still answers. `clients` decides what
|
|
|
5340
5612
|
|
|
5341
5613
|
| Key | Set to `false` |
|
|
5342
5614
|
| --- | --- |
|
|
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. |
|
|
5615
|
+
| `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
5616
|
| `openapi` | No operation in `/__abide/openapi.json`. |
|
|
5345
5617
|
| `mcp` | No tool on `/__abide/mcp`, and none for the assistant. |
|
|
5346
5618
|
| `cli` | Not callable with `abide call`. |
|
|
@@ -5354,7 +5626,8 @@ A handler that returns a whole `Response` starts with `mcp` and `cli` off, havin
|
|
|
5354
5626
|
```abide src/assistant/lib/AssistantChat.abide
|
|
5355
5627
|
<script>
|
|
5356
5628
|
import { reactive } from '@abide/abide'
|
|
5357
|
-
|
|
5629
|
+
|
|
5630
|
+
import askAssistant from '../askAssistant.handler.ts'
|
|
5358
5631
|
|
|
5359
5632
|
const QUESTION = 'Which company is Ada at?'
|
|
5360
5633
|
|
|
@@ -5369,32 +5642,34 @@ const events = reactive(() =>
|
|
|
5369
5642
|
<ul class="chat">
|
|
5370
5643
|
<li data-speaker="you"><span>you</span><p>{QUESTION}</p></li>
|
|
5371
5644
|
{#for event of events}
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
{/for}
|
|
5645
|
+
{#if event.type === 'tool'}
|
|
5646
|
+
<li><span>tool call</span>
|
|
5647
|
+
<p class="font-mono text-chrome">{event.name}</p></li>
|
|
5648
|
+
{:else if event.type === 'input'}
|
|
5649
|
+
<li><span>with input</span>
|
|
5650
|
+
<p class="font-mono text-chrome">
|
|
5651
|
+
{JSON.stringify(event.input)}
|
|
5652
|
+
</p></li>
|
|
5653
|
+
{:else if event.type === 'result'}
|
|
5654
|
+
<li><span>your handler answered</span>
|
|
5655
|
+
<p class="font-mono text-chrome">{event.text}</p></li>
|
|
5656
|
+
{:else if event.type === 'text'}
|
|
5657
|
+
<li><span>assistant</span><p>{event.text}</p></li>
|
|
5658
|
+
{/if}
|
|
5659
|
+
{/for}
|
|
5387
5660
|
</ul>
|
|
5388
5661
|
|
|
5389
5662
|
<div class="row">
|
|
5390
|
-
<button disabled={asked} onclick={() => {
|
|
5663
|
+
<button disabled={asked} onclick={() => {
|
|
5664
|
+
asked = true
|
|
5665
|
+
}}>Ask</button>
|
|
5391
5666
|
</div>
|
|
5392
5667
|
```
|
|
5393
5668
|
|
|
5394
|
-
```ts src/assistant/askAssistant.ts
|
|
5669
|
+
```ts src/assistant/askAssistant.handler.ts
|
|
5395
5670
|
import {
|
|
5396
|
-
type AssistantMessage,
|
|
5397
5671
|
assistant,
|
|
5672
|
+
type AssistantMessage,
|
|
5398
5673
|
POST,
|
|
5399
5674
|
type Provider,
|
|
5400
5675
|
} from '@abide/abide/server'
|
|
@@ -5429,6 +5704,7 @@ export default POST(
|
|
|
5429
5704
|
|
|
5430
5705
|
```ts src/assistant/lib/AssistantChat.test.ts
|
|
5431
5706
|
import { expect, test } from 'bun:test'
|
|
5707
|
+
|
|
5432
5708
|
import { createApp } from '@abide/abide/server'
|
|
5433
5709
|
|
|
5434
5710
|
test('the model calls the CRM search, through the app', async () => {
|
|
@@ -5483,20 +5759,21 @@ test('the model calls the CRM search, through the app', async () => {
|
|
|
5483
5759
|
```abide src/assistant/lib/ClaudeApiRequest.abide
|
|
5484
5760
|
<script>
|
|
5485
5761
|
import { reactive } from '@abide/abide'
|
|
5486
|
-
|
|
5762
|
+
|
|
5763
|
+
import askClaude from '../askClaude.handler.ts'
|
|
5487
5764
|
|
|
5488
5765
|
const QUESTION = 'Which company is Ada at?'
|
|
5489
5766
|
|
|
5490
5767
|
let asked = reactive(false)
|
|
5491
5768
|
const events = reactive(() =>
|
|
5492
|
-
asked
|
|
5493
|
-
? askClaude({ messages: [{ role: 'user', content: QUESTION }] })
|
|
5494
|
-
: [],
|
|
5769
|
+
asked ? askClaude({ messages: [{ role: 'user', content: QUESTION }] }) : [],
|
|
5495
5770
|
)
|
|
5496
5771
|
</script>
|
|
5497
5772
|
|
|
5498
5773
|
<div class="row">
|
|
5499
|
-
<button disabled={asked} onclick={() => {
|
|
5774
|
+
<button disabled={asked} onclick={() => {
|
|
5775
|
+
asked = true
|
|
5776
|
+
}}>Ask</button>
|
|
5500
5777
|
<span class="status">{QUESTION}</span>
|
|
5501
5778
|
</div>
|
|
5502
5779
|
|
|
@@ -5508,15 +5785,16 @@ const events = reactive(() =>
|
|
|
5508
5785
|
{/for}
|
|
5509
5786
|
```
|
|
5510
5787
|
|
|
5511
|
-
```ts src/assistant/askClaude.ts
|
|
5788
|
+
```ts src/assistant/askClaude.handler.ts
|
|
5512
5789
|
import {
|
|
5513
|
-
type AssistantMessage,
|
|
5514
5790
|
assistant,
|
|
5791
|
+
type AssistantMessage,
|
|
5515
5792
|
claudeApi,
|
|
5516
5793
|
POST,
|
|
5517
5794
|
request,
|
|
5518
5795
|
} from '@abide/abide/server'
|
|
5519
|
-
|
|
5796
|
+
|
|
5797
|
+
import standIn from './v1/messages.handler.ts'
|
|
5520
5798
|
|
|
5521
5799
|
export default POST(
|
|
5522
5800
|
async function* ({ messages }: { messages: AssistantMessage[] }) {
|
|
@@ -5537,7 +5815,7 @@ export default POST(
|
|
|
5537
5815
|
)
|
|
5538
5816
|
```
|
|
5539
5817
|
|
|
5540
|
-
```ts src/assistant/v1/messages.ts
|
|
5818
|
+
```ts src/assistant/v1/messages.handler.ts
|
|
5541
5819
|
import { POST, sse } from '@abide/abide/server'
|
|
5542
5820
|
|
|
5543
5821
|
// A stand-in for the Messages API, streaming back the body it was sent.
|
|
@@ -5571,6 +5849,7 @@ export default POST(
|
|
|
5571
5849
|
|
|
5572
5850
|
```ts src/assistant/lib/ClaudeApiRequest.test.ts
|
|
5573
5851
|
import { expect, spyOn, test } from 'bun:test'
|
|
5852
|
+
|
|
5574
5853
|
import { createApp } from '@abide/abide/server'
|
|
5575
5854
|
|
|
5576
5855
|
test('the body is sent as written, with the transcript', async () => {
|
|
@@ -5626,8 +5905,9 @@ Leave `baseURL` out and the request goes to Anthropic. `claudeApi` reads no key
|
|
|
5626
5905
|
```abide src/assistant/lib/ClaudeCodeBridge.abide
|
|
5627
5906
|
<script>
|
|
5628
5907
|
import { reactive } from '@abide/abide'
|
|
5629
|
-
|
|
5630
|
-
import
|
|
5908
|
+
|
|
5909
|
+
import askClaudeCode from '../askClaudeCode.handler.ts'
|
|
5910
|
+
import bridge from '../bridge.handler.ts'
|
|
5631
5911
|
|
|
5632
5912
|
const QUESTION = 'Which company is Grace at?'
|
|
5633
5913
|
|
|
@@ -5657,24 +5937,28 @@ const answer = reactive(() => {
|
|
|
5657
5937
|
text-chrome">{pairing.at(-1)?.command}</pre>
|
|
5658
5938
|
{:else}
|
|
5659
5939
|
<div class="row">
|
|
5660
|
-
<button onclick={() => {
|
|
5940
|
+
<button onclick={() => {
|
|
5941
|
+
connecting = true
|
|
5942
|
+
}}>Connect</button>
|
|
5661
5943
|
</div>
|
|
5662
5944
|
{/if}
|
|
5663
5945
|
|
|
5664
5946
|
<ul class="chat">
|
|
5665
5947
|
<li data-speaker="you"><span>you</span><p>{QUESTION}</p></li>
|
|
5666
5948
|
{#if answer}
|
|
5667
|
-
|
|
5668
|
-
{/if}
|
|
5949
|
+
<li><span>claude code</span><p>{answer}</p></li>
|
|
5950
|
+
{/if}
|
|
5669
5951
|
</ul>
|
|
5670
5952
|
|
|
5671
5953
|
<div class="row">
|
|
5672
|
-
<button onclick={() => {
|
|
5954
|
+
<button onclick={() => {
|
|
5955
|
+
asked += 1
|
|
5956
|
+
}}>Ask</button>
|
|
5673
5957
|
<span class="status">{events.error()?.message ?? ''}</span>
|
|
5674
5958
|
</div>
|
|
5675
5959
|
```
|
|
5676
5960
|
|
|
5677
|
-
```ts src/assistant/bridge.ts
|
|
5961
|
+
```ts src/assistant/bridge.handler.ts
|
|
5678
5962
|
import { claudeCode, GET } from '@abide/abide/server'
|
|
5679
5963
|
|
|
5680
5964
|
// This browser's pairing, for as long as the page watches it.
|
|
@@ -5686,10 +5970,10 @@ export default GET(() => claudeCode.bridge(), {
|
|
|
5686
5970
|
})
|
|
5687
5971
|
```
|
|
5688
5972
|
|
|
5689
|
-
```ts src/assistant/askClaudeCode.ts
|
|
5973
|
+
```ts src/assistant/askClaudeCode.handler.ts
|
|
5690
5974
|
import {
|
|
5691
|
-
type AssistantMessage,
|
|
5692
5975
|
assistant,
|
|
5976
|
+
type AssistantMessage,
|
|
5693
5977
|
claudeCode,
|
|
5694
5978
|
POST,
|
|
5695
5979
|
} from '@abide/abide/server'
|
|
@@ -5708,6 +5992,7 @@ export default POST(
|
|
|
5708
5992
|
|
|
5709
5993
|
```ts src/assistant/lib/ClaudeCodeBridge.test.ts
|
|
5710
5994
|
import { expect, test } from 'bun:test'
|
|
5995
|
+
|
|
5711
5996
|
import { createApp } from '@abide/abide/server'
|
|
5712
5997
|
|
|
5713
5998
|
// A request from one browser: the cookie a page is handed on its first visit.
|
|
@@ -5821,7 +6106,8 @@ Each card is a file of the reports page, beside the test or the bench that prove
|
|
|
5821
6106
|
```abide src/reports/lib/PipelineSummary.abide
|
|
5822
6107
|
<script>
|
|
5823
6108
|
import { reactive } from '@abide/abide'
|
|
5824
|
-
|
|
6109
|
+
|
|
6110
|
+
import getSummary from '../getSummary.handler.ts'
|
|
5825
6111
|
|
|
5826
6112
|
const summary = reactive(() => getSummary())
|
|
5827
6113
|
</script>
|
|
@@ -5832,8 +6118,9 @@ const summary = reactive(() => getSummary())
|
|
|
5832
6118
|
</ul>
|
|
5833
6119
|
```
|
|
5834
6120
|
|
|
5835
|
-
```ts src/reports/getSummary.ts
|
|
6121
|
+
```ts src/reports/getSummary.handler.ts
|
|
5836
6122
|
import { GET } from '@abide/abide/server'
|
|
6123
|
+
|
|
5837
6124
|
import { DEALS } from './lib/DEALS.server.ts'
|
|
5838
6125
|
|
|
5839
6126
|
/** Answers at /reports/getSummary; the summary tiles and their test read it. */
|
|
@@ -5854,6 +6141,7 @@ export default GET(
|
|
|
5854
6141
|
|
|
5855
6142
|
```ts src/reports/lib/PipelineSummary.test.ts
|
|
5856
6143
|
import { expect, test } from 'bun:test'
|
|
6144
|
+
|
|
5857
6145
|
import { createApp } from '@abide/abide/server'
|
|
5858
6146
|
|
|
5859
6147
|
// Run from the app's own directory: `createApp` finds the app there.
|
|
@@ -5889,6 +6177,7 @@ A `.test.ts` has no address, so a test sits beside the file it tests and is neve
|
|
|
5889
6177
|
```abide src/reports/lib/ClosedTiles.abide
|
|
5890
6178
|
<script>
|
|
5891
6179
|
import { reactive } from '@abide/abide'
|
|
6180
|
+
|
|
5892
6181
|
import { closedTotals } from './closedTotals.ts'
|
|
5893
6182
|
|
|
5894
6183
|
let days = reactive(7)
|
|
@@ -5931,8 +6220,10 @@ export function closedTotals(days: number) {
|
|
|
5931
6220
|
|
|
5932
6221
|
```ts src/reports/lib/ClosedTiles.test.ts
|
|
5933
6222
|
import { expect, test } from 'bun:test'
|
|
6223
|
+
|
|
5934
6224
|
import { flushSync } from '@abide/abide'
|
|
5935
6225
|
import { measure, mount } from '@abide/harness/abide'
|
|
6226
|
+
|
|
5936
6227
|
import ClosedTiles from './ClosedTiles.abide'
|
|
5937
6228
|
|
|
5938
6229
|
test('a range switch rewrites two numbers and builds nothing', async () => {
|
|
@@ -5988,6 +6279,7 @@ The emulated DOM is JavaScript, so a count from it holds in a browser and a mill
|
|
|
5988
6279
|
```abide src/reports/lib/StageTiles.abide
|
|
5989
6280
|
<script>
|
|
5990
6281
|
import { reactive } from '@abide/abide'
|
|
6282
|
+
|
|
5991
6283
|
import { summarizeStage } from './summarizeStage.ts'
|
|
5992
6284
|
|
|
5993
6285
|
let stage = reactive('qualified')
|
|
@@ -6044,9 +6336,11 @@ export function summarizeStage(stage: string) {
|
|
|
6044
6336
|
|
|
6045
6337
|
```ts src/reports/lib/StageTiles.test.ts
|
|
6046
6338
|
import { expect, test } from 'bun:test'
|
|
6339
|
+
|
|
6047
6340
|
import { flushSync } from '@abide/abide'
|
|
6048
6341
|
import { mount } from '@abide/harness/abide'
|
|
6049
6342
|
import { measure, publishedBy } from '@abide/harness/measure'
|
|
6343
|
+
|
|
6050
6344
|
import StageTiles from './StageTiles.abide'
|
|
6051
6345
|
|
|
6052
6346
|
test('three tiles read one summary, worked out once per switch', async () => {
|
|
@@ -6072,6 +6366,7 @@ The three tiles read one derived value, so the summary runs once however many ti
|
|
|
6072
6366
|
```abide src/reports/lib/WonDeals.abide
|
|
6073
6367
|
<script>
|
|
6074
6368
|
import { reactive } from '@abide/abide'
|
|
6369
|
+
|
|
6075
6370
|
import { money } from './money.ts'
|
|
6076
6371
|
|
|
6077
6372
|
let won = reactive([{ company: 'Analytical Engines', amount: 8000 }])
|
|
@@ -6111,9 +6406,11 @@ export const money = {
|
|
|
6111
6406
|
|
|
6112
6407
|
```ts src/reports/lib/WonDeals.test.ts
|
|
6113
6408
|
import { expect, spyOn } from 'bun:test'
|
|
6409
|
+
|
|
6114
6410
|
import { flushSync } from '@abide/abide'
|
|
6115
6411
|
import { mount } from '@abide/harness/abide'
|
|
6116
6412
|
import { gate } from '@abide/harness/gate'
|
|
6413
|
+
|
|
6117
6414
|
import { money } from './money.ts'
|
|
6118
6415
|
import WonDeals from './WonDeals.abide'
|
|
6119
6416
|
|
|
@@ -6169,6 +6466,7 @@ With that set, every gate runs a second time with the revert installed, and that
|
|
|
6169
6466
|
```abide src/reports/lib/Forecast.abide
|
|
6170
6467
|
<script>
|
|
6171
6468
|
import { reactive } from '@abide/abide'
|
|
6469
|
+
|
|
6172
6470
|
import { advance, type Deal, forecast } from './forecast.ts'
|
|
6173
6471
|
|
|
6174
6472
|
let deals = reactive<Deal[]>([
|
|
@@ -6225,8 +6523,10 @@ export function advance(deals: Deal[], index: number): Deal[] {
|
|
|
6225
6523
|
|
|
6226
6524
|
```ts src/reports/lib/Forecast.test.ts
|
|
6227
6525
|
import { expect, test } from 'bun:test'
|
|
6526
|
+
|
|
6228
6527
|
import { flushSync } from '@abide/abide'
|
|
6229
6528
|
import { mount } from '@abide/harness/abide'
|
|
6529
|
+
|
|
6230
6530
|
import Forecast from './Forecast.abide'
|
|
6231
6531
|
|
|
6232
6532
|
test('advancing a deal moves the forecast by its weighted amount', async () => {
|
|
@@ -6242,7 +6542,8 @@ test('advancing a deal moves the forecast by its weighted amount', async () => {
|
|
|
6242
6542
|
|
|
6243
6543
|
```ts src/reports/lib/Forecast.bench.ts
|
|
6244
6544
|
import { reactive } from '@abide/abide'
|
|
6245
|
-
import { type Batched
|
|
6545
|
+
import { batch, type Batched } from '@abide/harness/report'
|
|
6546
|
+
|
|
6246
6547
|
import { advance, type Deal, forecast } from './forecast.ts'
|
|
6247
6548
|
|
|
6248
6549
|
// The forecast as the card keeps it, over a pipeline of `count` deals.
|
|
@@ -6304,6 +6605,7 @@ The `bench` script is `harness-bench`, which hands `runBenches` every `*.bench.t
|
|
|
6304
6605
|
```abide src/reports/lib/TopDeals.abide
|
|
6305
6606
|
<script>
|
|
6306
6607
|
import { reactive } from '@abide/abide'
|
|
6608
|
+
|
|
6307
6609
|
import { topFive } from './rankDeals.ts'
|
|
6308
6610
|
|
|
6309
6611
|
let by = reactive<'amount' | 'idle'>('amount')
|
|
@@ -6394,6 +6696,7 @@ A faster sort saves at most the part of the click the sort was. Find that part b
|
|
|
6394
6696
|
```abide src/reports/lib/DealsExport.abide
|
|
6395
6697
|
<script>
|
|
6396
6698
|
import { props } from '@abide/abide'
|
|
6699
|
+
|
|
6397
6700
|
import { dealsOf, type ExportedDeal } from './dealsOf.ts'
|
|
6398
6701
|
|
|
6399
6702
|
const { deals = dealsOf(4) } = props<{ deals?: ExportedDeal[] }>()
|
|
@@ -6429,8 +6732,10 @@ export function dealsOf(count: number): ExportedDeal[] {
|
|
|
6429
6732
|
|
|
6430
6733
|
```ts src/reports/lib/DealsExport.test.ts
|
|
6431
6734
|
import { expect, test } from 'bun:test'
|
|
6735
|
+
|
|
6432
6736
|
import { render } from '@abide/abide/server'
|
|
6433
6737
|
import { bytes } from '@abide/harness/server'
|
|
6738
|
+
|
|
6434
6739
|
import DealsExport from './DealsExport.abide'
|
|
6435
6740
|
import { dealsOf } from './dealsOf.ts'
|
|
6436
6741
|
|
|
@@ -6486,6 +6791,8 @@ abide start --port 8080
|
|
|
6486
6791
|
|
|
6487
6792
|
`abide check [dir…]` type-checks the `.abide` files as well as the TypeScript, and reports on the line you wrote. `abide lsp` is the same check in your editor. Run it first in CI, because it is the cheapest failure to report.
|
|
6488
6793
|
|
|
6794
|
+
`abide format [--check] [dir…]` formats every `.abide` the way `bun fmt` formats everything else: its scripts, styles and expressions under the `[fmt]` options in `bunfig.toml`, and its markup indented by its nesting. `--check` writes nothing and fails where a file would change, and `abide lsp` formats on save the same way.
|
|
6795
|
+
|
|
6489
6796
|
`abide build` writes content-hashed chunks and a manifest naming them. `abide start [--port <n>]` serves what was built, and builds first where nothing was. Configuration is checked as it starts, so a misconfigured deployment fails to come up.
|
|
6490
6797
|
|
|
6491
6798
|
| Name | What it does for the CRM |
|
|
@@ -7696,9 +8003,9 @@ Transports: what is written in place rather than imported.
|
|
|
7696
8003
|
|
|
7697
8004
|
| File | Declares | Served at | Rules |
|
|
7698
8005
|
| --- | --- | --- | --- |
|
|
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) |
|
|
8006
|
+
| `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) |
|
|
8007
|
+
| `health.handler.ts` | `export default GET(…)` | `/health` | [16.2](/rulebook#16.2), [23.14](/rulebook#23.14) |
|
|
8008
|
+
| `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
8009
|
| `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
8010
|
|
|
7704
8011
|
## Headers abide generates
|
|
@@ -7872,14 +8179,14 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
7872
8179
|
|
|
7873
8180
|
| Name | Signature | Meaning | Rules |
|
|
7874
8181
|
| --- | --- | --- | --- |
|
|
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) |
|
|
8182
|
+
| `/__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
8183
|
| `OpenApiDocument` | `JsonValue` | OpenAPI 3.1, whose schema dialect is `JsonSchema`. | [20.6](/rulebook#20.6) |
|
|
7877
8184
|
|
|
7878
8185
|
## MCP
|
|
7879
8186
|
|
|
7880
8187
|
| Name | Signature | Meaning | Rules |
|
|
7881
8188
|
| --- | --- | --- | --- |
|
|
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) |
|
|
8189
|
+
| `/__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
8190
|
|
|
7884
8191
|
## Taught in
|
|
7885
8192
|
|
|
@@ -8799,7 +9106,7 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
8799
9106
|
|
|
8800
9107
|
| Name | Signature | Meaning | Rules |
|
|
8801
9108
|
| --- | --- | --- | --- |
|
|
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) |
|
|
9109
|
+
| `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
9110
|
|
|
8804
9111
|
## Taught in
|
|
8805
9112
|
|
|
@@ -8817,7 +9124,7 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
8817
9124
|
|
|
8818
9125
|
| Name | Signature | Meaning | Rules |
|
|
8819
9126
|
| --- | --- | --- | --- |
|
|
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) |
|
|
9127
|
+
| `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
9128
|
|
|
8822
9129
|
## Taught in
|
|
8823
9130
|
|
|
@@ -9046,9 +9353,10 @@ Pages and routing: what is written in place rather than imported.
|
|
|
9046
9353
|
| --- | --- | --- |
|
|
9047
9354
|
| `<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
9355
|
| `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) |
|
|
9356
|
+
| `[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) |
|
|
9357
|
+
| `[[name]]` | An optional segment. | [23.5](/rulebook#23.5), [30.4](/rulebook#30.4), [30.34](/rulebook#30.34), [30.36](/rulebook#30.36) |
|
|
9358
|
+
| `[...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) |
|
|
9359
|
+
| `[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
9360
|
| `/__abide/**` | Every endpoint abide controls. | [20.8](/rulebook#20.8) |
|
|
9053
9361
|
|
|
9054
9362
|
## Sinks
|
|
@@ -9150,9 +9458,10 @@ Configuration: what is written in place rather than imported.
|
|
|
9150
9458
|
| `src/public/robots.txt` | The file answered at `/robots.txt`. | [36.11](/rulebook#36.11) |
|
|
9151
9459
|
| `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
9460
|
| `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
|
-
| `**/<
|
|
9461
|
+
| `**/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) |
|
|
9462
|
+
| `**/<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) |
|
|
9463
|
+
| `**/<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) |
|
|
9464
|
+
| `**/<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
9465
|
| `**/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
9466
|
| `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
9467
|
| `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 +10723,8 @@ CLI: what is written in place rather than imported.
|
|
|
10414
10723
|
| `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
10724
|
| `abide run <file> [args…]` | Runs a script under the abide runtime. | [38.2](/rulebook#38.2), [38.57](/rulebook#38.57) |
|
|
10416
10725
|
| `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
|
|
10726
|
+
| `abide format [--check] [dir…]` | The formatter over a `.abide` tree. | [38.25](/rulebook#38.25), [38.125](/rulebook#38.125), [38.126](/rulebook#38.126), [38.127](/rulebook#38.127), [38.128](/rulebook#38.128), [38.129](/rulebook#38.129) |
|
|
10727
|
+
| `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
10728
|
| `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
10729
|
| `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
10730
|
| `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) |
|
|
@@ -10425,7 +10735,7 @@ CLI: what is written in place rather than imported.
|
|
|
10425
10735
|
| `abide watch <address> [args]` | A live subscription to one of the app's followed `GET`s. | [38.69](/rulebook#38.69) |
|
|
10426
10736
|
| `abide compile [--target] [--out] [--platforms]` | One standalone executable. | [37.8](/rulebook#37.8), [37.10](/rulebook#37.10), [37.11](/rulebook#37.11), [37.12](/rulebook#37.12), [38.10](/rulebook#38.10), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.44](/rulebook#38.44), [38.45](/rulebook#38.45), [38.46](/rulebook#38.46), [38.47](/rulebook#38.47), [38.48](/rulebook#38.48), [38.49](/rulebook#38.49), [38.50](/rulebook#38.50), [38.51](/rulebook#38.51), [38.52](/rulebook#38.52), [38.56](/rulebook#38.56), [38.93](/rulebook#38.93), [38.97](/rulebook#38.97), [38.123](/rulebook#38.123) |
|
|
10427
10737
|
| `abide bundle [--out]` | A desktop app for the host platform, with a window on the app. | [38.10](/rulebook#38.10), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.70](/rulebook#38.70), [38.71](/rulebook#38.71), [38.72](/rulebook#38.72), [38.73](/rulebook#38.73), [38.74](/rulebook#38.74), [38.75](/rulebook#38.75), [38.76](/rulebook#38.76), [38.77](/rulebook#38.77), [38.78](/rulebook#38.78), [38.79](/rulebook#38.79), [38.80](/rulebook#38.80), [38.81](/rulebook#38.81), [38.82](/rulebook#38.82), [38.83](/rulebook#38.83), [38.84](/rulebook#38.84), [38.85](/rulebook#38.85), [38.86](/rulebook#38.86), [38.87](/rulebook#38.87), [38.88](/rulebook#38.88), [38.89](/rulebook#38.89), [38.90](/rulebook#38.90), [38.91](/rulebook#38.91), [38.92](/rulebook#38.92), [38.101](/rulebook#38.101), [38.102](/rulebook#38.102), [38.123](/rulebook#38.123) |
|
|
10428
|
-
| `abide lsp` | The `.abide` language server, over stdio. | [38.11](/rulebook#38.11), [38.34](/rulebook#38.34), [38.39](/rulebook#38.39), [38.98](/rulebook#38.98), [38.116](/rulebook#38.116), [38.117](/rulebook#38.117), [38.118](/rulebook#38.118) |
|
|
10738
|
+
| `abide lsp` | The `.abide` language server, over stdio. | [38.11](/rulebook#38.11), [38.34](/rulebook#38.34), [38.39](/rulebook#38.39), [38.98](/rulebook#38.98), [38.116](/rulebook#38.116), [38.117](/rulebook#38.117), [38.118](/rulebook#38.118), [38.130](/rulebook#38.130) |
|
|
10429
10739
|
| `--help` · `-h` · `abide help` | Usage, generated from the command list. | [38.12](/rulebook#38.12), [38.113](/rulebook#38.113), [38.114](/rulebook#38.114) |
|
|
10430
10740
|
| `--version` · `-v` | The version of abide, or of the app a compiled executable holds. | [38.115](/rulebook#38.115) |
|
|
10431
10741
|
|