@abide/abide 0.41.1 → 0.43.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 (167) hide show
  1. package/AGENTS.md +234 -294
  2. package/CHANGELOG.md +82 -0
  3. package/README.md +51 -95
  4. package/package.json +3 -2
  5. package/src/abideLsp.ts +46 -8
  6. package/src/abideResolverPlugin.ts +107 -19
  7. package/src/buildCli.ts +1 -1
  8. package/src/discoveryEntry.ts +3 -3
  9. package/src/lib/bundle/BundleMenuItem.ts +1 -1
  10. package/src/lib/cli/createClient.ts +12 -12
  11. package/src/lib/cli/dispatchCommand.ts +1 -1
  12. package/src/lib/cli/printSessionStatus.ts +1 -1
  13. package/src/lib/cli/resolveCliTarget.ts +1 -1
  14. package/src/lib/cli/runCli.ts +1 -1
  15. package/src/lib/cli/types/CliManifest.ts +1 -1
  16. package/src/lib/cli/types/CliManifestEntry.ts +2 -2
  17. package/src/lib/mcp/annotationsForMethod.ts +5 -5
  18. package/src/lib/mcp/createMcpServer.ts +3 -3
  19. package/src/lib/mcp/mcpSurface.ts +14 -14
  20. package/src/lib/mcp/types/McpServerOptions.ts +1 -1
  21. package/src/lib/server/DELETE.ts +4 -4
  22. package/src/lib/server/GET.ts +4 -4
  23. package/src/lib/server/HEAD.ts +4 -4
  24. package/src/lib/server/PATCH.ts +4 -4
  25. package/src/lib/server/POST.ts +4 -4
  26. package/src/lib/server/PUT.ts +4 -4
  27. package/src/lib/server/agent.ts +4 -4
  28. package/src/lib/server/env.ts +1 -1
  29. package/src/lib/server/error.ts +49 -7
  30. package/src/lib/server/json.ts +1 -1
  31. package/src/lib/server/rpc/buildErrorConstructors.ts +19 -0
  32. package/src/lib/server/rpc/{defineVerb.ts → defineRpc.ts} +52 -34
  33. package/src/lib/server/rpc/{dispatchVerbInProcess.ts → dispatchRpcInProcess.ts} +3 -3
  34. package/src/lib/server/rpc/fieldErrorsFromIssues.ts +23 -0
  35. package/src/lib/server/rpc/{findVerbByCommandName.ts → findRpcByCommandName.ts} +5 -5
  36. package/src/lib/server/rpc/parseArgs.ts +6 -6
  37. package/src/lib/server/rpc/readBodyWithinLimit.ts +2 -2
  38. package/src/lib/server/rpc/registerRpc.ts +6 -0
  39. package/src/lib/server/rpc/{verbRegistry.ts → rpcRegistry.ts} +4 -4
  40. package/src/lib/server/rpc/{runWithVerbTimeout.ts → runWithRpcTimeout.ts} +4 -4
  41. package/src/lib/server/rpc/types/RemoteHandler.ts +9 -3
  42. package/src/lib/server/rpc/types/{VerbHelper.ts → RpcHelper.ts} +57 -23
  43. package/src/lib/server/rpc/types/{VerbRegistryEntry.ts → RpcRegistryEntry.ts} +3 -3
  44. package/src/lib/server/rpc/types/TypedResponse.ts +2 -2
  45. package/src/lib/server/rpc/unprocessed.ts +6 -7
  46. package/src/lib/server/rpc/validationError.ts +17 -0
  47. package/src/lib/server/runtime/buildInspectorSurface.ts +7 -7
  48. package/src/lib/server/runtime/buildOpenApiSpec.ts +6 -6
  49. package/src/lib/server/runtime/createRouteDispatcher.ts +7 -7
  50. package/src/lib/server/runtime/createServer.ts +4 -4
  51. package/src/lib/server/runtime/crossOriginForbidden.ts +1 -1
  52. package/src/lib/server/runtime/crossOriginGate.ts +4 -4
  53. package/src/lib/server/runtime/devClientFingerprint.ts +2 -1
  54. package/src/lib/server/runtime/logExposedSurfaces.ts +4 -4
  55. package/src/lib/server/runtime/registryManifests.ts +2 -2
  56. package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
  57. package/src/lib/server/runtime/types/InspectorRpc.ts +27 -0
  58. package/src/lib/server/runtime/types/InspectorSurface.ts +4 -4
  59. package/src/lib/server/runtime/types/RequestStore.ts +1 -1
  60. package/src/lib/server/runtime/warnUnguardedMcp.ts +2 -2
  61. package/src/lib/server/sockets/createSocketDispatcher.ts +15 -14
  62. package/src/lib/server/sockets/types/SocketOperation.ts +2 -2
  63. package/src/lib/shared/HttpError.ts +15 -1
  64. package/src/lib/shared/UNREACHABLE_STATUSES.ts +13 -0
  65. package/src/lib/shared/buildRpcRequest.ts +5 -5
  66. package/src/lib/shared/cache.ts +50 -168
  67. package/src/lib/shared/carriesBodyArgs.ts +4 -4
  68. package/src/lib/shared/createCacheStore.ts +12 -0
  69. package/src/lib/shared/createRemoteFunction.ts +11 -6
  70. package/src/lib/shared/decodeResponse.ts +2 -2
  71. package/src/lib/shared/detectRpcMethod.ts +17 -0
  72. package/src/lib/shared/emitLogRecord.ts +3 -3
  73. package/src/lib/shared/escapeRegex.ts +9 -0
  74. package/src/lib/shared/extraForwardHeaders.ts +1 -1
  75. package/src/lib/shared/fileName.ts +9 -0
  76. package/src/lib/shared/fileStem.ts +3 -1
  77. package/src/lib/shared/findExportCallSite.ts +30 -273
  78. package/src/lib/shared/forwardHeaders.ts +2 -2
  79. package/src/lib/shared/httpErrorFor.ts +26 -0
  80. package/src/lib/shared/isAsciiWhitespace.ts +5 -0
  81. package/src/lib/shared/isIdentPart.ts +9 -0
  82. package/src/lib/shared/isIdentStart.ts +8 -0
  83. package/src/lib/shared/isReadOnlyMethod.ts +4 -4
  84. package/src/lib/shared/keyForRemoteCall.ts +4 -4
  85. package/src/lib/shared/log.ts +1 -1
  86. package/src/lib/shared/outboxProbeSlot.ts +20 -0
  87. package/src/lib/shared/pending.ts +4 -1
  88. package/src/lib/shared/prepareRpcModule.ts +96 -14
  89. package/src/lib/shared/probeRegistries.ts +17 -4
  90. package/src/lib/shared/programNameForPackage.ts +3 -1
  91. package/src/lib/shared/queryStringFromArgs.ts +1 -1
  92. package/src/lib/shared/remoteMetaStore.ts +2 -2
  93. package/src/lib/shared/resolveClientFlags.ts +1 -1
  94. package/src/lib/shared/skipNonCode.ts +230 -0
  95. package/src/lib/shared/streamResponse.ts +9 -6
  96. package/src/lib/shared/stripImport.ts +6 -4
  97. package/src/lib/shared/types/CacheEntry.ts +2 -4
  98. package/src/lib/shared/types/CacheOnContext.ts +1 -11
  99. package/src/lib/shared/types/CacheStore.ts +13 -7
  100. package/src/lib/shared/types/ClientFlags.ts +1 -1
  101. package/src/lib/shared/types/ErrorConstructors.ts +17 -0
  102. package/src/lib/shared/types/ErrorDescriptor.ts +10 -0
  103. package/src/lib/shared/types/ErrorSpec.ts +11 -0
  104. package/src/lib/shared/types/HttpMethod.ts +1 -0
  105. package/src/lib/shared/types/Outbox.ts +9 -0
  106. package/src/lib/shared/types/OutboxEntry.ts +27 -0
  107. package/src/lib/shared/types/RawRemoteFunction.ts +2 -2
  108. package/src/lib/shared/types/RemoteCallable.ts +2 -2
  109. package/src/lib/shared/types/RemoteFunction.ts +21 -6
  110. package/src/lib/shared/types/RequestScopeInfo.ts +1 -1
  111. package/src/lib/shared/types/RpcErrorGuard.ts +40 -0
  112. package/src/lib/shared/types/RpcInvoker.ts +1 -1
  113. package/src/lib/shared/types/StandardSchemaV1.ts +1 -1
  114. package/src/lib/shared/types/ValidationErrorData.ts +20 -0
  115. package/src/lib/shared/url.ts +3 -3
  116. package/src/lib/shared/writeRpcDts.ts +7 -7
  117. package/src/lib/shared/writeTestRpcDts.ts +3 -3
  118. package/src/lib/test/createTestApp.ts +10 -10
  119. package/src/lib/ui/compile/ABIDE_SEMANTIC_TOKENS_LEGEND.ts +45 -0
  120. package/src/lib/ui/compile/abideUiPlugin.ts +2 -1
  121. package/src/lib/ui/compile/awaitPlan.ts +48 -0
  122. package/src/lib/ui/compile/compileShadow.ts +117 -31
  123. package/src/lib/ui/compile/createShadowLanguageService.ts +48 -0
  124. package/src/lib/ui/compile/desugarSignals.ts +42 -6
  125. package/src/lib/ui/compile/encodeSemanticTokens.ts +37 -0
  126. package/src/lib/ui/compile/generateBuild.ts +45 -62
  127. package/src/lib/ui/compile/generateSSR.ts +90 -60
  128. package/src/lib/ui/compile/ifPlan.ts +30 -0
  129. package/src/lib/ui/compile/makeVarNamer.ts +10 -0
  130. package/src/lib/ui/compile/offsetToPosition.ts +9 -0
  131. package/src/lib/ui/compile/parseTemplate.ts +565 -115
  132. package/src/lib/ui/compile/stripEffects.ts +17 -9
  133. package/src/lib/ui/compile/structuralBlockTokens.ts +101 -0
  134. package/src/lib/ui/compile/switchPlan.ts +22 -0
  135. package/src/lib/ui/compile/tryPlan.ts +29 -0
  136. package/src/lib/ui/compile/types/SemanticToken.ts +11 -0
  137. package/src/lib/ui/compile/types/TemplateAttr.ts +8 -2
  138. package/src/lib/ui/compile/types/TemplateNode.ts +15 -2
  139. package/src/lib/ui/createScope.ts +20 -2
  140. package/src/lib/ui/dom/appendText.ts +1 -5
  141. package/src/lib/ui/dom/applyResolved.ts +1 -5
  142. package/src/lib/ui/dom/disposeRange.ts +6 -10
  143. package/src/lib/ui/dom/hydrate.ts +2 -5
  144. package/src/lib/ui/dom/isComment.ts +6 -0
  145. package/src/lib/ui/dom/mount.ts +1 -2
  146. package/src/lib/ui/dom/withScope.ts +6 -9
  147. package/src/lib/ui/effect.ts +4 -2
  148. package/src/lib/ui/navigate.ts +40 -13
  149. package/src/lib/ui/outbox.ts +30 -110
  150. package/src/lib/ui/remoteProxy.ts +160 -10
  151. package/src/lib/ui/router.ts +3 -3
  152. package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +281 -0
  153. package/src/lib/ui/rpcOutbox/outboxRegistry.ts +69 -0
  154. package/src/lib/ui/runtime/createDoc.ts +3 -3
  155. package/src/lib/ui/runtime/pathSegments.ts +10 -0
  156. package/src/lib/ui/socketChannel.ts +7 -1
  157. package/src/lib/ui/types/Scope.ts +20 -5
  158. package/template/.zed/settings.json +4 -0
  159. package/template/CLAUDE.md +1 -1
  160. package/template/src/server/rpc/getHello.ts +3 -3
  161. package/template/src/ui/pages/page.abide +4 -4
  162. package/template/test/app.test.ts +2 -2
  163. package/src/lib/server/rpc/registerVerb.ts +0 -6
  164. package/src/lib/server/runtime/types/InspectorVerb.ts +0 -27
  165. package/src/lib/shared/detectVerbMethod.ts +0 -17
  166. package/src/lib/shared/types/HttpVerb.ts +0 -1
  167. package/src/lib/ui/types/Outbox.ts +0 -14
@@ -1,16 +1,18 @@
1
1
  import type { ClientFlags } from '../../../shared/types/ClientFlags.ts'
2
+ import type { ErrorSpec } from '../../../shared/types/ErrorSpec.ts'
2
3
  import type { RemoteFunction } from '../../../shared/types/RemoteFunction.ts'
3
4
  import type { StandardSchemaV1 } from '../../../shared/types/StandardSchemaV1.ts'
4
5
  import type { RemoteHandler } from './RemoteHandler.ts'
5
6
 
6
7
  /*
7
- Options every verb overload accepts: the OpenAPI 200 `outputSchema`, the
8
+ Options every rpc overload accepts: the OpenAPI 200 `outputSchema`, the
8
9
  `clients` surface flags, the same-origin CSRF exemption (`crossOrigin`), the
9
10
  pre-parse body-byte ceiling (`maxBodySize`), and the per-surface handler
10
11
  `timeout` (ms). The schema-bearing overloads intersect this with their own
11
- `inputSchema`/`filesSchema` members.
12
+ `inputSchema`/`filesSchema` members. Mutating helpers widen it with `outbox`
13
+ (see MutatingRpcOpts) — a read RPC never accepts it.
12
14
  */
13
- type VerbBaseOpts = {
15
+ type RpcBaseOpts = {
14
16
  outputSchema?: StandardSchemaV1
15
17
  clients?: Partial<ClientFlags>
16
18
  crossOrigin?: boolean
@@ -19,9 +21,22 @@ type VerbBaseOpts = {
19
21
  }
20
22
 
21
23
  /*
22
- Shared signature for every verb helper (GET / POST / …). Three overloads:
24
+ Mutating-helper options: the shared base plus durable delivery. `outbox` lives
25
+ here, not on RpcBaseOpts, because a read RPC has nothing to durably deliver —
26
+ so `GET(fn, { outbox: true })` is a compile error, not the runtime throw it used
27
+ to be. Keeps the type surface honest with the defineRpc guard.
28
+ */
29
+ type MutatingRpcOpts = RpcBaseOpts & {
30
+ /* Durable delivery: on an unreachable server the call still throws, and the request is
31
+ parked for replay. Drains on `rpc.outbox.retry()` — no auto-drain. The call shape is
32
+ unchanged — `rpc.outbox` exposes the queue. */
33
+ outbox?: boolean
34
+ }
35
+
36
+ /*
37
+ Shared signature for every rpc helper (GET / POST / …). Three overloads:
23
38
 
24
- - `Verb(fn, { inputSchema, outputSchema?, clients? })` — `Args` infers
39
+ - `Rpc(fn, { inputSchema, outputSchema?, clients? })` — `Args` infers
25
40
  from `InferInput<InputSchema>`, the handler receives
26
41
  `InferOutput<InputSchema>`. Generic order is `<Return, InputSchema>` so
27
42
  users can override `Return` while letting `InputSchema` infer from
@@ -29,10 +44,10 @@ Shared signature for every verb helper (GET / POST / …). Three overloads:
29
44
  the success body — it feeds the OpenAPI 200 response and the MCP tool
30
45
  `outputSchema`. JSON Schema is projected from each schema's own
31
46
  `toJSONSchema()` (wrap with withJsonSchema if the library lacks one).
32
- `clients` controls which surfaces (browser / mcp / cli) expose this verb.
33
- `crossOrigin: true` exempts a mutating verb from the router's same-origin
47
+ `clients` controls which surfaces (browser / mcp / cli) expose this rpc.
48
+ `crossOrigin: true` exempts a mutating rpc from the router's same-origin
34
49
  CSRF gate — by default a browser request whose Origin doesn't match the
35
- app's own host is refused with 403 on every non-GET/HEAD verb.
50
+ app's own host is refused with 403 on every non-GET/HEAD rpc.
36
51
  `maxBodySize` caps the body's actual received bytes (413 past it),
37
52
  enforced before parsing; omitted, the only ceiling is Bun.serve's
38
53
  server-wide maxRequestBodySize. `timeout` (ms) bounds the handler's
@@ -40,15 +55,15 @@ Shared signature for every verb helper (GET / POST / …). Three overloads:
40
55
  exceeded; on the network path it also aborts request().signal so a
41
56
  handler's `fetch(ext, { signal: request().signal })` is cancelled, not
42
57
  just abandoned.
43
- - `Verb(fn, { clients })` — schemaless but with explicit client
58
+ - `Rpc(fn, { clients })` — schemaless but with explicit client
44
59
  targeting (e.g. server-internal RPC with `clients: { browser: false }`).
45
- - `Verb(fn)` — bare handler. `Args` and `Return` come from the handler
60
+ - `Rpc(fn)` — bare handler. `Args` and `Return` come from the handler
46
61
  type; `Return` is usually inferred via the `TypedResponse<T>` brand on
47
62
  `json`/`error`/`redirect`/`jsonl`/`sse`.
48
63
  */
49
- export type VerbHelper = {
64
+ type RpcHelperOf<Opts> = {
50
65
  /*
51
- `Verb(fn, { inputSchema, filesSchema, … })` — multipart upload. The
66
+ `Rpc(fn, { inputSchema, filesSchema, … })` — multipart upload. The
52
67
  handler receives the text fields (`InferOutput<InputSchema>`) intersected
53
68
  with the validated File parts (`InferOutput<FilesSchema>`); both are merged
54
69
  into one args bag. The call site sends a FormData (RemoteFunction's call
@@ -60,25 +75,44 @@ export type VerbHelper = {
60
75
  Return = unknown,
61
76
  InputSchema extends StandardSchemaV1 = StandardSchemaV1,
62
77
  FilesSchema extends StandardSchemaV1 = StandardSchemaV1,
78
+ Errors extends ErrorSpec = Record<string, never>,
63
79
  >(
64
80
  fn: RemoteHandler<
65
81
  StandardSchemaV1.InferOutput<InputSchema> & StandardSchemaV1.InferOutput<FilesSchema>,
66
- Return
82
+ Return,
83
+ Errors
67
84
  >,
68
- opts: VerbBaseOpts & {
85
+ opts: Opts & {
69
86
  inputSchema: InputSchema
70
87
  filesSchema: FilesSchema
88
+ errors?: Errors
71
89
  },
72
- ): RemoteFunction<StandardSchemaV1.InferInput<InputSchema>, Return>
73
- <Return = unknown, InputSchema extends StandardSchemaV1 = StandardSchemaV1>(
74
- fn: RemoteHandler<StandardSchemaV1.InferOutput<InputSchema>, Return>,
75
- opts: VerbBaseOpts & { inputSchema: InputSchema },
76
- ): RemoteFunction<StandardSchemaV1.InferInput<InputSchema>, Return>
77
- <Args = undefined, Return = unknown>(
78
- fn: RemoteHandler<Args, Return>,
79
- opts: VerbBaseOpts,
80
- ): RemoteFunction<Args, Return>
90
+ ): RemoteFunction<StandardSchemaV1.InferInput<InputSchema>, Return, Errors>
91
+ <
92
+ Return = unknown,
93
+ InputSchema extends StandardSchemaV1 = StandardSchemaV1,
94
+ Errors extends ErrorSpec = Record<string, never>,
95
+ >(
96
+ fn: RemoteHandler<StandardSchemaV1.InferOutput<InputSchema>, Return, Errors>,
97
+ opts: Opts & { inputSchema: InputSchema; errors?: Errors },
98
+ ): RemoteFunction<StandardSchemaV1.InferInput<InputSchema>, Return, Errors>
99
+ <Args = undefined, Return = unknown, Errors extends ErrorSpec = Record<never, never>>(
100
+ fn: RemoteHandler<Args, Return, Errors>,
101
+ opts: Opts & { errors?: Errors },
102
+ ): RemoteFunction<Args, Return, Errors>
81
103
  <Args = undefined, Return = unknown>(
82
104
  fn: RemoteHandler<Args, Return>,
83
105
  ): RemoteFunction<Args, Return>
84
106
  }
107
+
108
+ /* The read helpers (GET/HEAD): no `outbox` — a read has nothing to durably deliver. */
109
+ export type RpcHelper = RpcHelperOf<RpcBaseOpts>
110
+
111
+ /*
112
+ The mutating helpers (POST/PUT/PATCH/DELETE). A durable (`outbox`) call is a normal
113
+ RemoteFunction — it throws exactly like a non-durable one and only parks the request as a
114
+ side-effect on an unreachable server — so there is no separate return shape; `outbox` rides
115
+ MutatingRpcOpts and `rpc.outbox` exposes the queue. The distinct opts base is what makes
116
+ `outbox` legal here and a compile error on the read helpers.
117
+ */
118
+ export type MutatingRpcHelper = RpcHelperOf<MutatingRpcOpts>
@@ -3,7 +3,7 @@ import type { RemoteFunction } from '../../../shared/types/RemoteFunction.ts'
3
3
  import type { StandardSchemaV1 } from '../../../shared/types/StandardSchemaV1.ts'
4
4
 
5
5
  /*
6
- Per-verb registry record on the server side. MCP and CLI enumerate this
6
+ Per-rpc registry record on the server side. MCP and CLI enumerate this
7
7
  to discover which RPCs are advertised (clients flags) and what shapes
8
8
  they expect/return. The schemas and resolved clients stay off the public
9
9
  RemoteFunction shape so the browser-side proxy doesn't need to carry
@@ -20,13 +20,13 @@ from `inputSchema` because a File has no honest JSON-Schema conversion — it
20
20
  stays out of the MCP/CLI projection that `inputSchema` feeds, and the OpenAPI
21
21
  multipart body advertises the file parts generically as binary.
22
22
  */
23
- export type VerbRegistryEntry = {
23
+ export type RpcRegistryEntry = {
24
24
  remote: RemoteFunction<unknown, unknown>
25
25
  inputSchema: StandardSchemaV1 | undefined
26
26
  outputSchema: StandardSchemaV1 | undefined
27
27
  filesSchema: StandardSchemaV1 | undefined
28
28
  clients: ClientFlags
29
- /* The verb's declared opts, recorded so introspection (inspector) can report
29
+ /* The rpc's declared opts, recorded so introspection (inspector) can report
30
30
  the deadline/body-cap/CSRF-exemption a handler runs under. Undefined = the
31
31
  framework default (no deadline, Bun's server-wide body ceiling, gated). */
32
32
  timeout: number | undefined
@@ -1,14 +1,14 @@
1
1
  /*
2
2
  A `Response` tagged with the body type the framework will hand back to
3
3
  callers after Content-Type-driven decoding. The tag is phantom — it
4
- adds no runtime field, only a type-level slot so the verb helpers can
4
+ adds no runtime field, only a type-level slot so the rpc helpers can
5
5
  infer `Return` from the handler's return type instead of forcing every
6
6
  route to annotate it via `GET<Args, Return>`.
7
7
 
8
8
  The respond helpers (`json<T>`, `error`, `redirect`, `jsonl<F>`,
9
9
  `sse<F>`) all return a `TypedResponse<T>`, so a handler ending in
10
10
  `return json({ user })` exposes `{ user: ... }` as its body type; the
11
- verb overload picks it up via `RemoteHandler<Args, Return>`.
11
+ rpc overload picks it up via `RemoteHandler<Args, Return>`.
12
12
 
13
13
  `T` is optional on the brand so a plain `new Response(...)` (untagged)
14
14
  remains assignable to `TypedResponse<unknown>`; in that case `Return`
@@ -1,14 +1,13 @@
1
- import type { RemoteFunction } from '../../shared/types/RemoteFunction.ts'
2
-
3
1
  /*
4
- Verb helpers (GET / POST / …) are placeholders — the bundler rewrites every
2
+ Rpc helpers (GET / POST / …) are placeholders — the bundler rewrites every
5
3
  `export const x = GET(fn)` inside `src/server/rpc/<file>.ts` into a call to
6
- defineVerb (server target) or remoteProxy (client target). If a call slips
4
+ defineRpc (server target) or remoteProxy (client target). If a call slips
7
5
  through, the bundler plugin didn't process the file; throwing here surfaces
8
- that cleanly instead of silently returning undefined.
6
+ that cleanly instead of silently returning undefined. Returns `never` (it always
7
+ throws), so a helper's arrow satisfies every overload of RpcHelper/MutatingRpcHelper.
9
8
  */
10
- export function unprocessed<Args, Return>(verb: string): RemoteFunction<Args, Return> {
9
+ export function unprocessed(method: string): never {
11
10
  throw new Error(
12
- `[abide] \`${verb}\` was called outside an $rpc module — verb helpers are only valid as the value of \`export const <filename> = ...\` inside a file under src/server/rpc/`,
11
+ `[abide] \`${method}\` was called outside an $rpc module — rpc helpers are only valid as the value of \`export const <filename> = ...\` inside a file under src/server/rpc/`,
13
12
  )
14
13
  }
@@ -0,0 +1,17 @@
1
+ import type { StandardSchemaV1 } from '../../shared/types/StandardSchemaV1.ts'
2
+ import type { ValidationErrorData } from '../../shared/types/ValidationErrorData.ts'
3
+ import { error } from '../error.ts'
4
+ import { fieldErrorsFromIssues } from './fieldErrorsFromIssues.ts'
5
+
6
+ /*
7
+ The framework-reserved `validation` typed error a 422 carries: the raw Standard
8
+ Schema `issues` plus the form-friendly field → first-message map. Routed through
9
+ the single `error()` funnel (descriptor form) so it serializes as the same
10
+ `{ $abideError, data }` body every typed error uses, and so the 422's reason
11
+ phrase reaches `HttpError.statusText`. The client parses it back onto
12
+ `HttpError.kind = 'validation'` / `.data: ValidationErrorData`.
13
+ */
14
+ export function validationError(issues: readonly StandardSchemaV1.Issue[]): Response {
15
+ const data: ValidationErrorData = { issues, fields: fieldErrorsFromIssues(issues) }
16
+ return error({ $abideError: 'validation', status: 422, data })
17
+ }
@@ -1,20 +1,20 @@
1
1
  import { jsonSchemaForSchema } from '../../shared/jsonSchemaForSchema.ts'
2
2
  import { promptRegistry } from '../prompts/promptRegistry.ts'
3
- import { verbRegistry } from '../rpc/verbRegistry.ts'
3
+ import { rpcRegistry } from '../rpc/rpcRegistry.ts'
4
4
  import { socketOperations } from '../sockets/socketOperations.ts'
5
5
  import { socketRegistry } from '../sockets/socketRegistry.ts'
6
6
  import type { InspectorSurface } from './types/InspectorSurface.ts'
7
7
 
8
8
  /*
9
- Projects the live verb + socket registries into the inspector's catalog. Read
10
- at call time (not cached) so verbs constructed after boot — lazily on first
9
+ Projects the live rpc + socket registries into the inspector's catalog. Read
10
+ at call time (not cached) so rpcs constructed after boot — lazily on first
11
11
  hit, or eagerly once ensureRegistriesLoaded walked the manifest — show up.
12
12
  Schemas go through jsonSchemaForSchema, the same projection MCP and OpenAPI
13
- use, so the three can't disagree on a verb's shape; a missing schema stays
14
- undefined to mark a verb that carries no machine-advertisable contract.
13
+ use, so the three can't disagree on a rpc's shape; a missing schema stays
14
+ undefined to mark a rpc that carries no machine-advertisable contract.
15
15
  */
16
16
  export function buildInspectorSurface(): InspectorSurface {
17
- const verbs = Array.from(verbRegistry.values()).map((entry) => ({
17
+ const rpcs = Array.from(rpcRegistry.values()).map((entry) => ({
18
18
  url: entry.remote.url,
19
19
  method: entry.remote.method,
20
20
  clients: { ...entry.remote.clients },
@@ -41,5 +41,5 @@ export function buildInspectorSurface(): InspectorSurface {
41
41
  description: entry.prompt.description,
42
42
  inputSchema: entry.jsonSchema,
43
43
  }))
44
- return { verbs, sockets, prompts }
44
+ return { rpcs, sockets, prompts }
45
45
  }
@@ -1,10 +1,10 @@
1
1
  import { carriesBodyArgs } from '../../shared/carriesBodyArgs.ts'
2
2
  import { commandNameForUrl } from '../../shared/commandNameForUrl.ts'
3
3
  import { jsonSchemaForSchema } from '../../shared/jsonSchemaForSchema.ts'
4
- import { verbRegistry } from '../rpc/verbRegistry.ts'
4
+ import { rpcRegistry } from '../rpc/rpcRegistry.ts'
5
5
 
6
6
  /*
7
- Turns a verb's resolved JSON Schema into OpenAPI query parameters — one
7
+ Turns a rpc's resolved JSON Schema into OpenAPI query parameters — one
8
8
  per top-level property, marked required when the schema lists it. Used
9
9
  for GET/DELETE/HEAD operations, which carry their args on the query
10
10
  string (mirroring buildRpcRequest).
@@ -24,7 +24,7 @@ function queryParameters(jsonSchema: Record<string, unknown>): Array<Record<stri
24
24
  }
25
25
 
26
26
  /*
27
- Request body schema for a multipart upload verb: the text fields from
27
+ Request body schema for a multipart upload rpc: the text fields from
28
28
  inputSchema, plus the binary parts. A File has no honest
29
29
  Standard-Schema→JSON-Schema conversion, so the file parts are advertised
30
30
  generically as additional binary properties rather than named per field.
@@ -44,7 +44,7 @@ function multipartBodySchema(textSchema: Record<string, unknown>): Record<string
44
44
  }
45
45
 
46
46
  /*
47
- Builds an OpenAPI 3.1 document from the verb registry — the HTTP surface
47
+ Builds an OpenAPI 3.1 document from the rpc registry — the HTTP surface
48
48
  every rpc exposes regardless of which non-browser clients it advertises.
49
49
  GET/DELETE/HEAD args become query parameters; POST/PUT/PATCH args become
50
50
  a JSON request body. operationId is the folder-prefixed command name so
@@ -55,13 +55,13 @@ export function buildOpenApiSpec(info: {
55
55
  version: string
56
56
  }): Record<string, unknown> {
57
57
  const paths: Record<string, Record<string, unknown>> = {}
58
- for (const entry of verbRegistry.values()) {
58
+ for (const entry of rpcRegistry.values()) {
59
59
  const url = entry.remote.url
60
60
  const method = entry.remote.method
61
61
  const jsonSchema = jsonSchemaForSchema(entry.inputSchema)
62
62
  const description = jsonSchema.description as string | undefined
63
63
  /*
64
- When the verb declares an `outputSchema`, describe the 200 body
64
+ When the rpc declares an `outputSchema`, describe the 200 body
65
65
  with it so external tooling sees the real return shape; otherwise
66
66
  fall back to a bare OK.
67
67
  */
@@ -3,7 +3,7 @@ import { memoizeByKey } from '../../shared/memoizeByKey.ts'
3
3
  import { NAV_HEADER } from '../../shared/NAV_HEADER.ts'
4
4
  import { REMOTE_FUNCTION } from '../../shared/REMOTE_FUNCTION.ts'
5
5
  import { TEXT_PLAIN } from '../../shared/TEXT_PLAIN.ts'
6
- import type { HttpVerb } from '../../shared/types/HttpVerb.ts'
6
+ import type { HttpMethod } from '../../shared/types/HttpMethod.ts'
7
7
  import type { RemoteFunction } from '../../shared/types/RemoteFunction.ts'
8
8
  import type { Pages } from '../../ui/types/Pages.ts'
9
9
  import type { RemoteRoutes } from '../rpc/types/RemoteRoutes.ts'
@@ -26,7 +26,7 @@ type RenderPage = (
26
26
  store: RequestStore,
27
27
  ) => Promise<Response>
28
28
 
29
- /* The framework's 405 — `Allow` names the permitted verb(s), body and NO_STORE shared so the rpc and page branches can't drift. */
29
+ /* The framework's 405 — `Allow` names the permitted rpc(s), body and NO_STORE shared so the rpc and page branches can't drift. */
30
30
  function methodNotAllowed(allow: string): Response {
31
31
  return new Response('Method Not Allowed', {
32
32
  status: 405,
@@ -36,9 +36,9 @@ function methodNotAllowed(allow: string): Response {
36
36
 
37
37
  /*
38
38
  Owns route dispatch: deciding, per registered URL, whether a request hits an
39
- rpc verb, a page render, or nothing — and the method-matching that picks the
39
+ rpc rpc, a page render, or nothing — and the method-matching that picks the
40
40
  status. Page URLs (under src/ui/pages/) serve GET/HEAD by rendering; rpc
41
- URLs (under src/server/rpc/, `/rpc/...`) dispatch to the single declared verb,
41
+ URLs (under src/server/rpc/, `/rpc/...`) dispatch to the single declared rpc,
42
42
  405 on method mismatch; an unregistered URL is 404. Page and rpc URLs are
43
43
  disjoint by construction, so each route lands in exactly one branch.
44
44
 
@@ -66,7 +66,7 @@ export function createRouteDispatcher({
66
66
  Each $rpc module has exactly one named export, validated at build
67
67
  time. Pick the first REMOTE_FUNCTION-branded export — exact, so an
68
68
  incidental re-export carrying method/url props can't be mistaken
69
- for the verb.
69
+ for the rpc.
70
70
  */
71
71
  return loader().then((mod) => {
72
72
  for (const value of Object.values(mod)) {
@@ -82,14 +82,14 @@ export function createRouteDispatcher({
82
82
  const hasPage = pages[routeUrl] !== undefined
83
83
  const hasRpc = rpc[routeUrl] !== undefined
84
84
  return async function routeHandler(req, pathParams, store) {
85
- const method = req.method as HttpVerb
85
+ const method = req.method as HttpMethod
86
86
  if (hasRpc) {
87
87
  const fn = await loadRpc(routeUrl)
88
88
  if (fn && fn.method === method) {
89
89
  const forbidden = crossOriginGate(req, store.url, {
90
90
  allowReadOnly: true,
91
91
  optOut: fn.crossOrigin === true,
92
- hint: 'Declare `crossOrigin: true` on the verb to accept cross-site calls.',
92
+ hint: 'Declare `crossOrigin: true` on the rpc to accept cross-site calls.',
93
93
  })
94
94
  if (forbidden) {
95
95
  return forbidden
@@ -71,7 +71,7 @@ const DEV_REBUILD_PATH = '/__abide/reload'
71
71
  /*
72
72
  Unlike the framework's own plumbing routes above (the socket multiplex, MCP
73
73
  endpoint, CLI download), the OpenAPI document describes the app's public HTTP
74
- surface — the /rpc/* verbs — rather than abide internals, so it sits at the
74
+ surface — the /rpc/* rpcs — rather than abide internals, so it sits at the
75
75
  conventional root path where external tooling and scanners expect to find it
76
76
  (/openapi.json, alongside /swagger.json, /.well-known/*) rather than under the
77
77
  /__abide/ namespace.
@@ -81,7 +81,7 @@ const OPENAPI_PATH = '/openapi.json'
81
81
  /*
82
82
  Starts a Bun HTTP server that ties together the framework conventions:
83
83
  page.abide under src/ui/pages/ for views (layout.abide wraps the pages beneath
84
- it), one named export per file under src/server/rpc/ for verb-bound remote
84
+ it), one named export per file under src/server/rpc/ for rpc-bound remote
85
85
  functions, one named export
86
86
  per file under src/server/sockets/ for broadcast sockets, and an optional
87
87
  app.ts for boot-time setup, request middleware, and error fallback. Page
@@ -120,7 +120,7 @@ export async function createServer({
120
120
  /*
121
121
  Bun's server-wide request body ceiling, enforced natively by Bun.serve
122
122
  (its own default is ~128MB). Surfaced as an option + env so deployments
123
- can raise/lower it; per-verb tightening is the verbs' maxBodySize.
123
+ can raise/lower it; per-rpc tightening is the rpcs' maxBodySize.
124
124
  */
125
125
  maxRequestBodySize = parseBoundedEnvInt(
126
126
  process.env.ABIDE_MAX_REQUEST_BODY_SIZE,
@@ -242,7 +242,7 @@ export async function createServer({
242
242
  isn't installed. Resolved at boot so the fetch route below can branch on it.
243
243
  */
244
244
  const inspectorHandler = await maybeMountInspector({ name: appName, version: appVersion })
245
- /* Built on first request, then reused — the verb registry is frozen after load. */
245
+ /* Built on first request, then reused — the rpc registry is frozen after load. */
246
246
  let openApiSpec: ReturnType<typeof buildOpenApiSpec> | undefined
247
247
  const cliCwd = process.cwd()
248
248
 
@@ -4,7 +4,7 @@ import { TEXT_PLAIN } from '../../shared/TEXT_PLAIN.ts'
4
4
  /*
5
5
  The framework's CSRF refusal, shared by every same-origin gate — the socket
6
6
  upgrade, the socket REST publish face, the MCP endpoint, and mutating rpc
7
- verbs — so the 403 body can't drift between surfaces. `hint` appends a
7
+ rpcs — so the 403 body can't drift between surfaces. `hint` appends a
8
8
  surface-specific remedy; rpc names its `crossOrigin: true` opt-out so the
9
9
  first developer this 403s can self-serve.
10
10
  */
@@ -1,18 +1,18 @@
1
1
  import { isReadOnlyMethod } from '../../shared/isReadOnlyMethod.ts'
2
- import type { HttpVerb } from '../../shared/types/HttpVerb.ts'
2
+ import type { HttpMethod } from '../../shared/types/HttpMethod.ts'
3
3
  import { crossOriginForbidden } from './crossOriginForbidden.ts'
4
4
  import { isCrossOriginRequest } from './isCrossOriginRequest.ts'
5
5
 
6
6
  /*
7
7
  The framework's CSRF/CSWSH posture in one place: a cross-origin browser
8
8
  request to a mutating framework endpoint is refused. Every endpoint that
9
- parses bodies ignoring Content-Type (rpc verbs, socket publish, MCP JSON-RPC)
9
+ parses bodies ignoring Content-Type (rpc rpcs, socket publish, MCP JSON-RPC)
10
10
  must gate here — a hostile page's text/plain form trick could otherwise
11
11
  smuggle a payload in with the visitor's ambient cookies; non-browser clients
12
12
  send no Origin and pass. Returns the 403 to send, or undefined to proceed.
13
13
  Mount sites declare their variation: `allowReadOnly` lets GET/HEAD reads
14
14
  through (rpc reads and socket tails stay open cross-origin), `optOut` honours
15
- a verb's explicit `crossOrigin: true`, `hint` names the remedy in the 403.
15
+ a rpc's explicit `crossOrigin: true`, `hint` names the remedy in the 403.
16
16
  */
17
17
  export function crossOriginGate(
18
18
  req: Request,
@@ -22,7 +22,7 @@ export function crossOriginGate(
22
22
  if (options.optOut) {
23
23
  return undefined
24
24
  }
25
- if (options.allowReadOnly && isReadOnlyMethod(req.method as HttpVerb)) {
25
+ if (options.allowReadOnly && isReadOnlyMethod(req.method as HttpMethod)) {
26
26
  return undefined
27
27
  }
28
28
  return isCrossOriginRequest(req, url) ? crossOriginForbidden(options.hint) : undefined
@@ -1,4 +1,5 @@
1
1
  import { relative } from 'node:path'
2
+ import { fileName } from '../../shared/fileName.ts'
2
3
  import { analyzeComponent } from '../../ui/compile/analyzeComponent.ts'
3
4
  import { compileComponent } from '../../ui/compile/compileComponent.ts'
4
5
  import { nearestProjectRoot } from '../../ui/compile/nearestProjectRoot.ts'
@@ -19,7 +20,7 @@ const GENERATED = /(^|\/)\.abide\//
19
20
  // page.abide / layout.abide are router-mounted, not `mountChild`-tracked, so they
20
21
  // can't hot-swap — they fold into `structure` (a reload) instead.
21
22
  function isPageOrLayout(moduleId: string): boolean {
22
- const file = moduleId.split('/').pop() ?? ''
23
+ const file = fileName(moduleId)
23
24
  return file === 'page.abide' || file === 'layout.abide'
24
25
  }
25
26
 
@@ -1,6 +1,6 @@
1
1
  import { abideLog } from '../../shared/abideLog.ts'
2
2
  import type { Pages } from '../../ui/types/Pages.ts'
3
- import { verbRegistry } from '../rpc/verbRegistry.ts'
3
+ import { rpcRegistry } from '../rpc/rpcRegistry.ts'
4
4
  import { socketRegistry } from '../sockets/socketRegistry.ts'
5
5
  import { ensureRegistriesLoaded } from './registryManifests.ts'
6
6
 
@@ -15,7 +15,7 @@ const redden = (text: string): string =>
15
15
  hasColor ? `${Bun.color('red', 'ansi-256')}${text}\x1b[39m` : text
16
16
 
17
17
  /*
18
- A declared inputSchema is what makes mcp/cli safe to advertise (see defineVerb /
18
+ A declared inputSchema is what makes mcp/cli safe to advertise (see defineRpc /
19
19
  defineSocket), so a missing schema gets a red `·` to flag the declaration whose
20
20
  machine surfaces are gated behind it.
21
21
  */
@@ -127,12 +127,12 @@ export async function logExposedSurfaces(routing: { pages: Pages }): Promise<voi
127
127
  */
128
128
  const methodWidth = Math.max(
129
129
  'http'.length,
130
- ...Array.from(verbRegistry.values(), (entry) => entry.remote.method.length),
130
+ ...Array.from(rpcRegistry.values(), (entry) => entry.remote.method.length),
131
131
  )
132
132
  const withMethod = (method: string, identifier: string): string =>
133
133
  method.padEnd(methodWidth + COLUMN_GAP) + identifier
134
134
 
135
- const rpcRows = Array.from(verbRegistry.values(), (entry) => [
135
+ const rpcRows = Array.from(rpcRegistry.values(), (entry) => [
136
136
  withMethod(entry.remote.method, entry.remote.url),
137
137
  schemaCell(Boolean(entry.inputSchema)),
138
138
  flag(entry.clients.browser),
@@ -7,7 +7,7 @@ Process-wide slot for the rpc + sockets + prompts manifests. createServer
7
7
  assigns once at boot (right after the route table is built); the MCP
8
8
  server, the OpenAPI emitter, and prompt enumeration read it on first
9
9
  request so they can lazy-import every module and walk the
10
- verb/socket/prompt registries.
10
+ rpc/socket/prompt registries.
11
11
 
12
12
  The slot pattern (mirrors getActiveServer) lets the framework-generated
13
13
  McpServer bind to the manifests at module scope while the loaders stay
@@ -29,7 +29,7 @@ export function setRegistryManifests(value: RegistryManifests): void {
29
29
 
30
30
  /*
31
31
  On first call, eagerly imports every rpc + socket + prompt module so
32
- defineVerb / defineSocket / definePrompt fire and populate the
32
+ defineRpc / defineSocket / definePrompt fire and populate the
33
33
  registries. Idempotent — repeat calls reuse the same in-flight promise,
34
34
  so concurrent first requests (e.g. /openapi.json + an MCP tools/list)
35
35
  trigger exactly one load instead of racing to fire the full import set
@@ -9,7 +9,7 @@ export type InspectorCacheEntry = {
9
9
  key: string
10
10
  /* Lifecycle: 'settled' | 'in-flight' | 'refreshing'. */
11
11
  status: string
12
- /* True when the entry stores a wire Response (a remote verb), false for a plain producer value. */
12
+ /* True when the entry stores a wire Response (a remote rpc), false for a plain producer value. */
13
13
  remote: boolean
14
14
  /* Retention ttl in ms; undefined = forever, 0 = dedupe-only. */
15
15
  ttl: number | undefined
@@ -0,0 +1,27 @@
1
+ /*
2
+ One RPC rpc projected for the inspector: the registry entry reduced to the
3
+ serializable facts the UI renders — where it mounts, its method, which
4
+ non-browser surfaces advertise it, and its argument/result shapes as JSON
5
+ Schema. Schemas project through jsonSchemaForSchema (same as MCP/OpenAPI), so
6
+ a rpc whose library can't render a schema still lists with an opaque shape.
7
+ */
8
+ export type InspectorRpc = {
9
+ /* Registry key — the rpc URL the rpc mounts at (e.g. /rpc/users/create). */
10
+ url: string
11
+ /* HTTP method bound to the rpc. */
12
+ method: string
13
+ /* Which surfaces advertise it (browser/mcp/cli), from the rpc's ClientFlags. */
14
+ clients: Record<string, boolean>
15
+ /* Argument-bag shape as JSON Schema; undefined when the rpc declares none. */
16
+ inputSchema: Record<string, unknown> | undefined
17
+ /* Success-body shape as JSON Schema; undefined when the rpc declares none. */
18
+ outputSchema: Record<string, unknown> | undefined
19
+ /* True when the rpc declares a filesSchema — it accepts multipart File parts. */
20
+ files: boolean
21
+ /* Per-rpc handler deadline in ms; undefined = no deadline. */
22
+ timeout: number | undefined
23
+ /* Per-rpc received-body cap in bytes; undefined = Bun's server-wide ceiling. */
24
+ maxBodySize: number | undefined
25
+ /* True when the rpc opts out of the same-origin CSRF gate. */
26
+ crossOrigin: boolean | undefined
27
+ }
@@ -1,16 +1,16 @@
1
1
  import type { InspectorPrompt } from './InspectorPrompt.ts'
2
+ import type { InspectorRpc } from './InspectorRpc.ts'
2
3
  import type { InspectorSocket } from './InspectorSocket.ts'
3
- import type { InspectorVerb } from './InspectorVerb.ts'
4
4
 
5
5
  /*
6
6
  The app's machine surface projected for the inspector — the static catalog the
7
- UI renders. Built by reading the verb, socket, and prompt registries after
7
+ UI renders. Built by reading the rpc, socket, and prompt registries after
8
8
  they're eager-loaded, so a freshly-booted server lists its whole surface, not
9
- just the verbs hit so far. Pages stay out: they're the human surface, already
9
+ just the rpcs hit so far. Pages stay out: they're the human surface, already
10
10
  navigable.
11
11
  */
12
12
  export type InspectorSurface = {
13
- verbs: InspectorVerb[]
13
+ rpcs: InspectorRpc[]
14
14
  sockets: InspectorSocket[]
15
15
  prompts: InspectorPrompt[]
16
16
  }
@@ -3,7 +3,7 @@ import type { TraceContext } from '../../../shared/types/TraceContext.ts'
3
3
 
4
4
  /*
5
5
  Per-request state propagated through AsyncLocalStorage. Every field is
6
- populated once at the server's fetch boundary; helpers and verb-defined
6
+ populated once at the server's fetch boundary; helpers and rpc-defined
7
7
  remote functions read from it without threading arguments through user code.
8
8
  The inbound request's AbortSignal is reached via `req.signal` rather than a
9
9
  separate field.
@@ -1,5 +1,5 @@
1
1
  import { abideLog } from '../../shared/abideLog.ts'
2
- import { verbRegistry } from '../rpc/verbRegistry.ts'
2
+ import { rpcRegistry } from '../rpc/rpcRegistry.ts'
3
3
  import { socketRegistry } from '../sockets/socketRegistry.ts'
4
4
  import { ensureRegistriesLoaded } from './registryManifests.ts'
5
5
 
@@ -19,7 +19,7 @@ export async function warnUnguardedMcp(): Promise<void> {
19
19
  return
20
20
  }
21
21
  const isMcpExposed = (entry: { clients: { mcp: boolean } }): boolean => entry.clients.mcp
22
- const exposed = [...verbRegistry.values(), ...socketRegistry.values()].filter(
22
+ const exposed = [...rpcRegistry.values(), ...socketRegistry.values()].filter(
23
23
  isMcpExposed,
24
24
  ).length
25
25
  if (exposed === 0) {