@abide/abide 0.42.0 → 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.
- package/AGENTS.md +234 -300
- package/CHANGELOG.md +34 -0
- package/README.md +50 -93
- package/package.json +3 -2
- package/src/abideResolverPlugin.ts +104 -14
- package/src/buildCli.ts +1 -1
- package/src/discoveryEntry.ts +3 -3
- package/src/lib/bundle/BundleMenuItem.ts +1 -1
- package/src/lib/cli/createClient.ts +12 -12
- package/src/lib/cli/dispatchCommand.ts +1 -1
- package/src/lib/cli/printSessionStatus.ts +1 -1
- package/src/lib/cli/resolveCliTarget.ts +1 -1
- package/src/lib/cli/runCli.ts +1 -1
- package/src/lib/cli/types/CliManifest.ts +1 -1
- package/src/lib/cli/types/CliManifestEntry.ts +2 -2
- package/src/lib/mcp/annotationsForMethod.ts +5 -5
- package/src/lib/mcp/createMcpServer.ts +3 -3
- package/src/lib/mcp/mcpSurface.ts +14 -14
- package/src/lib/mcp/types/McpServerOptions.ts +1 -1
- package/src/lib/server/DELETE.ts +4 -4
- package/src/lib/server/GET.ts +4 -4
- package/src/lib/server/HEAD.ts +4 -4
- package/src/lib/server/PATCH.ts +4 -4
- package/src/lib/server/POST.ts +4 -4
- package/src/lib/server/PUT.ts +4 -4
- package/src/lib/server/agent.ts +4 -4
- package/src/lib/server/env.ts +1 -1
- package/src/lib/server/error.ts +49 -7
- package/src/lib/server/json.ts +1 -1
- package/src/lib/server/rpc/buildErrorConstructors.ts +19 -0
- package/src/lib/server/rpc/{defineVerb.ts → defineRpc.ts} +52 -34
- package/src/lib/server/rpc/{dispatchVerbInProcess.ts → dispatchRpcInProcess.ts} +3 -3
- package/src/lib/server/rpc/fieldErrorsFromIssues.ts +23 -0
- package/src/lib/server/rpc/{findVerbByCommandName.ts → findRpcByCommandName.ts} +5 -5
- package/src/lib/server/rpc/parseArgs.ts +6 -6
- package/src/lib/server/rpc/readBodyWithinLimit.ts +2 -2
- package/src/lib/server/rpc/registerRpc.ts +6 -0
- package/src/lib/server/rpc/{verbRegistry.ts → rpcRegistry.ts} +4 -4
- package/src/lib/server/rpc/{runWithVerbTimeout.ts → runWithRpcTimeout.ts} +4 -4
- package/src/lib/server/rpc/types/RemoteHandler.ts +9 -3
- package/src/lib/server/rpc/types/{VerbHelper.ts → RpcHelper.ts} +57 -23
- package/src/lib/server/rpc/types/{VerbRegistryEntry.ts → RpcRegistryEntry.ts} +3 -3
- package/src/lib/server/rpc/types/TypedResponse.ts +2 -2
- package/src/lib/server/rpc/unprocessed.ts +6 -7
- package/src/lib/server/rpc/validationError.ts +17 -0
- package/src/lib/server/runtime/buildInspectorSurface.ts +7 -7
- package/src/lib/server/runtime/buildOpenApiSpec.ts +6 -6
- package/src/lib/server/runtime/createRouteDispatcher.ts +7 -7
- package/src/lib/server/runtime/createServer.ts +4 -4
- package/src/lib/server/runtime/crossOriginForbidden.ts +1 -1
- package/src/lib/server/runtime/crossOriginGate.ts +4 -4
- package/src/lib/server/runtime/logExposedSurfaces.ts +4 -4
- package/src/lib/server/runtime/registryManifests.ts +2 -2
- package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
- package/src/lib/server/runtime/types/InspectorRpc.ts +27 -0
- package/src/lib/server/runtime/types/InspectorSurface.ts +4 -4
- package/src/lib/server/runtime/types/RequestStore.ts +1 -1
- package/src/lib/server/runtime/warnUnguardedMcp.ts +2 -2
- package/src/lib/server/sockets/createSocketDispatcher.ts +15 -14
- package/src/lib/server/sockets/types/SocketOperation.ts +2 -2
- package/src/lib/shared/HttpError.ts +15 -1
- package/src/lib/shared/UNREACHABLE_STATUSES.ts +13 -0
- package/src/lib/shared/buildRpcRequest.ts +5 -5
- package/src/lib/shared/cache.ts +50 -168
- package/src/lib/shared/carriesBodyArgs.ts +4 -4
- package/src/lib/shared/createCacheStore.ts +12 -0
- package/src/lib/shared/createRemoteFunction.ts +11 -6
- package/src/lib/shared/decodeResponse.ts +2 -2
- package/src/lib/shared/detectRpcMethod.ts +17 -0
- package/src/lib/shared/emitLogRecord.ts +3 -3
- package/src/lib/shared/extraForwardHeaders.ts +1 -1
- package/src/lib/shared/findExportCallSite.ts +30 -273
- package/src/lib/shared/forwardHeaders.ts +2 -2
- package/src/lib/shared/httpErrorFor.ts +26 -0
- package/src/lib/shared/isAsciiWhitespace.ts +5 -0
- package/src/lib/shared/isIdentPart.ts +9 -0
- package/src/lib/shared/isIdentStart.ts +8 -0
- package/src/lib/shared/isReadOnlyMethod.ts +4 -4
- package/src/lib/shared/keyForRemoteCall.ts +4 -4
- package/src/lib/shared/log.ts +1 -1
- package/src/lib/shared/outboxProbeSlot.ts +20 -0
- package/src/lib/shared/pending.ts +4 -1
- package/src/lib/shared/prepareRpcModule.ts +96 -14
- package/src/lib/shared/probeRegistries.ts +17 -4
- package/src/lib/shared/queryStringFromArgs.ts +1 -1
- package/src/lib/shared/remoteMetaStore.ts +2 -2
- package/src/lib/shared/resolveClientFlags.ts +1 -1
- package/src/lib/shared/skipNonCode.ts +230 -0
- package/src/lib/shared/streamResponse.ts +9 -6
- package/src/lib/shared/stripImport.ts +3 -3
- package/src/lib/shared/types/CacheEntry.ts +2 -4
- package/src/lib/shared/types/CacheOnContext.ts +1 -11
- package/src/lib/shared/types/CacheStore.ts +13 -7
- package/src/lib/shared/types/ClientFlags.ts +1 -1
- package/src/lib/shared/types/ErrorConstructors.ts +17 -0
- package/src/lib/shared/types/ErrorDescriptor.ts +10 -0
- package/src/lib/shared/types/ErrorSpec.ts +11 -0
- package/src/lib/shared/types/HttpMethod.ts +1 -0
- package/src/lib/shared/types/Outbox.ts +9 -0
- package/src/lib/shared/types/OutboxEntry.ts +27 -0
- package/src/lib/shared/types/RawRemoteFunction.ts +2 -2
- package/src/lib/shared/types/RemoteCallable.ts +2 -2
- package/src/lib/shared/types/RemoteFunction.ts +21 -6
- package/src/lib/shared/types/RequestScopeInfo.ts +1 -1
- package/src/lib/shared/types/RpcErrorGuard.ts +40 -0
- package/src/lib/shared/types/RpcInvoker.ts +1 -1
- package/src/lib/shared/types/StandardSchemaV1.ts +1 -1
- package/src/lib/shared/types/ValidationErrorData.ts +20 -0
- package/src/lib/shared/url.ts +3 -3
- package/src/lib/shared/writeRpcDts.ts +7 -7
- package/src/lib/shared/writeTestRpcDts.ts +3 -3
- package/src/lib/test/createTestApp.ts +10 -10
- package/src/lib/ui/compile/awaitPlan.ts +48 -0
- package/src/lib/ui/compile/compileShadow.ts +75 -21
- package/src/lib/ui/compile/desugarSignals.ts +42 -6
- package/src/lib/ui/compile/generateBuild.ts +43 -60
- package/src/lib/ui/compile/generateSSR.ts +84 -54
- package/src/lib/ui/compile/ifPlan.ts +30 -0
- package/src/lib/ui/compile/parseTemplate.ts +41 -11
- package/src/lib/ui/compile/stripEffects.ts +17 -9
- package/src/lib/ui/compile/switchPlan.ts +22 -0
- package/src/lib/ui/compile/tryPlan.ts +29 -0
- package/src/lib/ui/compile/types/TemplateAttr.ts +8 -2
- package/src/lib/ui/compile/types/TemplateNode.ts +6 -2
- package/src/lib/ui/createScope.ts +20 -2
- package/src/lib/ui/dom/disposeRange.ts +6 -10
- package/src/lib/ui/dom/hydrate.ts +2 -5
- package/src/lib/ui/dom/mount.ts +1 -2
- package/src/lib/ui/dom/withScope.ts +6 -9
- package/src/lib/ui/effect.ts +4 -2
- package/src/lib/ui/navigate.ts +40 -13
- package/src/lib/ui/outbox.ts +30 -110
- package/src/lib/ui/remoteProxy.ts +160 -10
- package/src/lib/ui/router.ts +3 -3
- package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +281 -0
- package/src/lib/ui/rpcOutbox/outboxRegistry.ts +69 -0
- package/src/lib/ui/socketChannel.ts +7 -1
- package/src/lib/ui/types/Scope.ts +20 -5
- package/template/CLAUDE.md +1 -1
- package/template/src/server/rpc/getHello.ts +3 -3
- package/template/test/app.test.ts +2 -2
- package/src/lib/server/rpc/registerVerb.ts +0 -6
- package/src/lib/server/runtime/types/InspectorVerb.ts +0 -27
- package/src/lib/shared/detectVerbMethod.ts +0 -17
- package/src/lib/shared/types/HttpVerb.ts +0 -1
- package/src/lib/ui/types/Outbox.ts +0 -14
package/src/lib/cli/runCli.ts
CHANGED
|
@@ -67,7 +67,7 @@ a bare word runs a command:
|
|
|
67
67
|
/disconnect → forget the saved connection, exit
|
|
68
68
|
<cmd> [--flags] → one-shot RPC against the resumed target
|
|
69
69
|
|
|
70
|
-
The connection
|
|
70
|
+
The connection rpcs are `/`-prefixed only — no bare aliases — so a bare word is
|
|
71
71
|
always an RPC command and never collides. Env layers ABIDE_APP_URL/ABIDE_APP_TOKEN (shell >
|
|
72
72
|
data-dir > binary-dir) supply the baked default a fresh download resumes against.
|
|
73
73
|
*/
|
|
@@ -2,7 +2,7 @@ import type { CliManifestEntry } from './CliManifestEntry.ts'
|
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
4
|
Map from rpc export-name (e.g. "getReport") to its manifest entry. Built
|
|
5
|
-
by the bundler from the same
|
|
5
|
+
by the bundler from the same rpcRegistry MCP consumes; entries are
|
|
6
6
|
emitted only for rpcs with `clients.cli: true`. The CLI binary and any
|
|
7
7
|
programmatic createClient caller read this to dispatch calls.
|
|
8
8
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { HttpMethod } from '../../shared/types/HttpMethod.ts'
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
4
|
Per-command manifest entry baked into the standalone CLI binary by the
|
|
@@ -9,7 +9,7 @@ both rpcs and socket commands — a socket `tail` is a GET against
|
|
|
9
9
|
streams it live; a socket `publish` is a POST to the same path.
|
|
10
10
|
*/
|
|
11
11
|
export type CliManifestEntry = {
|
|
12
|
-
method:
|
|
12
|
+
method: HttpMethod
|
|
13
13
|
url: string
|
|
14
14
|
jsonSchema?: Record<string, unknown>
|
|
15
15
|
// Request Accept header. Socket tail sets text/event-stream to stream live frames.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { HttpMethod } from '../shared/types/HttpMethod.ts'
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
|
-
Maps an HTTP
|
|
5
|
-
a write before calling. Abide derives these from the
|
|
4
|
+
Maps an HTTP method to MCP tool annotations so a model can tell a read from
|
|
5
|
+
a write before calling. Abide derives these from the rpc the RPC was
|
|
6
6
|
declared with rather than asking the author to repeat the intent:
|
|
7
7
|
- GET / HEAD → read-only, non-destructive
|
|
8
8
|
- POST → creates; not idempotent, not (necessarily) destructive
|
|
@@ -10,9 +10,9 @@ declared with rather than asking the author to repeat the intent:
|
|
|
10
10
|
- PATCH → modifies; destructive, not idempotent
|
|
11
11
|
- DELETE → removes; idempotent + destructive
|
|
12
12
|
The shape matches MCP's ToolAnnotations (readOnlyHint / destructiveHint /
|
|
13
|
-
idempotentHint); fields a
|
|
13
|
+
idempotentHint); fields a rpc doesn't imply are left off.
|
|
14
14
|
*/
|
|
15
|
-
export function annotationsForMethod(method:
|
|
15
|
+
export function annotationsForMethod(method: HttpMethod): Record<string, boolean> {
|
|
16
16
|
switch (method) {
|
|
17
17
|
case 'GET':
|
|
18
18
|
case 'HEAD':
|
|
@@ -14,10 +14,10 @@ object whose `handle(request)` is the function the bun route at
|
|
|
14
14
|
default-constructs it; there is no user-authored server module. Server
|
|
15
15
|
name/version default from package.json.
|
|
16
16
|
|
|
17
|
-
Tools are derived from every
|
|
18
|
-
read-only
|
|
17
|
+
Tools are derived from every rpc with `clients.mcp: true` (auto-on for
|
|
18
|
+
read-only rpcs that carry a schema; mutating rpcs opt in explicitly)
|
|
19
19
|
and from every socket with `clients.mcp: true` (a `<base>-tail` read tool
|
|
20
|
-
plus a `<base>-publish` tool when clientPublish is set). The HTTP
|
|
20
|
+
plus a `<base>-publish` tool when clientPublish is set). The HTTP method
|
|
21
21
|
feeds each rpc tool's annotations. Auth inherits from the inbound request
|
|
22
22
|
— bearer / cookie headers flow into the synthesized Request that hits
|
|
23
23
|
each rpc handler. An optional `authorize` hook in opts can short-circuit
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { promptRegistry } from '../server/prompts/promptRegistry.ts'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { dispatchRpcInProcess } from '../server/rpc/dispatchRpcInProcess.ts'
|
|
3
|
+
import { findRpcByCommandName } from '../server/rpc/findRpcByCommandName.ts'
|
|
4
|
+
import { rpcRegistry } from '../server/rpc/rpcRegistry.ts'
|
|
5
5
|
import { socketOperations } from '../server/sockets/socketOperations.ts'
|
|
6
6
|
import { socketRegistry } from '../server/sockets/socketRegistry.ts'
|
|
7
7
|
import { abideLog } from '../shared/abideLog.ts'
|
|
@@ -58,12 +58,12 @@ export type McpSurface = {
|
|
|
58
58
|
/*
|
|
59
59
|
Builds the array of MCP tool descriptors.
|
|
60
60
|
|
|
61
|
-
RPCs: every
|
|
62
|
-
export's URL (folder segments joined with `-`). The HTTP
|
|
61
|
+
RPCs: every rpc with clients.mcp=true becomes one tool named after the
|
|
62
|
+
export's URL (folder segments joined with `-`). The HTTP method feeds the
|
|
63
63
|
tool's annotations (readOnlyHint / destructiveHint / idempotentHint) so
|
|
64
64
|
a model can tell a read from a write; reads auto-expose while mutating
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
rpcs require an explicit clients.mcp (see resolveClientFlags). When the
|
|
66
|
+
rpc declares an `outputSchema` it's advertised as the tool outputSchema.
|
|
67
67
|
|
|
68
68
|
Sockets: every socket with clients.mcp=true contributes a `<base>-tail`
|
|
69
69
|
read tool (recent buffered messages) and, when clientPublish is set, a
|
|
@@ -71,7 +71,7 @@ read tool (recent buffered messages) and, when clientPublish is set, a
|
|
|
71
71
|
*/
|
|
72
72
|
export function buildTools(): ToolDescriptor[] {
|
|
73
73
|
const tools: ToolDescriptor[] = []
|
|
74
|
-
for (const entry of
|
|
74
|
+
for (const entry of rpcRegistry.values()) {
|
|
75
75
|
if (!entry.clients.mcp) {
|
|
76
76
|
continue
|
|
77
77
|
}
|
|
@@ -154,7 +154,7 @@ export function buildPrompts(): PromptDescriptor[] {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
/* MCP tool-dispatch spans, opt-in via DEBUG=abide:mcp — a model's tool call,
|
|
157
|
-
wrapping the underlying
|
|
157
|
+
wrapping the underlying rpc dispatch in the same trace. */
|
|
158
158
|
const mcpLog = abideLog.channel('abide:mcp')
|
|
159
159
|
|
|
160
160
|
function textResult(text: string, isError = false): ToolResult {
|
|
@@ -203,7 +203,7 @@ function callSocketTool(
|
|
|
203
203
|
|
|
204
204
|
/*
|
|
205
205
|
Tool dispatch. RPC tools synthesize a Request (with forwarded auth
|
|
206
|
-
headers from `inbound`) and pipe it through
|
|
206
|
+
headers from `inbound`) and pipe it through rpc.fetch inside the request
|
|
207
207
|
scope — the same seam the HTTP router crosses, so validation, the handler,
|
|
208
208
|
and the request-scoped helpers (per-call cache(), cookies(), request())
|
|
209
209
|
behave identically. A handler throw is caught by the scope and framed as
|
|
@@ -217,14 +217,14 @@ export async function callTool(
|
|
|
217
217
|
args: Record<string, unknown> | undefined,
|
|
218
218
|
inbound: Request,
|
|
219
219
|
): Promise<ToolResult> {
|
|
220
|
-
const entry =
|
|
220
|
+
const entry = findRpcByCommandName(toolName)
|
|
221
221
|
if (entry) {
|
|
222
|
-
/* A
|
|
222
|
+
/* A rpc owns this name. If it isn't mcp-exposed it's still unavailable —
|
|
223
223
|
don't fall through to a socket op that happens to share the name. */
|
|
224
224
|
if (!entry.clients.mcp) {
|
|
225
225
|
throw new Error(`unknown tool: ${toolName}`)
|
|
226
226
|
}
|
|
227
|
-
const response = await
|
|
227
|
+
const response = await dispatchRpcInProcess({
|
|
228
228
|
remote: entry.remote,
|
|
229
229
|
args,
|
|
230
230
|
baseUrl: `${new URL(inbound.url).origin}/`,
|
|
@@ -264,7 +264,7 @@ export function renderPrompt(
|
|
|
264
264
|
|
|
265
265
|
/*
|
|
266
266
|
Projects the app's MCP surface for an in-process consumer bound to `request`
|
|
267
|
-
— tool calls forward that request's auth headers into the
|
|
267
|
+
— tool calls forward that request's auth headers into the rpc handler, so
|
|
268
268
|
the model acts with the caller's identity. Used by `agent()`.
|
|
269
269
|
*/
|
|
270
270
|
export function mcpSurface(request: Request): McpSurface {
|
|
@@ -7,7 +7,7 @@ package.json by the bundler when MCP is wired into createServer).
|
|
|
7
7
|
response. Defaults come from the project's package.json.
|
|
8
8
|
- `authorize`: optional boundary check. Runs once per MCP request before
|
|
9
9
|
any tool/resource dispatch. Throw HttpError (or any Error) to reject.
|
|
10
|
-
Per-tool authorization stays in the underlying
|
|
10
|
+
Per-tool authorization stays in the underlying rpc handler.
|
|
11
11
|
*/
|
|
12
12
|
export type McpServerOptions = {
|
|
13
13
|
name?: string
|
package/src/lib/server/DELETE.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MutatingRpcHelper } from './rpc/types/RpcHelper.ts'
|
|
2
2
|
import { unprocessed } from './rpc/unprocessed.ts'
|
|
3
3
|
|
|
4
4
|
/*
|
|
5
|
-
DELETE
|
|
6
|
-
`src/server/rpc/<file>.ts` into a
|
|
5
|
+
DELETE rpc helper. The bundler rewrites every `export const x = DELETE(fn)` inside
|
|
6
|
+
`src/server/rpc/<file>.ts` into a defineRpc call (server target) or a
|
|
7
7
|
remoteProxy stub (client target). Calling this directly throws.
|
|
8
8
|
*/
|
|
9
9
|
// @documentation rpc
|
|
10
|
-
export const DELETE:
|
|
10
|
+
export const DELETE: MutatingRpcHelper = (_fn: any, _opts?: any) => unprocessed('DELETE')
|
package/src/lib/server/GET.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RpcHelper } from './rpc/types/RpcHelper.ts'
|
|
2
2
|
import { unprocessed } from './rpc/unprocessed.ts'
|
|
3
3
|
|
|
4
4
|
/*
|
|
5
|
-
GET
|
|
6
|
-
`src/server/rpc/<file>.ts` into a
|
|
5
|
+
GET rpc helper. The bundler rewrites every `export const x = GET(fn)` inside
|
|
6
|
+
`src/server/rpc/<file>.ts` into a defineRpc call (server target) or a
|
|
7
7
|
remoteProxy stub (client target). Calling this directly throws.
|
|
8
8
|
*/
|
|
9
9
|
// @documentation rpc
|
|
10
|
-
export const GET:
|
|
10
|
+
export const GET: RpcHelper = (_fn: any, _opts?: any) => unprocessed('GET')
|
package/src/lib/server/HEAD.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RpcHelper } from './rpc/types/RpcHelper.ts'
|
|
2
2
|
import { unprocessed } from './rpc/unprocessed.ts'
|
|
3
3
|
|
|
4
4
|
/*
|
|
5
|
-
HEAD
|
|
6
|
-
`src/server/rpc/<file>.ts` into a
|
|
5
|
+
HEAD rpc helper. The bundler rewrites every `export const x = HEAD(fn)` inside
|
|
6
|
+
`src/server/rpc/<file>.ts` into a defineRpc call (server target) or a
|
|
7
7
|
remoteProxy stub (client target). Calling this directly throws.
|
|
8
8
|
*/
|
|
9
9
|
// @documentation rpc
|
|
10
|
-
export const HEAD:
|
|
10
|
+
export const HEAD: RpcHelper = (_fn: any, _opts?: any) => unprocessed('HEAD')
|
package/src/lib/server/PATCH.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MutatingRpcHelper } from './rpc/types/RpcHelper.ts'
|
|
2
2
|
import { unprocessed } from './rpc/unprocessed.ts'
|
|
3
3
|
|
|
4
4
|
/*
|
|
5
|
-
PATCH
|
|
6
|
-
`src/server/rpc/<file>.ts` into a
|
|
5
|
+
PATCH rpc helper. The bundler rewrites every `export const x = PATCH(fn)` inside
|
|
6
|
+
`src/server/rpc/<file>.ts` into a defineRpc call (server target) or a
|
|
7
7
|
remoteProxy stub (client target). Calling this directly throws.
|
|
8
8
|
*/
|
|
9
9
|
// @documentation rpc
|
|
10
|
-
export const PATCH:
|
|
10
|
+
export const PATCH: MutatingRpcHelper = (_fn: any, _opts?: any) => unprocessed('PATCH')
|
package/src/lib/server/POST.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MutatingRpcHelper } from './rpc/types/RpcHelper.ts'
|
|
2
2
|
import { unprocessed } from './rpc/unprocessed.ts'
|
|
3
3
|
|
|
4
4
|
/*
|
|
5
|
-
POST
|
|
6
|
-
`src/server/rpc/<file>.ts` into a
|
|
5
|
+
POST rpc helper. The bundler rewrites every `export const x = POST(fn)` inside
|
|
6
|
+
`src/server/rpc/<file>.ts` into a defineRpc call (server target) or a
|
|
7
7
|
remoteProxy stub (client target). Calling this directly throws.
|
|
8
8
|
*/
|
|
9
9
|
// @documentation rpc
|
|
10
|
-
export const POST:
|
|
10
|
+
export const POST: MutatingRpcHelper = (_fn: any, _opts?: any) => unprocessed('POST')
|
package/src/lib/server/PUT.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MutatingRpcHelper } from './rpc/types/RpcHelper.ts'
|
|
2
2
|
import { unprocessed } from './rpc/unprocessed.ts'
|
|
3
3
|
|
|
4
4
|
/*
|
|
5
|
-
PUT
|
|
6
|
-
`src/server/rpc/<file>.ts` into a
|
|
5
|
+
PUT rpc helper. The bundler rewrites every `export const x = PUT(fn)` inside
|
|
6
|
+
`src/server/rpc/<file>.ts` into a defineRpc call (server target) or a
|
|
7
7
|
remoteProxy stub (client target). Calling this directly throws.
|
|
8
8
|
*/
|
|
9
9
|
// @documentation rpc
|
|
10
|
-
export const PUT:
|
|
10
|
+
export const PUT: MutatingRpcHelper = (_fn: any, _opts?: any) => unprocessed('PUT')
|
package/src/lib/server/agent.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { request } from './request.ts'
|
|
|
6
6
|
The in-app agent surface. `agent(engine, messages)` runs a model engine
|
|
7
7
|
against the app's own MCP surface and returns the engine's frame stream —
|
|
8
8
|
it does NOT pick a transport. The handler wraps it in `jsonl()` or `sse()`,
|
|
9
|
-
so consumption is the app's choice, same as any other streaming
|
|
9
|
+
so consumption is the app's choice, same as any other streaming rpc:
|
|
10
10
|
|
|
11
11
|
// src/server/rpc/chat.ts
|
|
12
12
|
import { agent } from '@abide/abide/server/agent'
|
|
@@ -18,10 +18,10 @@ so consumption is the app's choice, same as any other streaming verb:
|
|
|
18
18
|
|
|
19
19
|
The engine — provider-specific, lives in a `@abide/<provider>` package —
|
|
20
20
|
only sees the surface in and yields frames out, so swapping providers never
|
|
21
|
-
touches the
|
|
21
|
+
touches the rpc or the UI.
|
|
22
22
|
|
|
23
23
|
Permission is decided server-side, not negotiated at runtime: the surface
|
|
24
|
-
is already gated by each
|
|
24
|
+
is already gated by each rpc's `clients.mcp` declaration plus its own
|
|
25
25
|
per-call handler auth, and any provider built-ins (e.g. Claude Code's bash
|
|
26
26
|
tool) are fenced by static rules in the engine's config.
|
|
27
27
|
*/
|
|
@@ -71,7 +71,7 @@ export type AgentEngine = (input: {
|
|
|
71
71
|
|
|
72
72
|
/*
|
|
73
73
|
Runs an engine against the current request's MCP surface and returns its
|
|
74
|
-
AgentFrame stream. Must be called inside a
|
|
74
|
+
AgentFrame stream. Must be called inside a rpc's request scope —
|
|
75
75
|
mcpSurface() forwards the caller's auth into every tool dispatch. The
|
|
76
76
|
handler chooses the transport: `jsonl(agent(engine, messages))` or
|
|
77
77
|
`sse(agent(engine, messages))`.
|
package/src/lib/server/env.ts
CHANGED
|
@@ -14,7 +14,7 @@ deep inside a handler:
|
|
|
14
14
|
|
|
15
15
|
Reads `Bun.env` (the process environment plus any `.env` Bun loaded), so any
|
|
16
16
|
Standard Schema library — zod, valibot, arktype — works without an adapter,
|
|
17
|
-
same as the
|
|
17
|
+
same as the rpc helpers. Coercion (e.g. a numeric PORT) lives in the schema.
|
|
18
18
|
|
|
19
19
|
The schema is registered (envSchemaStore) so the bundle launcher can project
|
|
20
20
|
the first-run setup form from the same declaration. When the launcher imports
|
package/src/lib/server/error.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NO_STORE } from '../shared/CACHE_CONTROL_VALUES.ts'
|
|
2
2
|
import { TEXT_PLAIN } from '../shared/TEXT_PLAIN.ts'
|
|
3
|
+
import type { ErrorDescriptor } from '../shared/types/ErrorDescriptor.ts'
|
|
3
4
|
import type { TypedResponse } from './rpc/types/TypedResponse.ts'
|
|
4
5
|
import { withResponseDefaults } from './runtime/withResponseDefaults.ts'
|
|
5
6
|
|
|
@@ -57,17 +58,58 @@ branch carries (`TypedResponse<{user}> | TypedResponse<never>` → Return
|
|
|
57
58
|
= {user}).
|
|
58
59
|
*/
|
|
59
60
|
// @documentation response
|
|
60
|
-
export function error(
|
|
61
|
+
export function error(descriptor: ErrorDescriptor): TypedResponse<never>
|
|
62
|
+
export function error(status: number, message?: string, init?: ResponseInit): TypedResponse<never>
|
|
63
|
+
export function error(
|
|
64
|
+
statusOrDescriptor: number | ErrorDescriptor,
|
|
65
|
+
message?: string,
|
|
66
|
+
init?: ResponseInit,
|
|
67
|
+
): TypedResponse<never> {
|
|
68
|
+
/* A typed-error descriptor (`error(errors.invalidCoupon({…}))`) serializes as a
|
|
69
|
+
JSON `{ $abideError, data }` body at the descriptor's status — the client
|
|
70
|
+
parses it back onto the thrown HttpError's `.kind` / `.data`. */
|
|
71
|
+
if (typeof statusOrDescriptor === 'object') {
|
|
72
|
+
const descriptor = statusOrDescriptor
|
|
73
|
+
return new Response(
|
|
74
|
+
JSON.stringify({ $abideError: descriptor.$abideError, data: descriptor.data }),
|
|
75
|
+
withStatusText(
|
|
76
|
+
withResponseDefaults(
|
|
77
|
+
undefined,
|
|
78
|
+
{ 'Content-Type': 'application/json', 'Cache-Control': NO_STORE },
|
|
79
|
+
descriptor.status,
|
|
80
|
+
),
|
|
81
|
+
descriptor.status,
|
|
82
|
+
),
|
|
83
|
+
) as TypedResponse<never>
|
|
84
|
+
}
|
|
85
|
+
const status = statusOrDescriptor
|
|
61
86
|
const body = message ?? STATUS_TEXT[status] ?? `HTTP ${status}`
|
|
62
87
|
return new Response(
|
|
63
88
|
body,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
89
|
+
withStatusText(
|
|
90
|
+
withResponseDefaults(
|
|
91
|
+
init,
|
|
92
|
+
{
|
|
93
|
+
'Content-Type': TEXT_PLAIN,
|
|
94
|
+
'Cache-Control': NO_STORE,
|
|
95
|
+
},
|
|
96
|
+
status,
|
|
97
|
+
),
|
|
70
98
|
status,
|
|
71
99
|
),
|
|
72
100
|
) as TypedResponse<never>
|
|
73
101
|
}
|
|
102
|
+
|
|
103
|
+
/*
|
|
104
|
+
Stamps the status's standard reason phrase onto `init.statusText` so the wire
|
|
105
|
+
status line reads `404 Not Found` and the client's `HttpError.statusText` is
|
|
106
|
+
populated — Bun's `Response` never derives statusText from the code. A caller's
|
|
107
|
+
explicit `init.statusText` (preserved through withResponseDefaults) still wins;
|
|
108
|
+
an unlisted code leaves it untouched.
|
|
109
|
+
*/
|
|
110
|
+
function withStatusText(init: ResponseInit, status: number): ResponseInit {
|
|
111
|
+
if (init.statusText === undefined && STATUS_TEXT[status] !== undefined) {
|
|
112
|
+
return { ...init, statusText: STATUS_TEXT[status] }
|
|
113
|
+
}
|
|
114
|
+
return init
|
|
115
|
+
}
|
package/src/lib/server/json.ts
CHANGED
|
@@ -13,7 +13,7 @@ own per-request cache handles in-process dedupe.
|
|
|
13
13
|
json(await db.getOrder(id)),
|
|
14
14
|
)
|
|
15
15
|
|
|
16
|
-
The return type carries `T` as a phantom brand so the
|
|
16
|
+
The return type carries `T` as a phantom brand so the rpc helper can
|
|
17
17
|
infer the caller-facing `Return` from the handler body — no need to
|
|
18
18
|
annotate `GET<Args, Return>` just to type the response shape.
|
|
19
19
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ErrorConstructors } from '../../shared/types/ErrorConstructors.ts'
|
|
2
|
+
import type { ErrorSpec } from '../../shared/types/ErrorSpec.ts'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
Turns a rpc's declared `ErrorSpec` into the constructor object handed to the
|
|
6
|
+
handler (`(args, { errors })`). Each constructor stamps its name + status onto an
|
|
7
|
+
`ErrorDescriptor` carrying the call's `data`, which `error()` serializes as the
|
|
8
|
+
`{ $abideError, data }` body. Receiver-agnostic on data: a nullary error ignores
|
|
9
|
+
the (absent) argument.
|
|
10
|
+
*/
|
|
11
|
+
export function buildErrorConstructors<Spec extends ErrorSpec>(
|
|
12
|
+
spec: Spec,
|
|
13
|
+
): ErrorConstructors<Spec> {
|
|
14
|
+
const entries = Object.entries(spec).map(([name, { status }]) => [
|
|
15
|
+
name,
|
|
16
|
+
(data: unknown) => ({ $abideError: name, status, data }),
|
|
17
|
+
])
|
|
18
|
+
return Object.fromEntries(entries) as ErrorConstructors<Spec>
|
|
19
|
+
}
|
|
@@ -5,34 +5,36 @@ import { forwardHeaders } from '../../shared/forwardHeaders.ts'
|
|
|
5
5
|
import { isReadOnlyMethod } from '../../shared/isReadOnlyMethod.ts'
|
|
6
6
|
import { resolveClientFlags } from '../../shared/resolveClientFlags.ts'
|
|
7
7
|
import type { ClientFlags } from '../../shared/types/ClientFlags.ts'
|
|
8
|
-
import type {
|
|
8
|
+
import type { ErrorSpec } from '../../shared/types/ErrorSpec.ts'
|
|
9
|
+
import type { HttpMethod } from '../../shared/types/HttpMethod.ts'
|
|
9
10
|
import type { RemoteFunction } from '../../shared/types/RemoteFunction.ts'
|
|
10
11
|
import type { StandardSchemaV1 } from '../../shared/types/StandardSchemaV1.ts'
|
|
11
|
-
import { json } from '../json.ts'
|
|
12
12
|
import { requestContext } from '../runtime/requestContext.ts'
|
|
13
|
+
import { buildErrorConstructors } from './buildErrorConstructors.ts'
|
|
13
14
|
import { parseArgs } from './parseArgs.ts'
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
15
|
+
import { registerRpc } from './registerRpc.ts'
|
|
16
|
+
import { runWithRpcTimeout } from './runWithRpcTimeout.ts'
|
|
16
17
|
import type { RemoteHandler } from './types/RemoteHandler.ts'
|
|
18
|
+
import { validationError } from './validationError.ts'
|
|
17
19
|
|
|
18
20
|
/*
|
|
19
|
-
Stash for the per-request AbortController a timed
|
|
21
|
+
Stash for the per-request AbortController a timed rpc composes into the
|
|
20
22
|
inbound signal — read back in invoke's deadline callback to fire it. Lives on
|
|
21
|
-
the scope's Request (one
|
|
23
|
+
the scope's Request (one rpc per .fetch — network or in-process dispatch) so
|
|
22
24
|
an SSR pass's many in-process cache reads, which call invoke() directly and
|
|
23
25
|
never reach parseArgsForFetch, can't cross-cancel.
|
|
24
26
|
*/
|
|
25
|
-
const
|
|
27
|
+
const RPC_TIMEOUT_ABORT = Symbol('abideRpcTimeoutAbort')
|
|
26
28
|
|
|
27
|
-
/*
|
|
29
|
+
/* Rpc dispatch + validation spans, opt-in via DEBUG=abide:rpc. Reveals an
|
|
28
30
|
in-process RPC→RPC call (same request scope, same trace) as a nested span. */
|
|
29
31
|
const rpcLog = abideLog.channel('abide:rpc')
|
|
30
32
|
|
|
31
33
|
/*
|
|
32
|
-
Builds a RemoteFunction from an HTTP
|
|
33
|
-
rewrites every `export const
|
|
34
|
-
so the
|
|
35
|
-
`src/server/rpc/`, with `/rpc/` prefix) are threaded into
|
|
34
|
+
Builds a RemoteFunction from an HTTP method + RPC URL + handler. The bundler
|
|
35
|
+
rewrites every `export const METHOD = handler(fn)` inside an `$rpc/**` module
|
|
36
|
+
so the rpc (from the export name) and the URL (from the file path under
|
|
37
|
+
`src/server/rpc/`, with `/rpc/` prefix) are threaded into defineRpc.
|
|
36
38
|
|
|
37
39
|
The plain call (`fn(args)`) resolves to the Content-Type-decoded body;
|
|
38
40
|
non-2xx responses throw HttpError. `.raw(args)` returns the underlying
|
|
@@ -45,32 +47,48 @@ Every raw invocation records the synthesized Request against the returned
|
|
|
45
47
|
promise so cache() can stash it on the entry without re-building.
|
|
46
48
|
*/
|
|
47
49
|
// @documentation plumbing
|
|
48
|
-
export function
|
|
49
|
-
method:
|
|
50
|
+
export function defineRpc<Args, Return>(
|
|
51
|
+
method: HttpMethod,
|
|
50
52
|
url: string,
|
|
51
53
|
handler: RemoteHandler<Args, Return>,
|
|
52
54
|
opts?: {
|
|
53
55
|
inputSchema?: StandardSchemaV1
|
|
54
56
|
outputSchema?: StandardSchemaV1
|
|
55
57
|
filesSchema?: StandardSchemaV1
|
|
58
|
+
errors?: ErrorSpec
|
|
56
59
|
clients?: Partial<ClientFlags>
|
|
57
60
|
crossOrigin?: boolean
|
|
58
|
-
/* Per-
|
|
61
|
+
/* Per-rpc cap on actual received body bytes (413 past it); omitted = Bun's server-wide maxRequestBodySize. */
|
|
59
62
|
maxBodySize?: number
|
|
60
|
-
/* Per-
|
|
63
|
+
/* Per-rpc handler deadline (ms): a 504 once exceeded, on every surface (SSR/MCP/CLI/network). */
|
|
61
64
|
timeout?: number
|
|
65
|
+
/* Durable delivery: on an unreachable server (transport failure / 502/503/504/52x)
|
|
66
|
+
the client proxy parks the request for replay instead of just throwing. The
|
|
67
|
+
parked write drains on `rpc.outbox.retry()` (no auto-drain). Mutating methods
|
|
68
|
+
only. */
|
|
69
|
+
outbox?: boolean
|
|
62
70
|
},
|
|
63
71
|
): RemoteFunction<Args, Return> {
|
|
72
|
+
/* `outbox: true` is a mutation contract — a read RPC has nothing to durably deliver,
|
|
73
|
+
so reject it at declaration rather than silently ignore. The server handler itself
|
|
74
|
+
is unaffected; durability is a client-proxy concern. */
|
|
75
|
+
if (opts?.outbox === true && isReadOnlyMethod(method)) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`[abide] outbox: true is only valid on mutating RPCs (POST/PUT/PATCH/DELETE), not ${method}`,
|
|
78
|
+
)
|
|
79
|
+
}
|
|
64
80
|
const timeout = opts?.timeout
|
|
65
81
|
const inputSchema = opts?.inputSchema
|
|
66
82
|
const outputSchema = opts?.outputSchema
|
|
67
83
|
const filesSchema = opts?.filesSchema
|
|
84
|
+
/* The declared error constructors handed to the handler as its `{ errors }` ctx. */
|
|
85
|
+
const errors = buildErrorConstructors(opts?.errors ?? {})
|
|
68
86
|
/*
|
|
69
87
|
An input schema makes the handler safe to advertise to non-browser
|
|
70
|
-
surfaces. CLI flips on for any
|
|
71
|
-
deliberately). MCP only auto-exposes read-only
|
|
88
|
+
surfaces. CLI flips on for any rpc with one (a human/script invokes it
|
|
89
|
+
deliberately). MCP only auto-exposes read-only rpcs (GET/HEAD) — a
|
|
72
90
|
model shouldn't be able to mutate/delete just because the handler
|
|
73
|
-
carries a schema, so mutating
|
|
91
|
+
carries a schema, so mutating rpcs require an explicit clients.mcp.
|
|
74
92
|
Explicit `clients` always wins.
|
|
75
93
|
*/
|
|
76
94
|
const hasSchema = inputSchema !== undefined
|
|
@@ -97,13 +115,13 @@ export function defineVerb<Args, Return>(
|
|
|
97
115
|
async function runHandler(args: Args | undefined): Promise<Response> {
|
|
98
116
|
return rpcLog.trace(
|
|
99
117
|
`rpc ${method} ${url}`,
|
|
100
|
-
() => handler(args as Args) as unknown as Response,
|
|
118
|
+
() => handler(args as Args, { errors }) as unknown as Response,
|
|
101
119
|
)
|
|
102
120
|
}
|
|
103
121
|
|
|
104
122
|
/*
|
|
105
123
|
Validates the parsed args against inputSchema (text fields), then — when the
|
|
106
|
-
|
|
124
|
+
rpc declares filesSchema — validates the File parts parseArgs split onto
|
|
107
125
|
the request store and merges them into the args bag the handler receives.
|
|
108
126
|
Either schema's issues become a 422. Files stay out of inputSchema so its
|
|
109
127
|
JSON-Schema projection (OpenAPI/MCP/CLI) never has to model a binary.
|
|
@@ -115,7 +133,7 @@ export function defineVerb<Args, Return>(
|
|
|
115
133
|
inputSchema['~standard'].validate(value),
|
|
116
134
|
)
|
|
117
135
|
if (result.issues) {
|
|
118
|
-
return
|
|
136
|
+
return validationError(result.issues)
|
|
119
137
|
}
|
|
120
138
|
value = result.value
|
|
121
139
|
}
|
|
@@ -123,7 +141,7 @@ export function defineVerb<Args, Return>(
|
|
|
123
141
|
const files = requestContext.getStore()?.files ?? {}
|
|
124
142
|
const result = await filesSchema['~standard'].validate(files)
|
|
125
143
|
if (result.issues) {
|
|
126
|
-
return
|
|
144
|
+
return validationError(result.issues)
|
|
127
145
|
}
|
|
128
146
|
value = { ...(value as object), ...(result.value as object) }
|
|
129
147
|
}
|
|
@@ -138,11 +156,11 @@ export function defineVerb<Args, Return>(
|
|
|
138
156
|
SSR call.
|
|
139
157
|
*/
|
|
140
158
|
/* Abort the controller parseArgsForFetch stashed on store.req; a no-op when none was stashed (SSR cache reads). */
|
|
141
|
-
function
|
|
159
|
+
function abortRpcTimeout(): void {
|
|
142
160
|
const req = requestContext.getStore()?.req as
|
|
143
|
-
| (Request & { [
|
|
161
|
+
| (Request & { [RPC_TIMEOUT_ABORT]?: AbortController })
|
|
144
162
|
| undefined
|
|
145
|
-
req?.[
|
|
163
|
+
req?.[RPC_TIMEOUT_ABORT]?.abort(new DOMException('handler timeout', 'TimeoutError'))
|
|
146
164
|
}
|
|
147
165
|
|
|
148
166
|
function invoke(args: Args | undefined): Promise<Response> {
|
|
@@ -153,9 +171,9 @@ export function defineVerb<Args, Return>(
|
|
|
153
171
|
/*
|
|
154
172
|
On the deadline, fire the controller parseArgsForFetch composed into
|
|
155
173
|
request().signal (absent on the SSR cache-read path, so a sibling
|
|
156
|
-
|
|
174
|
+
rpc's outbound fetch is never cancelled) — then 504.
|
|
157
175
|
*/
|
|
158
|
-
return
|
|
176
|
+
return runWithRpcTimeout(work, timeout, abortRpcTimeout)
|
|
159
177
|
}
|
|
160
178
|
|
|
161
179
|
const remote = createRemoteFunction<Args, Return>({
|
|
@@ -168,17 +186,17 @@ export function defineVerb<Args, Return>(
|
|
|
168
186
|
parseArgsForFetch: async (request) => {
|
|
169
187
|
const args = await parseArgs(method, request, opts?.maxBodySize)
|
|
170
188
|
/*
|
|
171
|
-
Compose this
|
|
189
|
+
Compose this rpc's deadline into request().signal so a handler's
|
|
172
190
|
fetch(ext, { signal: request().signal }) is cancelled when the
|
|
173
191
|
timeout fires — not just abandoned. Applied after parseArgs onto the
|
|
174
|
-
scope's *final* request: a maxBodySize
|
|
192
|
+
scope's *final* request: a maxBodySize rpc swaps store.req for a
|
|
175
193
|
buffered copy (readBodyWithinLimit) and an app.handle hook may
|
|
176
194
|
rewrite it, so composing onto the inbound `request` would leave
|
|
177
|
-
request() — and
|
|
195
|
+
request() — and abortRpcTimeout, which reads store.req — pointed at
|
|
178
196
|
an un-cancellable signal. Only the signal is shadowed; the body
|
|
179
197
|
stays readable. The store always exists here (network + in-process
|
|
180
198
|
dispatch both run inside runWithRequestScope); SSR cache reads call
|
|
181
|
-
invoke() directly, never this path, so a sibling
|
|
199
|
+
invoke() directly, never this path, so a sibling rpc is never
|
|
182
200
|
cross-cancelled.
|
|
183
201
|
*/
|
|
184
202
|
if (timeout !== undefined) {
|
|
@@ -187,7 +205,7 @@ export function defineVerb<Args, Return>(
|
|
|
187
205
|
const controller = new AbortController()
|
|
188
206
|
const composed = AbortSignal.any([req.signal, controller.signal])
|
|
189
207
|
Object.defineProperty(req, 'signal', { value: composed, configurable: true })
|
|
190
|
-
Object.defineProperty(req,
|
|
208
|
+
Object.defineProperty(req, RPC_TIMEOUT_ABORT, {
|
|
191
209
|
value: controller,
|
|
192
210
|
configurable: true,
|
|
193
211
|
})
|
|
@@ -196,7 +214,7 @@ export function defineVerb<Args, Return>(
|
|
|
196
214
|
return args as Args | undefined
|
|
197
215
|
},
|
|
198
216
|
})
|
|
199
|
-
|
|
217
|
+
registerRpc({
|
|
200
218
|
remote: remote as RemoteFunction<unknown, unknown>,
|
|
201
219
|
inputSchema,
|
|
202
220
|
outputSchema,
|
|
@@ -4,11 +4,11 @@ import type { AppModule } from '../AppModule.ts'
|
|
|
4
4
|
import { runWithRequestScope } from '../runtime/runWithRequestScope.ts'
|
|
5
5
|
|
|
6
6
|
/*
|
|
7
|
-
Runs a
|
|
7
|
+
Runs a rpc in-process: synthesizes the rpc Request from the remote's own
|
|
8
8
|
method + url and pipes it through remote.fetch — the same handler/validation/
|
|
9
9
|
error path the HTTP router uses, no network hop. The single in-process
|
|
10
10
|
dispatch every consumer surface (the CLI client, the MCP tool dispatcher, and
|
|
11
|
-
the test client) routes through, so they can't drift on how a
|
|
11
|
+
the test client) routes through, so they can't drift on how a rpc is invoked.
|
|
12
12
|
Takes the RemoteFunction directly — invocation never reads the registry
|
|
13
13
|
entry's schemas/clients (validation is closed over inside the remote), so the
|
|
14
14
|
entry is not a dependency here. `baseUrl` gives the synthetic Request its
|
|
@@ -22,7 +22,7 @@ resolution, and the app's handleError (or the 500 fallback) on a throw. The
|
|
|
22
22
|
synthesized Request is shared between the scope store and the handler fetch so
|
|
23
23
|
request() returns the same Request parseArgs read from.
|
|
24
24
|
*/
|
|
25
|
-
export function
|
|
25
|
+
export function dispatchRpcInProcess({
|
|
26
26
|
remote,
|
|
27
27
|
args,
|
|
28
28
|
baseUrl,
|