@abide/abide 0.103.0 → 0.105.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.
Files changed (88) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/bin/abide.ts +80 -173
  3. package/docs/REGISTRY.md +27 -27
  4. package/docs/RULEBOOK.md +68 -24
  5. package/docs/abide.md +31 -46
  6. package/package.json +1 -1
  7. package/src/compiler/Lowering.ts +15 -5
  8. package/src/compiler/Refusals.ts +57 -0
  9. package/src/compiler/TypeSession.ts +46 -89
  10. package/src/compiler/appLayout.ts +447 -0
  11. package/src/compiler/appMap.ts +21 -25
  12. package/src/compiler/checkApp.ts +61 -97
  13. package/src/compiler/checkedShadow.ts +12 -10
  14. package/src/compiler/classifyRead.ts +3 -6
  15. package/src/compiler/compileApp.ts +183 -478
  16. package/src/compiler/componentGraph.ts +2 -71
  17. package/src/compiler/declaredExports.ts +79 -60
  18. package/src/compiler/emitClient.ts +11 -13
  19. package/src/compiler/emitComponent.ts +21 -6
  20. package/src/compiler/emitSource.ts +20 -20
  21. package/src/compiler/handlersUnder.ts +46 -36
  22. package/src/compiler/hookCalls.ts +152 -0
  23. package/src/compiler/mountsOf.ts +31 -54
  24. package/src/compiler/projectShadow.ts +1 -1
  25. package/src/compiler/readSources.ts +27 -42
  26. package/src/compiler/readsIn.ts +5 -16
  27. package/src/compiler/resolverIn.ts +77 -0
  28. package/src/server/abideModules.ts +152 -146
  29. package/src/server/answerRequest.ts +18 -4
  30. package/src/server/answerSocket.ts +136 -76
  31. package/src/server/answerValue.ts +28 -15
  32. package/src/server/appTree.ts +59 -73
  33. package/src/server/claudeApi.ts +2 -5
  34. package/src/server/clientBundle.ts +198 -0
  35. package/src/server/coerceArgs.ts +1 -1
  36. package/src/server/commands.ts +93 -57
  37. package/src/server/createApp.ts +90 -16
  38. package/src/server/deferredBuilds.ts +175 -0
  39. package/src/server/devWorker.ts +107 -319
  40. package/src/server/faceOf.ts +120 -0
  41. package/src/server/fenceServer.ts +10 -18
  42. package/src/server/generatedSurfaces.ts +12 -53
  43. package/src/server/index.ts +3 -0
  44. package/src/server/placement.ts +167 -0
  45. package/src/server/remote.ts +14 -42
  46. package/src/server/render.ts +25 -17
  47. package/src/server/renderComponent.ts +9 -5
  48. package/src/server/requestScope.ts +10 -4
  49. package/src/server/responseRelay.ts +389 -0
  50. package/src/server/runCompiled.ts +9 -12
  51. package/src/server/runDesktop.ts +6 -9
  52. package/src/server/serveBuild.ts +26 -73
  53. package/src/server/serveDev.ts +19 -176
  54. package/src/server/streamEvent.ts +29 -0
  55. package/src/server/versionOf.ts +1 -2
  56. package/src/server/withAbideSources.ts +2 -1
  57. package/src/shared/HandlerRoute.ts +68 -0
  58. package/src/shared/REFUSAL_KEY.ts +5 -0
  59. package/src/shared/RESERVED_DIRECTORIES.ts +6 -0
  60. package/src/shared/answerOf.ts +105 -78
  61. package/src/shared/filledPath.ts +1 -9
  62. package/src/shared/framing.ts +127 -128
  63. package/src/shared/queryText.ts +15 -7
  64. package/src/shared/reactive/Cursor.ts +69 -124
  65. package/src/shared/reactive/Reactive.ts +296 -162
  66. package/src/shared/reactive/callLoad.ts +4 -18
  67. package/src/shared/reactive/index.ts +11 -8
  68. package/src/shared/reactive/pairReactive.ts +1 -1
  69. package/src/shared/reactive/scoped.ts +4 -3
  70. package/src/shared/reactive/share.ts +11 -1
  71. package/src/shared/routePattern.ts +0 -110
  72. package/src/shared/rpc.ts +17 -24
  73. package/src/shared/rpcRequest.ts +18 -47
  74. package/src/shared/takeEarlyCall.ts +12 -37
  75. package/src/shared/url.ts +1 -1
  76. package/src/ui/Level.ts +276 -0
  77. package/src/ui/List.ts +17 -17
  78. package/src/ui/Router.ts +20 -87
  79. package/src/ui/applyHead.ts +92 -79
  80. package/src/ui/index.ts +4 -2
  81. package/src/ui/reconcile.ts +4 -110
  82. package/src/ui/rows.ts +123 -2
  83. package/src/ui/wire.ts +12 -27
  84. package/src/compiler/hooksCalled.ts +0 -35
  85. package/src/server/clientBuildOptions.ts +0 -54
  86. package/src/server/refuseAddressCollision.ts +0 -37
  87. package/src/shared/reactive/chunksOfStream.ts +0 -57
  88. package/src/ui/MOUNTING_LEVEL.ts +0 -18
@@ -11,10 +11,10 @@ import {
11
11
  SignatureKind,
12
12
  SymbolFlags,
13
13
  } from 'typescript/unstable/async'
14
- import { appFiles } from '#server/appTree.ts'
15
- import { fileKind, placeOf, routeOf } from '#shared/routePattern.ts'
14
+ import { placeOf } from '#server/placement.ts'
16
15
  import { aliasedSymbol } from './aliasedSymbol.ts'
17
16
  import { AMBIENT_SOURCES, ambientReadsIn } from './ambientReadsIn.ts'
17
+ import type { AppLayout, PlacedSource } from './appLayout.ts'
18
18
  import { argsDefaultsIn } from './argsDefaultsIn.ts'
19
19
  import { type CheckerAnswer, UNCHECKED } from './CheckerAnswer.ts'
20
20
  import {
@@ -26,7 +26,6 @@ import {
26
26
  import type { Finding } from './Finding.ts'
27
27
  import { CHECKED_VALUES } from './Lowering.ts'
28
28
  import { diagnosisAt, lineOf } from './lineOf.ts'
29
- import { mountedUnder } from './mountsOf.ts'
30
29
  import type { AbideFile } from './parseAbide.ts'
31
30
  import { projectShadow } from './projectShadow.ts'
32
31
  import { abideIn } from './reactiveTypes.ts'
@@ -65,18 +64,6 @@ export class TypeSession {
65
64
  * text's offsets.
66
65
  */
67
66
  private shadows = new Map<string, { path: string; text: string }>()
68
- /**
69
- * What `diagnose` last read off one walk of the root: the route table it declared (30.26) and the
70
- * modules it read for their `onConfig` (27.13). Kept until `edited` hears anything move, so a
71
- * check asked again and again while an editor types walks and reads nothing on disk.
72
- */
73
- private walked:
74
- | {
75
- within: string
76
- routes: string
77
- configModules: Promise<[string, string][]>
78
- }
79
- | undefined = undefined
80
67
  /** The checker's own key for a path: resolved, and case-folded where its host folds case. */
81
68
  private keyOf: (path: string) => string
82
69
  /**
@@ -358,7 +345,6 @@ export class TypeSession {
358
345
  * checker is not told what was under it.
359
346
  */
360
347
  edited(paths: string[] | undefined): void {
361
- this.walked = undefined
362
348
  if (paths === undefined) {
363
349
  this.everything = true
364
350
  return
@@ -447,10 +433,12 @@ export class TypeSession {
447
433
  }
448
434
 
449
435
  /**
450
- * 34.25, 23.19 — makes `path`, a dependency's file, a root of the opened config, telling
451
- * `changed` the config moved where it was not one already.
436
+ * 34.25, 23.19 — where `source` is a dependency's file, makes `path` — it, or what it lowers to
437
+ * — a root of the opened config, telling `changed` the config moved where it was not one
438
+ * already. The app's own is a root already: telling the config it moved reloads the project.
452
439
  */
453
- private openOutside(path: string, changed: string[]): void {
440
+ private openOutside(source: string, path: string, changed: string[]): void {
441
+ if (placeOf(this.belowRoot(source)) !== 'dependency') return
454
442
  if (this.outside.has(path)) return
455
443
  this.outside.add(path)
456
444
  if (!changed.includes(this.opened)) changed.push(this.opened)
@@ -472,9 +460,7 @@ export class TypeSession {
472
460
  const changed: string[] = []
473
461
  for (const path of modules) {
474
462
  const canonical = this.canonical(path)
475
- // The app's own is a root already: telling the config it moved reloads the project.
476
- if (placeOf(this.belowRoot(canonical)) === 'dependency')
477
- this.openOutside(canonical, changed)
463
+ this.openOutside(canonical, canonical, changed)
478
464
  }
479
465
  const project = await this.currentProject([], changed)
480
466
  if (project === undefined) return project
@@ -574,7 +560,6 @@ export class TypeSession {
574
560
  async readsFor(
575
561
  sources: readonly (Pick<ParsedSource, 'path' | 'file'> & {
576
562
  text?: string
577
- position?: string | undefined
578
563
  })[],
579
564
  ): Promise<{ answers: CheckerAnswer[]; warnings: Diagnosis[] }> {
580
565
  const answered = await this.classify(sources)
@@ -597,7 +582,6 @@ export class TypeSession {
597
582
  private async classify(
598
583
  sources: readonly (Pick<ParsedSource, 'path' | 'file'> & {
599
584
  text?: string
600
- position?: string | undefined
601
585
  })[],
602
586
  ): Promise<{ answers: CheckerAnswer[]; warnings: Diagnosis[] }> {
603
587
  const warnings: Diagnosis[] = []
@@ -646,8 +630,7 @@ export class TypeSession {
646
630
  // AN UNCHANGED SHADOW IS NOT REPORTED, which is what a kept session saves on a save:
647
631
  // the one page that moved is re-read rather than all of them.
648
632
  this.register(map.shadowPath, projection.text, created, changed)
649
- if (placeOf(this.belowRoot(source.path)) === 'dependency')
650
- this.openOutside(map.shadowPath, changed)
633
+ this.openOutside(source.path, map.shadowPath, changed)
651
634
  queries.push({
652
635
  map,
653
636
  spans: {
@@ -690,8 +673,6 @@ export class TypeSession {
690
673
  sourceFile,
691
674
  query.map.shadowPath,
692
675
  query.spans,
693
- // 23.19 — a mounted page's route is its position's.
694
- source.position ?? this.belowRoot(source.path),
695
676
  source.file,
696
677
  )
697
678
  answers[at] = read.answer
@@ -812,22 +793,27 @@ export class TypeSession {
812
793
  * 38.3 — WHAT THE CHECKER SAYS ABOUT EACH FILE AS IT RUNS, placed on the `.abide` line. Each
813
794
  * source is checked as its lowered program (`checkedShadow`), which is registered for the life
814
795
  * of the session beside its shadow for the reason a shadow is; and every `.ts` root of the
815
- * project under `within` is checked as it stands, where `scripts` asks for them — an editor
796
+ * project under the app's root is checked as it stands, where `scripts` asks for them — an editor
816
797
  * asking about one page does not want the whole app. `sources` carry their text, which is what
817
798
  * a position is a line of. A `quiet` source is lowered and not reported on: a component an
818
799
  * editor's page imports, which is there for the props the page is held to (33.4).
819
800
  *
801
+ * `app` is the app the sources are checked in (`appLayout`): its route table, which `url`'s
802
+ * literal is held to (30.26), and its handler files, which type `Config` (27.13). A source placed
803
+ * as a page or a layout carries its `kind` and `route`, which its `route.params` is typed by (23.9).
804
+ *
820
805
  * `refused` is the path of every source whose checked lowering refused, which `diagnoses` has
821
806
  * reported at its line — `abide check` emits the rest of the app without them.
822
807
  */
823
808
  async diagnose(
824
- sources: (Omit<ReadSource, 'quiet' | 'position'> & {
825
- quiet?: boolean
826
- position?: string | undefined
827
- })[],
828
- within: string,
809
+ sources: (Omit<ReadSource, 'quiet'> &
810
+ Partial<Pick<PlacedSource, 'kind' | 'route'>> & {
811
+ quiet?: boolean
812
+ })[],
813
+ app: Pick<AppLayout, 'root' | 'addresses' | 'handlers'>,
829
814
  scripts = true,
830
815
  ): Promise<{ diagnoses: Diagnosis[]; refused: Set<string> }> {
816
+ const within = app.root
831
817
  const created: string[] = []
832
818
  const changed: string[] = []
833
819
  const checked: {
@@ -836,13 +822,24 @@ export class TypeSession {
836
822
  refusals: readonly Finding[]
837
823
  }[] = []
838
824
  for (const source of sources) {
839
- const shadow = checkedShadow(source.file, source.answer)
825
+ const shadow = checkedShadow(
826
+ source.file,
827
+ source.answer,
828
+ (source.kind === 'page' || source.kind === 'layout') &&
829
+ source.route !== undefined
830
+ ? { route: source.route, kind: source.kind }
831
+ : undefined,
832
+ )
840
833
  const map = new ShadowMap(
841
834
  this.canonical(source.path),
842
835
  projectShadow(source.file).origins,
843
836
  shadow.origins,
844
837
  )
845
838
  this.register(map.checkedPath, shadow.text, created, changed)
839
+ // 23.19 — a package's page an editor opens is reported on, so its checked module is a
840
+ // root of the project as its shadow is (`classify`).
841
+ if (!source.quiet)
842
+ this.openOutside(source.path, map.checkedPath, changed)
846
843
  checked.push({ source, map, refusals: shadow.refusals })
847
844
  }
848
845
  // 34.6 — WHAT THE BUNDLER RESOLVES AND THE CHECKER CANNOT: a stylesheet imported for its
@@ -854,30 +851,23 @@ export class TypeSession {
854
851
  created,
855
852
  changed,
856
853
  )
857
- if (this.walked?.within !== within) {
858
- // A refused tree is `checkApp`'s finding (38.33), never the checker's crash.
859
- const files = appFiles(within, () => {})
860
- this.walked = {
861
- within,
862
- routes: routesDeclaration(within, files),
863
- configModules: configModulesUnder(within, files),
864
- }
865
- }
866
- // 27.13 — what a handler file or a server module defaults, beside it.
867
- for (const [path, text] of await this.walked.configModules) {
868
- const shadow = moduleConfigShadow(text)
869
- const at = `${this.canonical(path)}${CONFIG_SUFFIX}`
854
+ // 27.13 — what a handler file defaults, beside it.
855
+ for (const handler of app.handlers) {
856
+ const at = `${this.canonical(handler.path)}${CONFIG_SUFFIX}`
857
+ const shadow = handler.hooks.includes('onConfig')
858
+ ? moduleConfigShadow(handler.text)
859
+ : undefined
870
860
  // One that stops filing keeps its place and declares nothing, so the next snapshot is
871
861
  // told it moved.
872
862
  if (shadow !== undefined || this.shadows.has(this.keyOf(at)))
873
863
  this.register(at, shadow ?? 'export {}\n', created, changed)
874
864
  }
875
- // 30.26 — THE APP'S ROUTE TABLE, which `url`'s literal is held to: every page under the
876
- // root, declared into abide's own `Routes`. A module rather than a script, since only a
865
+ // 30.26 — THE APP'S ROUTE TABLE, which `url`'s literal is held to: every page the build
866
+ // routes, declared into abide's own `Routes`. A module rather than a script, since only a
877
867
  // module augments one.
878
868
  this.register(
879
869
  `${this.canonical(within)}/${ROUTES_DECLARATION}`,
880
- this.walked.routes,
870
+ routesDeclaration(app.addresses),
881
871
  created,
882
872
  changed,
883
873
  )
@@ -1056,7 +1046,7 @@ export class TypeSession {
1056
1046
  const named = (node as { name?: Node }).name ?? node
1057
1047
  const file = node.getSourceFile()
1058
1048
  definitions.push(
1059
- this.placeOf(
1049
+ this.spanInSource(
1060
1050
  file.fileName,
1061
1051
  file.text,
1062
1052
  named.getStart(file),
@@ -1078,7 +1068,7 @@ export class TypeSession {
1078
1068
  * back through both of its maps, and one this session has not lowered — or a span of the
1079
1069
  * scaffolding a lowering adds, which the author never wrote — placed at its top.
1080
1070
  */
1081
- private placeOf(
1071
+ private spanInSource(
1082
1072
  fileName: string,
1083
1073
  text: string,
1084
1074
  start: number,
@@ -1153,23 +1143,10 @@ function isFile(path: string): boolean {
1153
1143
  const ROUTES_DECLARATION = 'abide-routes.d.ts'
1154
1144
  const URL_SOURCE = Bun.fileURLToPath(new URL('../shared/url', import.meta.url))
1155
1145
 
1156
- /**
1157
- * 30.26 — every page under `root`, and every public file's address, as a key of abide's `Routes`, or
1158
- * nothing where it has neither.
1159
- */
1160
- function routesDeclaration(
1161
- root: string,
1162
- { abide, published }: ReturnType<typeof appFiles>,
1163
- ): string {
1164
- const routes = new Set<string>()
1165
- for (const path of abide)
1166
- if (fileKind(path) === 'page') routes.add(routeOf(path))
1167
- // 23.19 — and every page a mount places in the app, at its position.
1168
- for (const { position } of mountedUnder(root, abide))
1169
- if (fileKind(position) === 'page') routes.add(routeOf(position))
1170
- for (const address of published.values()) routes.add(address)
1146
+ /** 30.26 — each of `addresses` as a key of abide's `Routes`. */
1147
+ function routesDeclaration(addresses: readonly string[]): string {
1171
1148
  let keys = ''
1172
- for (const route of [...routes].sort())
1149
+ for (const route of addresses)
1173
1150
  keys += ` ${JSON.stringify(route)}: true\n`
1174
1151
  return `export {}\ndeclare module ${JSON.stringify(URL_SOURCE)} {\n interface Routes {\n${keys} }\n}\n`
1175
1152
  }
@@ -1327,23 +1304,3 @@ function flatten(diagnostic: {
1327
1304
 
1328
1305
  /** What a module that is no `.abide` is checked beside as, for the `Config` it files (27.13). */
1329
1306
  const CONFIG_SUFFIX = '.config-shadow.ts'
1330
-
1331
- /**
1332
- * Every handler file and every module under a `server` directory below `within` that names
1333
- * `onConfig`, with its text — the modules a server runs that 27.13 types `Config` off.
1334
- */
1335
- async function configModulesUnder(
1336
- within: string,
1337
- { handlers, modules }: ReturnType<typeof appFiles>,
1338
- ): Promise<[string, string][]> {
1339
- const paths: string[] = []
1340
- for (const name of handlers) paths.push(`${within}/${name}`)
1341
- for (const name of modules)
1342
- if (placeOf(name) === 'server') paths.push(`${within}/${name}`)
1343
- const texts = await Promise.all(paths.map((path) => Bun.file(path).text()))
1344
- const found: [string, string][] = []
1345
- for (let at = 0; at < paths.length; at += 1)
1346
- if ((texts[at] as string).includes('onConfig'))
1347
- found.push([paths[at] as string, texts[at] as string])
1348
- return found
1349
- }