@abide/abide 0.123.2 → 0.124.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 (150) hide show
  1. package/ABIDE.md +8 -5
  2. package/CHANGELOG.md +58 -0
  3. package/bin/abide.ts +36 -65
  4. package/docs/REGISTRY.md +45 -12
  5. package/docs/RULEBOOK.md +24 -14
  6. package/docs/abide.md +999 -459
  7. package/package.json +1 -1
  8. package/src/compiler/ARRAY_MUTATORS.ts +20 -0
  9. package/src/compiler/Diagnostics.ts +198 -0
  10. package/src/compiler/KeptChecker.ts +97 -0
  11. package/src/compiler/LoweredFile.ts +609 -0
  12. package/src/compiler/Lowering.ts +18 -1
  13. package/src/compiler/PositionedRefusal.ts +1 -1
  14. package/src/compiler/SHARED_NAMES.ts +2 -0
  15. package/src/compiler/ShadowHost.ts +670 -0
  16. package/src/compiler/ShadowMap.ts +1 -1
  17. package/src/compiler/TypeSession.ts +76 -1743
  18. package/src/compiler/appLayout.ts +28 -20
  19. package/src/compiler/appLints.ts +109 -0
  20. package/src/compiler/argsDefaultsIn.ts +1 -1
  21. package/src/compiler/checkApp.ts +64 -68
  22. package/src/compiler/checkedShadow.ts +2 -2
  23. package/src/compiler/classifyRead.ts +16 -1
  24. package/src/compiler/compileApp.ts +102 -71
  25. package/src/compiler/declaredExports.ts +46 -21
  26. package/src/compiler/diagnoseSources.ts +232 -0
  27. package/src/compiler/emitClient.ts +1 -1
  28. package/src/compiler/formatAbide.ts +7 -8
  29. package/src/compiler/handlersUnder.ts +32 -63
  30. package/src/compiler/lineOf.ts +0 -19
  31. package/src/compiler/matchersOf.ts +3 -3
  32. package/src/compiler/mountsOf.ts +3 -3
  33. package/src/compiler/reactiveReads.ts +235 -0
  34. package/src/compiler/reactiveTypes.ts +13 -0
  35. package/src/compiler/readSources.ts +16 -18
  36. package/src/compiler/readsIn.ts +181 -82
  37. package/src/compiler/routesDeclaration.ts +46 -0
  38. package/src/compiler/scaffold.ts +7 -2
  39. package/src/compiler/serveLanguage.ts +46 -74
  40. package/src/compiler/sharedModuleWrites.ts +1 -1
  41. package/src/compiler/splitImports.ts +2 -2
  42. package/src/compiler/templateWarnings.ts +1 -2
  43. package/src/compiler/trackingWarnings.ts +1 -1
  44. package/src/server/CLIENT_BUILD.ts +4 -32
  45. package/src/server/DesktopSession.ts +213 -0
  46. package/src/server/Followers.ts +399 -0
  47. package/src/server/MODULE_QUERIES.ts +6 -2
  48. package/src/server/PageRender.ts +201 -0
  49. package/src/server/RESUME_MILLISECONDS.ts +2 -0
  50. package/src/server/abideModules.ts +7 -7
  51. package/src/server/answerFailure.ts +3 -2
  52. package/src/server/answerRequest.ts +16 -369
  53. package/src/server/answerRpc.ts +48 -302
  54. package/src/server/answerSocket.ts +41 -490
  55. package/src/server/appBuild.ts +377 -0
  56. package/src/server/appKit.ts +1 -1
  57. package/src/server/appPlugins.ts +15 -17
  58. package/src/server/appTree.ts +6 -5
  59. package/src/server/argsContract.ts +209 -0
  60. package/src/server/bridgeRelay.ts +153 -65
  61. package/src/server/claudeCode.ts +30 -127
  62. package/src/server/config.ts +6 -7
  63. package/src/server/createApp.ts +38 -66
  64. package/src/server/csp.ts +1 -1
  65. package/src/server/deferredBuilds.ts +3 -16
  66. package/src/server/devWorker.ts +11 -15
  67. package/src/server/faceOf.ts +1 -1
  68. package/src/server/generatedSurfaces.ts +2 -1
  69. package/src/server/handlerFileMount.ts +40 -0
  70. package/src/server/index.ts +3 -3
  71. package/src/server/installServer.ts +5 -9
  72. package/src/server/invokeHandler.ts +230 -0
  73. package/src/server/mountHandlers.ts +184 -0
  74. package/src/server/openDesktopWindow.ts +308 -0
  75. package/src/server/packageDirectory.ts +14 -0
  76. package/src/server/preload.ts +21 -2
  77. package/src/server/principal.ts +5 -4
  78. package/src/server/remote.ts +2 -2
  79. package/src/server/render.ts +31 -59
  80. package/src/server/requestScope.ts +5 -36
  81. package/src/server/runCompiled.ts +1 -1
  82. package/src/server/runDesktop.ts +20 -448
  83. package/src/server/seedBuffer.ts +12 -12
  84. package/src/server/serveDev.ts +24 -58
  85. package/src/server/servedApp.ts +390 -0
  86. package/src/server/shell.ts +1 -1
  87. package/src/server/signInChanged.ts +20 -0
  88. package/src/server/surfaceOf.ts +3 -2
  89. package/src/server/versionOf.ts +7 -16
  90. package/src/shared/RPC_SERVER.ts +27 -31
  91. package/src/shared/Schema.ts +1 -2
  92. package/src/shared/applyPatch.ts +1 -1
  93. package/src/shared/index.ts +1 -1
  94. package/src/shared/{note.ts → issueReport.ts} +22 -0
  95. package/src/shared/loadCall.ts +66 -0
  96. package/src/shared/muxFrames.ts +1 -1
  97. package/src/shared/reactive/Keyed.ts +24 -16
  98. package/src/shared/reactive/Reactive.ts +37 -991
  99. package/src/shared/reactive/argsContract.ts +131 -4
  100. package/src/shared/reactive/byOf.ts +2 -5
  101. package/src/shared/reactive/callLoad.ts +8 -14
  102. package/src/shared/reactive/cellShape.ts +734 -0
  103. package/src/shared/reactive/index.ts +12 -23
  104. package/src/shared/reactive/pairReactive.ts +5 -3
  105. package/src/shared/reactive/patchLog.ts +79 -0
  106. package/src/shared/reactive/patchMarks.ts +34 -0
  107. package/src/shared/reactive/producer.ts +121 -0
  108. package/src/shared/reactive/recordWrites.ts +11 -14
  109. package/src/shared/reactive/scoped.ts +3 -2
  110. package/src/shared/reactive/selections.ts +209 -0
  111. package/src/shared/reactive/share.ts +4 -3
  112. package/src/shared/reactive/watch.ts +3 -4
  113. package/src/shared/rpc.ts +20 -24
  114. package/src/shared/validateJson.ts +1 -2
  115. package/src/shared/writePath.ts +29 -0
  116. package/src/ui/ArmRegion.ts +10 -4
  117. package/src/ui/Level.ts +11 -20
  118. package/src/ui/LevelChain.ts +272 -0
  119. package/src/ui/Router.ts +106 -313
  120. package/src/ui/Scope.ts +17 -3
  121. package/src/ui/TryRegion.ts +22 -23
  122. package/src/ui/bindSpread.ts +2 -4
  123. package/src/ui/listenForRoutes.ts +3 -10
  124. package/src/ui/outboxQueue.ts +23 -11
  125. package/src/ui/outcomeOf.ts +120 -0
  126. package/src/ui/reconcile.ts +4 -6
  127. package/src/ui/routing.ts +2 -1
  128. package/src/ui/rows.ts +2 -2
  129. package/src/ui/setAttribute.ts +12 -3
  130. package/src/ui/socketWire.ts +28 -13
  131. package/src/ui/wire.ts +9 -10
  132. package/src/compiler/Finding.ts +0 -2
  133. package/src/compiler/Refusals.ts +0 -65
  134. package/src/compiler/formatDiagnosis.ts +0 -20
  135. package/src/server/appDirectories.ts +0 -9
  136. package/src/server/argsFor.ts +0 -39
  137. package/src/server/buildDirectory.ts +0 -10
  138. package/src/server/coerceArgs.ts +0 -109
  139. package/src/server/derivedArgs.ts +0 -26
  140. package/src/server/runBuild.ts +0 -24
  141. package/src/server/serveBuild.ts +0 -144
  142. package/src/server/testBuild.ts +0 -220
  143. package/src/shared/LOAD_OFFLINE.ts +0 -15
  144. package/src/shared/LOAD_SIGNAL.ts +0 -10
  145. package/src/shared/issuesOf.ts +0 -18
  146. package/src/shared/reactive/ENTRY_LIVES.ts +0 -7
  147. package/src/shared/reactive/FOLLOWED.ts +0 -8
  148. package/src/shared/reactive/PATCHES.ts +0 -11
  149. package/src/shared/reactive/PATCH_SINKS.ts +0 -26
  150. package/src/shared/reactive/canonicalKey.ts +0 -95
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abide/abide",
3
- "version": "0.123.2",
3
+ "version": "0.124.0",
4
4
  "type": "module",
5
5
  "description": "Isomorphic type-safe framework for reactive async interfaces for humans and machines",
6
6
  "license": "MIT",
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The methods an array declares that change it in place.
3
+ *
4
+ * A constants leaf, with no imports of its own.
5
+ *
6
+ * 31.9 — a call of one on a name bound to a `Reactive` is lifted to a write through the receiver's
7
+ * path only where the receiver's TYPE is an array (`liftedMutators` in `readsIn`): the name says
8
+ * which method, the type says whose.
9
+ */
10
+ export const ARRAY_MUTATORS: ReadonlySet<string> = new Set([
11
+ 'copyWithin',
12
+ 'fill',
13
+ 'pop',
14
+ 'push',
15
+ 'reverse',
16
+ 'shift',
17
+ 'sort',
18
+ 'splice',
19
+ 'unshift',
20
+ ])
@@ -0,0 +1,198 @@
1
+ import { relative, resolve } from 'node:path' // bun exposes no path helpers of its own
2
+
3
+ import { isIgnored } from './isIgnored.ts'
4
+ import { lineOf } from './lineOf.ts'
5
+ import { PositionedRefusal } from './PositionedRefusal.ts'
6
+
7
+ // EVERYTHING THE COMPILER HAS TO SAY, IN ONE PLACE: what a finding is, where it lands on the line an
8
+ // author wrote, which an author silenced, the order the lot is reported in, and the one line each is
9
+ // printed as. `abide build`, `abide dev`, `abide check`, `abide format` and `abide lsp` all read
10
+ // findings off this, so none of them decides for itself what a refusal does or how a warning reads.
11
+
12
+ /**
13
+ * One thing the checker said, placed in the file the author wrote.
14
+ *
15
+ * 38.3 — line and column from 1.
16
+ */
17
+ export type Diagnosis = {
18
+ path: string
19
+ line: number
20
+ column: number
21
+ code: number
22
+ category: 'error' | 'warning' | 'suggestion' | 'message'
23
+ message: string
24
+ }
25
+
26
+ /** A warning or a refusal at a projection offset. */
27
+ export type Finding = { at: number; message: string }
28
+
29
+ /**
30
+ * Where everything a compile finds goes, each at the file it was found in.
31
+ *
32
+ * TWO MODES AND ONE CALL for a refusal: a build stops at one, so a throwing sink throws it; `abide
33
+ * check` reports every one at its file and goes on (38.33), so a collecting sink keeps each as a
34
+ * finding at the file's first line. A warning is kept either way — it does not stop a build — and so
35
+ * is what the checker says (`checked`). Every stage takes this and nothing else, so no stage decides
36
+ * for itself what a refusal does, and none spells a path its own way: a file is named relative to
37
+ * `root` or absolutely, and kept absolute.
38
+ */
39
+ export class Diagnostics {
40
+ /** Every refusal kept, in the order met; empty in a throwing sink. */
41
+ readonly refused: Diagnosis[] = []
42
+ /** Every warning the compiler raised, in the order met (14, 32.14, 11.30, 18.15). */
43
+ readonly warnings: Diagnosis[] = []
44
+ /** What the checker said of each file (38.3). */
45
+ readonly checked: Diagnosis[] = []
46
+ /** What a file below the root is named relative to. */
47
+ readonly root: string
48
+ /** Whether a refusal is kept and the compile goes on, rather than thrown. */
49
+ readonly collecting: boolean
50
+
51
+ constructor(root: string, collecting: boolean) {
52
+ this.root = root
53
+ this.collecting = collecting
54
+ }
55
+
56
+ /** Every finding, in the order reported: the refusals, the warnings, then the checker's. */
57
+ get findings(): Diagnosis[] {
58
+ return [...this.refused, ...this.warnings, ...this.checked]
59
+ }
60
+
61
+ /**
62
+ * Refuses at `file`, below the root or absolute: `thrown` is thrown, or kept. Handed the file's
63
+ * `text`, a `PositionedRefusal` is placed on its line and column, and thrown as that line.
64
+ */
65
+ at(file: string, thrown: unknown, text?: string): void {
66
+ const positioned =
67
+ text !== undefined && thrown instanceof PositionedRefusal
68
+ if (!this.collecting && !positioned) throw thrown
69
+ const diagnosis = diagnosisAt(
70
+ resolve(this.root, file),
71
+ positioned ? text : undefined,
72
+ positioned ? thrown.offset : 0,
73
+ 'error',
74
+ thrown instanceof Error ? thrown.message : String(thrown),
75
+ )
76
+ if (!this.collecting)
77
+ throw new Error(formatDiagnosis(diagnosis, process.cwd()))
78
+ this.refused.push(diagnosis)
79
+ }
80
+
81
+ /**
82
+ * What another stage kept, as this one takes it: every one kept, or every one thrown as one
83
+ * error, each at its file — a build that stops names all it found rather than a first one and a
84
+ * second run (11.37).
85
+ */
86
+ adopt(found: readonly Diagnosis[]): void {
87
+ if (found.length === 0) return
88
+ if (this.collecting) {
89
+ this.refused.push(...found)
90
+ return
91
+ }
92
+ const lines: string[] = []
93
+ for (const one of found) lines.push(formatDiagnosis(one, process.cwd()))
94
+ throw new Error(lines.join('\n'))
95
+ }
96
+
97
+ /** Keeps `found` as warnings: the build goes on. */
98
+ warn(found: readonly Diagnosis[]): void {
99
+ this.warnings.push(...found)
100
+ }
101
+
102
+ /** Keeps what the checker said. */
103
+ check(found: readonly Diagnosis[]): void {
104
+ this.checked.push(...found)
105
+ }
106
+ }
107
+
108
+ /** abide's own finding at `offset` into `text`, or at line 0 where the text was not handed in. */
109
+ export function diagnosisAt(
110
+ path: string,
111
+ text: string | undefined,
112
+ offset: number,
113
+ category: Diagnosis['category'],
114
+ message: string,
115
+ ): Diagnosis {
116
+ return {
117
+ path,
118
+ ...(text === undefined ? { line: 0, column: 0 } : lineOf(text, offset)),
119
+ code: 0,
120
+ category,
121
+ message,
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Each of `findings`, at an offset `place` maps into the file at `path` the author wrote, as a
127
+ * finding on its line — or on line 0 where the text was not handed in.
128
+ *
129
+ * 14.19 — `reads`: they are warnings on reactive reads, and one the line above silences with
130
+ * `abide-ignore` and its clause is left out. No other finding is silenced so.
131
+ */
132
+ export function placedFindings(
133
+ path: string,
134
+ text: string | undefined,
135
+ findings: readonly Finding[],
136
+ place: (at: number) => number,
137
+ category: Diagnosis['category'],
138
+ reads = false,
139
+ ): Diagnosis[] {
140
+ const placed: Diagnosis[] = []
141
+ for (const finding of findings) {
142
+ const offset = place(finding.at)
143
+ if (
144
+ reads &&
145
+ text !== undefined &&
146
+ isIgnored(text, offset, finding.message)
147
+ )
148
+ continue
149
+ placed.push(diagnosisAt(path, text, offset, category, finding.message))
150
+ }
151
+ return placed
152
+ }
153
+
154
+ /** Orders findings by file, then line, then column. */
155
+ export function byPlace(one: Diagnosis, two: Diagnosis): number {
156
+ return (
157
+ one.path.localeCompare(two.path) ||
158
+ one.line - two.line ||
159
+ one.column - two.column
160
+ )
161
+ }
162
+
163
+ /**
164
+ * One finding as one `tsc`-style line.
165
+ *
166
+ * ONE LINE A FINDING, in the shape `tsc` and every editor's problem matcher already read:
167
+ * `file:line:column - error TS2322: …`, the path relative to `from`. A finding with no line — a
168
+ * whole-file or whole-app refusal — names the file alone.
169
+ */
170
+ export function formatDiagnosis(diagnosis: Diagnosis, from: string): string {
171
+ const path = relative(from, diagnosis.path) || diagnosis.path
172
+ const where =
173
+ diagnosis.line === 0
174
+ ? path
175
+ : `${path}:${diagnosis.line}:${diagnosis.column}`
176
+ const code = diagnosis.code === 0 ? '' : ` TS${diagnosis.code}`
177
+ return `${where} - ${diagnosis.category}${code}: ${diagnosis.message}`
178
+ }
179
+
180
+ /**
181
+ * Writes each error and warning in `found` as its line, relative to where the command was run, and
182
+ * answers how many of each — what every command that reports findings prints. A suggestion or a
183
+ * message is neither, and is not written.
184
+ */
185
+ export function printDiagnoses(
186
+ found: readonly Diagnosis[],
187
+ write: (line: string) => void,
188
+ ): { errors: number; warnings: number } {
189
+ let errors = 0
190
+ let warnings = 0
191
+ for (const diagnosis of found) {
192
+ if (diagnosis.category === 'error') errors += 1
193
+ else if (diagnosis.category === 'warning') warnings += 1
194
+ else continue
195
+ write(formatDiagnosis(diagnosis, process.cwd()))
196
+ }
197
+ return { errors, warnings }
198
+ }
@@ -0,0 +1,97 @@
1
+ import { type FSWatcher, watch } from 'node:fs' // bun exposes no file watcher of its own
2
+
3
+ import { type AppChecker, openChecker } from './compileApp.ts'
4
+
5
+ /**
6
+ * An app's checker kept for as long as a server runs, opened when first asked for and told what its
7
+ * watchers see move.
8
+ *
9
+ * `abide dev` and `abide lsp` keep one each, because spawning a checker is most of what a compile
10
+ * costs. WHAT KEEPING ONE TAKES is here rather than in either: one whose project moved
11
+ * (`AppChecker.current`) is closed and another opened, and one a question threw from is dropped —
12
+ * the checker's own process dying is a throw too, and a session nobody can tell apart from a dead
13
+ * one is not one to keep. `heard` is told every move after the checker is, so what it schedules
14
+ * reads a checker that already knows.
15
+ */
16
+ export class KeptChecker {
17
+ readonly root: string
18
+ private readonly heard: () => void
19
+ private readonly watchers: FSWatcher[] = []
20
+ private readonly watched = new Set<string>()
21
+ private checker: Promise<AppChecker> | undefined
22
+ private readonly open: (root: string) => Promise<AppChecker>
23
+
24
+ constructor(
25
+ root: string,
26
+ heard: () => void,
27
+ open: (root: string) => Promise<AppChecker> = openChecker,
28
+ ) {
29
+ this.root = root
30
+ this.heard = heard
31
+ this.open = open
32
+ this.watch(root, true)
33
+ }
34
+
35
+ /** A checker on the project as it stands: the one kept where it still is, or a fresh one. */
36
+ async live(): Promise<AppChecker> {
37
+ const kept = this.checker
38
+ if (kept !== undefined) {
39
+ const checker = await kept.catch(() => undefined)
40
+ if (checker?.current()) return checker
41
+ if (this.checker === kept) await this.drop()
42
+ }
43
+ this.checker ??= this.open(this.root)
44
+ return this.checker
45
+ }
46
+
47
+ /** `question` asked of a live checker; a throw drops it, so the next question opens another. */
48
+ async run<Answer>(
49
+ question: (checker: AppChecker) => Promise<Answer>,
50
+ ): Promise<Answer> {
51
+ try {
52
+ return await question(await this.live())
53
+ } catch (thrown) {
54
+ await this.drop()
55
+ throw thrown
56
+ }
57
+ }
58
+
59
+ /** Watches `directory` too — a linked package's, outside the root — once for the session. */
60
+ watch(directory: string, recursive = false): void {
61
+ if (this.watched.has(directory)) return
62
+ this.watched.add(directory)
63
+ this.watchers.push(
64
+ watch(directory, { recursive }, (_event, filename) =>
65
+ this.saw(
66
+ filename === null ? undefined : `${directory}/${filename}`,
67
+ ),
68
+ ),
69
+ )
70
+ }
71
+
72
+ /** Records a move at `path`, or one nobody could place, then tells `heard`. */
73
+ saw(path: string | undefined): void {
74
+ void this.checker?.then(
75
+ (checker) => checker.saw(path),
76
+ () => {},
77
+ )
78
+ this.heard()
79
+ }
80
+
81
+ /** Stops watching and closes the checker. */
82
+ async close(): Promise<void> {
83
+ for (const watcher of this.watchers) watcher.close()
84
+ this.watchers.length = 0
85
+ await this.drop()
86
+ }
87
+
88
+ private async drop(): Promise<void> {
89
+ const dropped = this.checker
90
+ this.checker = undefined
91
+ try {
92
+ await (await dropped)?.close()
93
+ } catch {
94
+ // Already gone, which is what closing it was for.
95
+ }
96
+ }
97
+ }