@abide/abide 0.105.1 → 0.107.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 +28 -0
- package/bin/abide.ts +4 -1
- package/docs/REGISTRY.md +156 -44
- package/docs/RULEBOOK.md +392 -88
- package/docs/abide.md +7245 -10248
- package/package.json +4 -1
- package/scaffold/AGENTS.md +2 -0
- package/scaffold/src/getGreeting.ts +6 -0
- package/scaffold/src/index.abide +3 -1
- package/scaffold/src/tests/getGreeting.test.ts +6 -0
- package/scaffold/src/tests/index.test.ts +18 -0
- package/src/compiler/DOCS_SITE.ts +2 -0
- package/src/compiler/Lowering.ts +234 -14
- package/src/compiler/PositionTable.ts +14 -7
- package/src/compiler/PositionedRefusal.ts +13 -0
- package/src/compiler/Refusals.ts +18 -11
- package/src/compiler/SHARED_NAMES.ts +2 -0
- package/src/compiler/ShadowMap.ts +37 -0
- package/src/compiler/TEMPLATE_BLOCKS.ts +12 -0
- package/src/compiler/TypeSession.ts +410 -31
- package/src/compiler/appLayout.ts +18 -2
- package/src/compiler/argsSchemaOf.ts +55 -9
- package/src/compiler/checkApp.ts +33 -0
- package/src/compiler/checkedShadow.ts +36 -22
- package/src/compiler/citedClauses.ts +14 -0
- package/src/compiler/classifyRead.ts +6 -0
- package/src/compiler/classifyTemplate.ts +7 -4
- package/src/compiler/compileApp.ts +2 -2
- package/src/compiler/declaredExports.ts +27 -6
- package/src/compiler/emitClient.ts +306 -103
- package/src/compiler/handlersUnder.ts +4 -0
- package/src/compiler/invocationProps.ts +1 -1
- package/src/compiler/isIgnored.ts +27 -0
- package/src/compiler/parseAbide.ts +176 -76
- package/src/compiler/projectShadow.ts +30 -6
- package/src/compiler/readSources.ts +1 -1
- package/src/compiler/readsIn.ts +100 -1
- package/src/compiler/renderMarkup.ts +36 -5
- package/src/compiler/scaffold.ts +19 -6
- package/src/compiler/serveLanguage.ts +156 -21
- package/src/compiler/templateContext.ts +150 -0
- package/src/compiler/tokenize.ts +6 -0
- package/src/server/BRIDGE_VERSION.ts +1 -1
- package/src/server/Inbox.ts +8 -0
- package/src/server/MCP_PROTOCOL_VERSION.ts +1 -1
- package/src/server/MOUNTABLE.ts +6 -0
- package/src/server/abideModules.ts +8 -1
- package/src/server/answerRequest.ts +3 -1
- package/src/server/answerRpc.ts +26 -12
- package/src/server/answerSocket.ts +26 -1
- package/src/server/argsFor.ts +4 -3
- package/src/server/assistant.ts +21 -14
- package/src/server/bridgeRelay.ts +108 -20
- package/src/server/claudeApi.ts +19 -11
- package/src/server/claudeCode.ts +19 -14
- package/src/server/commands.ts +73 -9
- package/src/server/createApp.ts +41 -7
- package/src/server/devWorker.ts +3 -3
- package/src/server/generatedSurfaces.ts +37 -4
- package/src/server/muxUpgrade.ts +47 -0
- package/src/server/preload.ts +13 -2
- package/src/server/render.ts +4 -1
- package/src/server/runCompiled.ts +1 -0
- package/src/server/seedBuffer.ts +31 -1
- package/src/server/serveDev.ts +83 -72
- package/src/server/surfaceOf.ts +3 -0
- package/src/server/testMount.ts +53 -0
- package/src/shared/ADDRESSES.ts +1 -1
- package/src/shared/ERROR_META.ts +3 -0
- package/src/shared/Schema.ts +1 -1
- package/src/shared/WORK.ts +14 -12
- package/src/shared/applyPatch.ts +1 -1
- package/src/shared/boundValue.ts +81 -0
- package/src/shared/index.ts +7 -2
- package/src/shared/jsonSchema.ts +25 -2
- package/src/shared/log.ts +11 -1
- package/src/shared/reactive/Keyed.ts +30 -6
- package/src/shared/reactive/PATCHES.ts +1 -1
- package/src/shared/reactive/Reactive.ts +118 -60
- package/src/shared/reactive/canonicalKey.ts +1 -1
- package/src/shared/reactive/index.ts +13 -5
- package/src/shared/reactive/recordWrites.ts +1 -1
- package/src/shared/reactive/watch.ts +26 -4
- package/src/shared/rpc.ts +6 -1
- package/src/shared/url.ts +7 -3
- package/src/shared/validateJson.ts +1 -1
- package/src/ui/ArmRegion.ts +2 -2
- package/src/ui/List.ts +9 -9
- package/src/ui/RawRun.ts +1 -1
- package/src/ui/Router.ts +194 -36
- package/src/ui/Scope.ts +29 -0
- package/src/ui/adoptWalk.ts +3 -3
- package/src/ui/applyHead.ts +25 -3
- package/src/ui/bindElement.ts +4 -0
- package/src/ui/bindProperty.ts +70 -22
- package/src/ui/index.ts +1 -1
- package/src/ui/listenForRoutes.ts +14 -3
- package/src/ui/reconcile.ts +23 -28
- package/src/ui/regions.ts +9 -2
- package/src/ui/rows.ts +108 -28
|
@@ -30,6 +30,11 @@ type Schema = { [keyword: string]: JsonValue }
|
|
|
30
30
|
* class graph instead is wide enough to walk for minutes, and widening it is 19.11's answer.
|
|
31
31
|
*/
|
|
32
32
|
export type Walk<Seen = Map<number, Schema>> = { seen: Seen; depth: number }
|
|
33
|
+
/**
|
|
34
|
+
* A schema walk, and which side of the wire it describes: what a caller SENDS, or what a handler
|
|
35
|
+
* ANSWERS (19.21).
|
|
36
|
+
*/
|
|
37
|
+
type SchemaWalk = Walk & { result: boolean }
|
|
33
38
|
/** The depth a walk stops at: an object type this deep or deeper is not walked. */
|
|
34
39
|
export const DEEPEST = 8
|
|
35
40
|
|
|
@@ -51,7 +56,11 @@ export async function argsSchemaOf(
|
|
|
51
56
|
const args =
|
|
52
57
|
call === undefined ? undefined : await checker.getParameterType(call, 0)
|
|
53
58
|
if (args === undefined) return undefined
|
|
54
|
-
const schema = await schemaOf(checker, args, {
|
|
59
|
+
const schema = await schemaOf(checker, args, {
|
|
60
|
+
seen: new Map(),
|
|
61
|
+
depth: 0,
|
|
62
|
+
result: false,
|
|
63
|
+
})
|
|
55
64
|
if (schema.type !== 'object') return undefined
|
|
56
65
|
const required = schema.required as string[] | undefined
|
|
57
66
|
if (required !== undefined && defaulted.length > 0) {
|
|
@@ -62,10 +71,30 @@ export async function argsSchemaOf(
|
|
|
62
71
|
return schema
|
|
63
72
|
}
|
|
64
73
|
|
|
74
|
+
/**
|
|
75
|
+
* The JSON Schema of what a handler answers, or `undefined` where it says nothing true of it.
|
|
76
|
+
*
|
|
77
|
+
* 19.21 — `value` is what the handler's `Rpc` reads, refusals already out of it. A value that may be
|
|
78
|
+
* nothing answers 204 rather than JSON, and a value the walk can say nothing of — `unknown`, a
|
|
79
|
+
* `Response`, a stream, a class instance — publishes no schema rather than `{}`.
|
|
80
|
+
*/
|
|
81
|
+
export async function resultSchemaOf(
|
|
82
|
+
checker: Checker,
|
|
83
|
+
value: Type | undefined,
|
|
84
|
+
): Promise<Schema | undefined> {
|
|
85
|
+
if (value === undefined || (await holdsUndefined(value))) return undefined
|
|
86
|
+
const schema = await schemaOf(checker, value, {
|
|
87
|
+
seen: new Map(),
|
|
88
|
+
depth: 0,
|
|
89
|
+
result: true,
|
|
90
|
+
})
|
|
91
|
+
return Object.keys(schema).length === 0 ? undefined : schema
|
|
92
|
+
}
|
|
93
|
+
|
|
65
94
|
async function schemaOf(
|
|
66
95
|
checker: Checker,
|
|
67
96
|
type: Type,
|
|
68
|
-
walk:
|
|
97
|
+
walk: SchemaWalk,
|
|
69
98
|
): Promise<Schema> {
|
|
70
99
|
const flags = type.flags
|
|
71
100
|
if (flags & TypeFlags.String) return { type: 'string' }
|
|
@@ -89,7 +118,11 @@ async function schemaOf(
|
|
|
89
118
|
if (walk.depth >= DEEPEST) return {}
|
|
90
119
|
const pending: Schema = {}
|
|
91
120
|
walk.seen.set(type.id, pending)
|
|
92
|
-
const inner:
|
|
121
|
+
const inner: SchemaWalk = {
|
|
122
|
+
seen: walk.seen,
|
|
123
|
+
depth: walk.depth + 1,
|
|
124
|
+
result: walk.result,
|
|
125
|
+
}
|
|
93
126
|
// Three questions of one type, asked together rather than one round trip after another.
|
|
94
127
|
const [array, tuple, calls] = await Promise.all([
|
|
95
128
|
checker.isArrayType(type),
|
|
@@ -113,7 +146,7 @@ async function schemaOf(
|
|
|
113
146
|
async function objectOf(
|
|
114
147
|
checker: Checker,
|
|
115
148
|
type: Type,
|
|
116
|
-
walk:
|
|
149
|
+
walk: SchemaWalk,
|
|
117
150
|
): Promise<Schema> {
|
|
118
151
|
const properties: Schema = {}
|
|
119
152
|
const required: string[] = []
|
|
@@ -124,6 +157,14 @@ async function objectOf(
|
|
|
124
157
|
const property = members[at] as (typeof members)[number]
|
|
125
158
|
const value = types[at]
|
|
126
159
|
if (value === undefined) continue
|
|
160
|
+
// 19.22 — AN ANSWER WITH A METHOD IS AN INSTANCE, and what `JSON.stringify` makes of one is
|
|
161
|
+
// its own `toJSON`'s or its fields', neither of which its members say.
|
|
162
|
+
if (
|
|
163
|
+
walk.result &&
|
|
164
|
+
(await checker.getSignaturesOfType(value, SignatureKind.Call))
|
|
165
|
+
.length > 0
|
|
166
|
+
)
|
|
167
|
+
return {}
|
|
127
168
|
properties[property.name] = await schemaOf(checker, value, walk)
|
|
128
169
|
// A member JSON can leave out is not required, whether `?` said so or `| undefined` did.
|
|
129
170
|
if (
|
|
@@ -136,8 +177,9 @@ async function objectOf(
|
|
|
136
177
|
if (required.length > 0) schema.required = required
|
|
137
178
|
// 19.18 — A MEMBER THE TYPE DOES NOT DECLARE IS REFUSED, where no index signature declares the
|
|
138
179
|
// rest: a caller outside the process sends whatever it likes, and a body writing its args to a
|
|
139
|
-
// row would write that too. An author who wants the rest declares a schema of their own.
|
|
140
|
-
|
|
180
|
+
// row would write that too. An author who wants the rest declares a schema of their own. 19.22 —
|
|
181
|
+
// an answer is wider than its type wherever the value handed back was.
|
|
182
|
+
if (!walk.result) schema.additionalProperties = false
|
|
141
183
|
for (const index of await checker.getIndexInfosOfType(type))
|
|
142
184
|
if (index.keyType.flags & TypeFlags.String)
|
|
143
185
|
schema.additionalProperties = await schemaOf(
|
|
@@ -156,7 +198,7 @@ async function objectOf(
|
|
|
156
198
|
async function unionOf(
|
|
157
199
|
checker: Checker,
|
|
158
200
|
union: UnionOrIntersectionType,
|
|
159
|
-
walk:
|
|
201
|
+
walk: SchemaWalk,
|
|
160
202
|
): Promise<Schema> {
|
|
161
203
|
const all = await union.getTypes()
|
|
162
204
|
const members: Type[] = []
|
|
@@ -182,8 +224,12 @@ async function unionOf(
|
|
|
182
224
|
type: 'boolean',
|
|
183
225
|
const: (member as LiteralType).value as boolean,
|
|
184
226
|
})
|
|
185
|
-
for (const member of members)
|
|
186
|
-
|
|
227
|
+
for (const member of members) {
|
|
228
|
+
const schema = await schemaOf(checker, member, walk)
|
|
229
|
+
// An arm that says nothing leaves the union saying nothing.
|
|
230
|
+
if (Object.keys(schema).length === 0) return schema
|
|
231
|
+
schemas.push(schema)
|
|
232
|
+
}
|
|
187
233
|
if (schemas.length === 1) return schemas[0] as Schema
|
|
188
234
|
const kinds: string[] = []
|
|
189
235
|
for (const schema of schemas) {
|
package/src/compiler/checkApp.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { relative } from 'node:path' // bun exposes no path helpers of its own
|
|
2
2
|
import { type Placement, placement } from '#server/placement.ts'
|
|
3
|
+
import type { PlacedSource } from './appLayout.ts'
|
|
3
4
|
import { type AppChecker, compiled } from './compileApp.ts'
|
|
4
5
|
import { abideImportsOf } from './componentGraph.ts'
|
|
5
6
|
import { hooksIn } from './hookCalls.ts'
|
|
@@ -48,6 +49,7 @@ export async function checkSource(
|
|
|
48
49
|
const { session, root } = checker
|
|
49
50
|
const layout = await checker.layout()
|
|
50
51
|
// 38.33 — WHAT THE TREE REFUSES OF IT, off the layout the checker keeps until a move is heard.
|
|
52
|
+
// What the parser refuses is the held text's, below, never the disk's (`layout.unparsed`).
|
|
51
53
|
const tree: Diagnosis[] = []
|
|
52
54
|
for (const refused of layout.refused)
|
|
53
55
|
if (refused.path === path) tree.push(refused)
|
|
@@ -116,6 +118,37 @@ export async function checkSource(
|
|
|
116
118
|
return [...tree, ...own, ...diagnoses]
|
|
117
119
|
}
|
|
118
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Lowers every `.abide` of the app into the checker, quietly, as an editor holds it.
|
|
123
|
+
*
|
|
124
|
+
* 38.116 — A NAME'S REFERENCES ARE THE WHOLE APP'S, and `checkSource` lowers one page and what it
|
|
125
|
+
* imports: a page this one never imports, reading the same export, is in no program until it is
|
|
126
|
+
* lowered. A file the parser refuses is left out, as `checkApp` leaves it.
|
|
127
|
+
*/
|
|
128
|
+
export async function lowerApp(
|
|
129
|
+
checker: AppChecker,
|
|
130
|
+
held: (path: string) => string | undefined,
|
|
131
|
+
): Promise<void> {
|
|
132
|
+
const layout = await checker.layout()
|
|
133
|
+
const refusals = new Refusals(checker.root, true)
|
|
134
|
+
const sources: (PlacedSource & { quiet: true })[] = []
|
|
135
|
+
for (const source of layout.sources) {
|
|
136
|
+
const editing = held(source.path)
|
|
137
|
+
if (editing === undefined || editing === source.text) {
|
|
138
|
+
sources.push({ ...source, quiet: true })
|
|
139
|
+
continue
|
|
140
|
+
}
|
|
141
|
+
const [parsed] = parseSources(
|
|
142
|
+
[{ path: source.path, text: editing, quiet: true }],
|
|
143
|
+
refusals,
|
|
144
|
+
)
|
|
145
|
+
if (parsed !== undefined)
|
|
146
|
+
sources.push({ ...source, ...parsed, quiet: true })
|
|
147
|
+
}
|
|
148
|
+
const { read } = await readSources(checker.session, sources, refusals)
|
|
149
|
+
await checker.session.diagnose(read, layout, false)
|
|
150
|
+
}
|
|
151
|
+
|
|
119
152
|
/** Whether a `.abide` an editor holds calls `onConfig`, where its text parses. */
|
|
120
153
|
function filesConfig(text: string): boolean {
|
|
121
154
|
try {
|
|
@@ -43,24 +43,36 @@ export type CheckedShadow = {
|
|
|
43
43
|
*/
|
|
44
44
|
export const COMPONENT_STAND_IN = '__component'
|
|
45
45
|
|
|
46
|
+
/** 34.16 — the arguments a component is called with: none where no prop is required of it. */
|
|
47
|
+
const COMPONENT_RECORD = '__record'
|
|
48
|
+
|
|
46
49
|
/**
|
|
47
50
|
* The component a checked module exports as its default: called with the record its tag passes
|
|
48
51
|
* (`projectShadow`'s `invocation`), and answering the `Component` a server module's call of it
|
|
49
52
|
* renders (34.16). What its blocks export by name is beside it.
|
|
53
|
+
*
|
|
54
|
+
* `props` is spelled ONCE, between `head` and `tail`, so the one spelling is the one a tag's keys are
|
|
55
|
+
* contextually typed by, and a caller can copy it from the author's call (38.116).
|
|
50
56
|
*/
|
|
51
|
-
function componentModule(
|
|
57
|
+
function componentModule(generics?: string): { head: string; tail: string } {
|
|
52
58
|
// 34.13 — generic over what the file's `<script>` declares, so a caller's props infer it, the
|
|
53
59
|
// signature's own parameters shadowing the opaque stand-ins the body was checked against. 34.16 —
|
|
54
60
|
// a component no prop is required of is called with none, as the runtime's bound face takes it.
|
|
55
61
|
const parameters = generics === undefined ? '' : `<${generics}>`
|
|
56
|
-
return
|
|
57
|
-
|
|
62
|
+
return {
|
|
63
|
+
head: `
|
|
64
|
+
type ${COMPONENT_RECORD}<Props> = {} extends Props ? [record?: Props] : [record: Props]
|
|
65
|
+
declare const ${COMPONENT_STAND_IN}: ${parameters}(...record: ${COMPONENT_RECORD}<`,
|
|
66
|
+
tail: `>) => import('${SERVER_SPECIFIER}').Component
|
|
58
67
|
export default ${COMPONENT_STAND_IN}
|
|
59
|
-
|
|
68
|
+
`,
|
|
69
|
+
}
|
|
60
70
|
}
|
|
61
71
|
|
|
72
|
+
const STAND_IN = componentModule()
|
|
73
|
+
|
|
62
74
|
/** A component nothing has lowered takes anything, which is no finding rather than a wrong one. */
|
|
63
|
-
export const COMPONENT_MODULE =
|
|
75
|
+
export const COMPONENT_MODULE = `${STAND_IN.head}any${STAND_IN.tail}`
|
|
64
76
|
|
|
65
77
|
/** The file lowered the way the emitters lower it, for the checker to report on. */
|
|
66
78
|
export function checkedShadow(
|
|
@@ -103,7 +115,25 @@ export function checkedShadow(
|
|
|
103
115
|
}
|
|
104
116
|
origins.push({ at: text.length, source: at, length: source.length - at })
|
|
105
117
|
text += source.slice(at)
|
|
106
|
-
|
|
118
|
+
// 33.4 — WHAT THE CHILD DECLARES IS WHAT A CALLER IS HELD TO: the type `props<X>()` hands back,
|
|
119
|
+
// read off the call as the author wrote it. 33.3 — `props()` with no argument hands back the
|
|
120
|
+
// untyped record, and takes anything. 38.116 — AND THE COPY IS PLACED where it was copied from,
|
|
121
|
+
// so a rename of a prop asked at a tag's key reaches the declaration it is a copy of.
|
|
122
|
+
const module = componentModule(file.generics)
|
|
123
|
+
text += module.head
|
|
124
|
+
const read = lowering.ownPropsRead()
|
|
125
|
+
if (read === undefined) text += NO_PROPS
|
|
126
|
+
else {
|
|
127
|
+
text += 'ReturnType<typeof '
|
|
128
|
+
const called = source.slice(read.from, read.to).replace(/\(\s*\)$/, '')
|
|
129
|
+
origins.push({
|
|
130
|
+
at: text.length,
|
|
131
|
+
source: read.from,
|
|
132
|
+
length: called.length,
|
|
133
|
+
})
|
|
134
|
+
text += `${called}>`
|
|
135
|
+
}
|
|
136
|
+
text += module.tail
|
|
107
137
|
// What each name read by name holds when it is read: what calling it hands back, and through a
|
|
108
138
|
// props accessor, what calling what that hands back does.
|
|
109
139
|
let values = ''
|
|
@@ -122,22 +152,6 @@ export function checkedShadow(
|
|
|
122
152
|
}
|
|
123
153
|
}
|
|
124
154
|
|
|
125
|
-
/**
|
|
126
|
-
* The props type a caller is held to, spelled off the file's `props` call.
|
|
127
|
-
*
|
|
128
|
-
* 33.4 — WHAT THE CHILD DECLARES IS WHAT A CALLER IS HELD TO: the type `props<X>()` hands back, read
|
|
129
|
-
* off the call as the author wrote it, which stands at the top of the module where this is
|
|
130
|
-
* declared. 33.3 — `props()` with no argument hands back the untyped record, and takes anything.
|
|
131
|
-
*/
|
|
132
|
-
function propsOf(lowering: Lowering): string {
|
|
133
|
-
const read = lowering.ownPropsRead()
|
|
134
|
-
return read === undefined
|
|
135
|
-
? NO_PROPS
|
|
136
|
-
: `ReturnType<typeof ${lowering.projection.text
|
|
137
|
-
.slice(read.from, read.to)
|
|
138
|
-
.replace(/\(\s*\)$/, '')}>`
|
|
139
|
-
}
|
|
140
|
-
|
|
141
155
|
/**
|
|
142
156
|
* 27.13 — WHAT EACH `onConfig` DEFAULTS IS DECLARED INTO `Config`, so `config().RETRIES` is the
|
|
143
157
|
* `number` its defaults return rather than the index's `unknown`. Each call is bound by the lowering
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every clause a finding's message cites, in the order it cites them: each number after a
|
|
3
|
+
* `RULEBOOK`, a list (`RULEBOOK 32.14, 32.32.`) included. What `abide-ignore` answers to (14.19)
|
|
4
|
+
* and what the editor links (38.118) — one reading, so the two cannot name different clauses.
|
|
5
|
+
*/
|
|
6
|
+
export function citedClauses(message: string): string[] {
|
|
7
|
+
const clauses: string[] = []
|
|
8
|
+
for (const citation of message.matchAll(
|
|
9
|
+
/\bRULEBOOK (\d+\.\d+(?:,\s*\d+\.\d+)*)/g,
|
|
10
|
+
))
|
|
11
|
+
for (const clause of (citation[1] as string).split(/,\s*/))
|
|
12
|
+
clauses.push(clause)
|
|
13
|
+
return clauses
|
|
14
|
+
}
|
|
@@ -103,6 +103,11 @@ export type Read = {
|
|
|
103
103
|
keys: string[]
|
|
104
104
|
/** 23.9 — the read is abide's `route.params`, which a page or a layout types by its route. */
|
|
105
105
|
routeParams: boolean
|
|
106
|
+
/**
|
|
107
|
+
* 32.67 — a read of a `{#for}` row's item: where that row's item binding starts in the
|
|
108
|
+
* projection, or -1 for every other read.
|
|
109
|
+
*/
|
|
110
|
+
item: number
|
|
106
111
|
}
|
|
107
112
|
|
|
108
113
|
/**
|
|
@@ -134,6 +139,7 @@ export function newRead(
|
|
|
134
139
|
local: false,
|
|
135
140
|
keys: [],
|
|
136
141
|
routeParams: false,
|
|
142
|
+
item: -1,
|
|
137
143
|
}
|
|
138
144
|
}
|
|
139
145
|
|
|
@@ -84,7 +84,8 @@ export type ClassifiedAttribute = {
|
|
|
84
84
|
| { kind: 'spread'; explicit: string[] }
|
|
85
85
|
// 32.5, 32.7, 32.8 — the property and the value, kept in step both ways. `reflects` is the
|
|
86
86
|
// attribute the property's value stands at in the tag, and `undefined` where it is not an
|
|
87
|
-
// attribute: a `<textarea>`'s
|
|
87
|
+
// attribute: a `<textarea>`'s value is its text and a `<select>`'s is its options' `selected`,
|
|
88
|
+
// which the markup writes inside the tag (32.61).
|
|
88
89
|
| {
|
|
89
90
|
kind: 'bound'
|
|
90
91
|
bind: ValueBind
|
|
@@ -463,13 +464,15 @@ export function isParserOwned(lowerTag: string): boolean {
|
|
|
463
464
|
/**
|
|
464
465
|
* The key a component's attribute passes a prop under, or `undefined` where it is no prop.
|
|
465
466
|
*
|
|
466
|
-
* 32.6, 32.10 — a `bind:` handing over a `Reactive` keys the name after it, and any other
|
|
467
|
-
* — `class:`, `style:`, a `bind:` with nothing bound — is an element's own.
|
|
467
|
+
* 32.6, 32.10, 32.69 — a `bind:` handing over a `Reactive` keys the name after it, and any other
|
|
468
|
+
* `:` name — `class:`, `style:`, `bind:element`, a `bind:` with nothing bound — is an element's own.
|
|
469
|
+
* Read as a prop, `bind:element` was one named `element` the child never declared, and the
|
|
470
|
+
* callback never ran. The build writes the
|
|
468
471
|
* props record by this and the checker projects it by this, so a tag is held to what it sends.
|
|
469
472
|
*/
|
|
470
473
|
export function propKeyOf(attribute: Attribute): string | undefined {
|
|
471
474
|
if (attribute.lowerName.startsWith('bind:'))
|
|
472
|
-
return attribute.expression
|
|
475
|
+
return attribute.expression && !isElementBind(attribute.lowerName)
|
|
473
476
|
? attribute.name.slice('bind:'.length)
|
|
474
477
|
: undefined
|
|
475
478
|
return attribute.name.includes(':') ? undefined : attribute.name
|
|
@@ -206,7 +206,7 @@ export async function compiled(
|
|
|
206
206
|
// ONE LAYOUT for the sources, the handler files and the public files, and what the tree refuses
|
|
207
207
|
// is met before the checker is asked anything.
|
|
208
208
|
const layout = await (kept?.layout() ?? appLayout(root))
|
|
209
|
-
refusals.adopt(layout.refused)
|
|
209
|
+
refusals.adopt([...layout.unparsed, ...layout.refused])
|
|
210
210
|
const app: CompiledApp = {
|
|
211
211
|
files: {},
|
|
212
212
|
tables: {},
|
|
@@ -534,7 +534,7 @@ ${tableOf(routes)}
|
|
|
534
534
|
]
|
|
535
535
|
|
|
536
536
|
// The router this document gets, the page it arrived on, and the two places a reader navigates
|
|
537
|
-
// from (30.7). Hydrating here rather than leaving it to the app is what makes the page the server
|
|
537
|
+
// from (RULEBOOK 30.7). Hydrating here rather than leaving it to the app is what makes the page the server
|
|
538
538
|
// painted live without anybody wiring it.
|
|
539
539
|
export function start(host: Element) {
|
|
540
540
|
const router = startRouter(routes, host, ${transitions})
|
|
@@ -35,8 +35,14 @@ import {
|
|
|
35
35
|
import type { Method } from '#shared/rpc.ts'
|
|
36
36
|
import { aliasedSymbol } from './aliasedSymbol.ts'
|
|
37
37
|
import { defaultedMembers, parameterPattern } from './argsDefaultsIn.ts'
|
|
38
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
argsSchemaOf,
|
|
40
|
+
DEEPEST,
|
|
41
|
+
resultSchemaOf,
|
|
42
|
+
type Walk,
|
|
43
|
+
} from './argsSchemaOf.ts'
|
|
39
44
|
import { diagnosisAt } from './lineOf.ts'
|
|
45
|
+
import type { Refusals } from './Refusals.ts'
|
|
40
46
|
import { isReactiveCall, METHODS, optionNamed } from './reactiveOptions.ts'
|
|
41
47
|
import {
|
|
42
48
|
abideIn,
|
|
@@ -44,7 +50,6 @@ import {
|
|
|
44
50
|
isReactiveMember,
|
|
45
51
|
valueTypeOf,
|
|
46
52
|
} from './reactiveTypes.ts'
|
|
47
|
-
import type { Refusals } from './Refusals.ts'
|
|
48
53
|
import type { Diagnosis } from './TypeSession.ts'
|
|
49
54
|
|
|
50
55
|
/**
|
|
@@ -83,7 +88,7 @@ export async function declaredExports(
|
|
|
83
88
|
refusals.at(
|
|
84
89
|
file,
|
|
85
90
|
new Error(
|
|
86
|
-
`abide: ${path} is not inside the project ${projectName}, so what it exports cannot be read (16.73). Give the app a tsconfig whose include reaches it.`,
|
|
91
|
+
`abide: ${path} is not inside the project ${projectName}, so what it exports cannot be read (RULEBOOK 16.73). Give the app a tsconfig whose include reaches it.`,
|
|
87
92
|
),
|
|
88
93
|
)
|
|
89
94
|
return undefined
|
|
@@ -123,17 +128,21 @@ export async function declaredExports(
|
|
|
123
128
|
declared === undefined
|
|
124
129
|
? undefined
|
|
125
130
|
: await checker.getTypeOfSymbol(target)
|
|
131
|
+
const value =
|
|
132
|
+
handlerType === undefined
|
|
133
|
+
? undefined
|
|
134
|
+
: await valueOfRpc(checker, handlerType)
|
|
126
135
|
if (handlerType !== undefined && abide.reactive) {
|
|
127
136
|
const at = await nestedReactiveIn(
|
|
128
137
|
checker,
|
|
129
138
|
abide.reactive,
|
|
130
|
-
|
|
139
|
+
value,
|
|
131
140
|
'',
|
|
132
141
|
{ seen: new Set(), depth: 0 },
|
|
133
142
|
)
|
|
134
143
|
if (at !== undefined)
|
|
135
144
|
throw new Error(
|
|
136
|
-
`abide: ${path} — \`${exported.name}\` returns a \`Reactive\` at \`${at}\`, which the wire would carry as the function it is rather than the value it holds. Read it there, or return the \`Reactive\` itself to have it adopted (11.12). RULEBOOK 16.39.`,
|
|
145
|
+
`abide: ${path} — \`${exported.name}\` returns a \`Reactive\` at \`${at}\`, which the wire would carry as the function it is rather than the value it holds. Read it there, or return the \`Reactive\` itself to have it adopted (RULEBOOK 11.12). RULEBOOK 16.39.`,
|
|
137
146
|
)
|
|
138
147
|
}
|
|
139
148
|
return {
|
|
@@ -153,6 +162,10 @@ export async function declaredExports(
|
|
|
153
162
|
handlerType,
|
|
154
163
|
declared?.defaulted ?? [],
|
|
155
164
|
),
|
|
165
|
+
// 19.21 — a stream's chunks are framed one by one, never one JSON value.
|
|
166
|
+
result: declared?.copy?.stream
|
|
167
|
+
? undefined
|
|
168
|
+
: await resultSchemaOf(checker, value),
|
|
156
169
|
unread,
|
|
157
170
|
}
|
|
158
171
|
} catch (thrown) {
|
|
@@ -179,7 +192,15 @@ const METHODS_SOURCE = Bun.fileURLToPath(
|
|
|
179
192
|
type Declared = Mount['method']
|
|
180
193
|
type DeclaredExport = Pick<
|
|
181
194
|
Mount,
|
|
182
|
-
|
|
195
|
+
| 'name'
|
|
196
|
+
| 'ui'
|
|
197
|
+
| 'cli'
|
|
198
|
+
| 'mcp'
|
|
199
|
+
| 'copy'
|
|
200
|
+
| 'defaults'
|
|
201
|
+
| 'args'
|
|
202
|
+
| 'result'
|
|
203
|
+
| 'description'
|
|
183
204
|
> & {
|
|
184
205
|
method: Declared | undefined
|
|
185
206
|
/** 18.15 — what its copy's options declare and the build could not read. */
|