@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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# abide
|
|
2
2
|
|
|
3
|
+
## 0.43.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6453cf9: repurpose the bare outbox() export to the global reactive aggregate ([`003713d`](https://github.com/briancray/abide/commit/003713dab125b3df72d6cf832ec18ea546922c79))
|
|
8
|
+
- 6453cf9: typed navigate params, scope-destructured primitives, precise prop diagnostics ([`0769896`](https://github.com/briancray/abide/commit/07698966147e96970c27e0278e2152a23e48b219))
|
|
9
|
+
- 6453cf9: OutboxEntry type + status ([`08d9b03`](https://github.com/briancray/abide/commit/08d9b036501110ab24c18258cc79364f9ac71ea8))
|
|
10
|
+
- 6453cf9: migrate kitchen-sink to rpc-native outbox; AGENTS.md surface ([`1b8aa3f`](https://github.com/briancray/abide/commit/1b8aa3fe631181e7798d0703eb09a0f22dba0f88))
|
|
11
|
+
- 6453cf9: persist request body + content-type, replay on reload ([`26c309f`](https://github.com/briancray/abide/commit/26c309f20bf573e512b74477ba2319c22218f7d9))
|
|
12
|
+
- 6453cf9: client registry of durable rpc queues ([`2b89c20`](https://github.com/briancray/abide/commit/2b89c20b1f1f1794f90152f46958ee760333bab5))
|
|
13
|
+
- 6453cf9: reject server-only imports in the client bundle with an evidence chain ([`2de1730`](https://github.com/briancray/abide/commit/2de1730872898d320c240a89b570dfaa9acace7a))
|
|
14
|
+
- 6453cf9: name-keyed typed errors via a handler errors ctx ([`62f75a8`](https://github.com/briancray/abide/commit/62f75a8341d61ac9cfd1e7cbeccc66ddec991643))
|
|
15
|
+
- 6453cf9: createOutboxQueue enqueue + reactive/persisted entries ([`88560c9`](https://github.com/briancray/abide/commit/88560c942d0f28623a68ed1315bb2341ad66a9c3))
|
|
16
|
+
- 6453cf9: bundler threads outbox:true into the client proxy emission ([`b279960`](https://github.com/briancray/abide/commit/b27996089f4a3c8786fcd1ade530057a34f086fc))
|
|
17
|
+
- 6453cf9: pending() reflects the durable queue (in-flight or queued) ([`bfa505d`](https://github.com/briancray/abide/commit/bfa505d8d5326f0464be35568ac8ae6fc4bd1901))
|
|
18
|
+
- 6453cf9: class: and style: directives for surgical reactive class/style writes ([`c5ee6d1`](https://github.com/briancray/abide/commit/c5ee6d13faad82ac45c6c1627c6e9435c9c59c5f))
|
|
19
|
+
- 6453cf9: durable return type — outbox:true overload on mutating rpc helpers ([`d916c6b`](https://github.com/briancray/abide/commit/d916c6b5b563ddc4dee07a015cde08d32e6dd750))
|
|
20
|
+
- 6453cf9: outbox:true rpc option, gated to mutating methods ([`e37f2a3`](https://github.com/briancray/abide/commit/e37f2a333e201ef40303dd25bafb9f0ca82dd220))
|
|
21
|
+
- 6453cf9: drain loop — deliver/retry/error/cancel under the entry signal ([`ec32868`](https://github.com/briancray/abide/commit/ec32868eb860b6185f21c196b18f03c7b3605ba8))
|
|
22
|
+
- 6453cf9: durable client invoke path + rpc.outbox face ([`f96a0c0`](https://github.com/briancray/abide/commit/f96a0c025cfda5f6daf713f4d69857854b8f3459))
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- 6453cf9: notify() forEach callback must not return a value ([`1c7f20f`](https://github.com/briancray/abide/commit/1c7f20fa5b6a24f3a5e90ab20521f7d24ba55d57))
|
|
27
|
+
- 6453cf9: Breaking: rename defineVerb export to defineRpc ([`29fe778`](https://github.com/briancray/abide/commit/29fe7782844e0de15c53fad711d6616cc90a27e0))
|
|
28
|
+
- 6453cf9: rename VERB*\* constants to RPC*\* ([`391687a`](https://github.com/briancray/abide/commit/391687a252a33ba355ba929944cd1a2cef83c1ef))
|
|
29
|
+
- 6453cf9: rename verb-named rpc helpers to rpc ([`4ebdd8f`](https://github.com/briancray/abide/commit/4ebdd8ff862121a48a39fbdb7b2893a14fd52022))
|
|
30
|
+
- 6453cf9: rename verbRegistry to rpcRegistry ([`507adf5`](https://github.com/briancray/abide/commit/507adf5d5ae3a1b66ef85fc495ba1a974a530abf))
|
|
31
|
+
- 6453cf9: rename Verb-named types/helpers/files to Rpc ([`8342e96`](https://github.com/briancray/abide/commit/8342e963babbddcb9a87dcc99fc2e4958dbc5750))
|
|
32
|
+
- 6453cf9: refresh AGENTS surface map ({#…} blocks, scaffold flags, migration error) ([`88cc4d9`](https://github.com/briancray/abide/commit/88cc4d97205bd3de74a08a6dbae3f2221f0275fe))
|
|
33
|
+
- 6453cf9: commit working tree — outbox, rpc errors, compile plans, cache refactor ([`c673dc7`](https://github.com/briancray/abide/commit/c673dc71522e33ad4f9b48f41ac73fa6c4f03a02))
|
|
34
|
+
- 6453cf9: rename HttpVerb type to HttpMethod ([`d64a59a`](https://github.com/briancray/abide/commit/d64a59a045f77c627b98dbb1b3a12b545f3d3c60))
|
|
35
|
+
- 6453cf9: scrub verb from comments, AGENTS.md, and inspector surface (rpc/method) ([`d9356ce`](https://github.com/briancray/abide/commit/d9356ce9eeba6b03d43ef67fc500cf697e34d39c))
|
|
36
|
+
|
|
3
37
|
## 0.42.0
|
|
4
38
|
|
|
5
39
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,170 +1,127 @@
|
|
|
1
1
|
# abide
|
|
2
2
|
|
|
3
|
-
**One typed declaration fans out to HTTP, a CLI, an MCP tool, and an
|
|
4
|
-
OpenAPI spec — the bundler swaps the runtime per side.**
|
|
3
|
+
**One typed declaration fans out to an HTTP endpoint, a CLI subcommand, an MCP tool, and an OpenAPI operation — and the same callable runs on the server and in the browser.**
|
|
5
4
|
|
|
6
|
-
abide is
|
|
7
|
-
it serves every consumer: a browser fetch, an in-process SSR call, a CLI
|
|
8
|
-
subcommand, an MCP tool, an OpenAPI operation. The same callable keeps its
|
|
9
|
-
name and behaviour on both sides — the bundler decides whether it runs the
|
|
10
|
-
real handler or a network proxy. Built for humans _and_ machines.
|
|
5
|
+
abide is a type-safe isomorphic framework on Bun and web standards. You declare an RPC, a socket, or a `.abide` component once; the bundler swaps the runtime per side (in-process on the server, a typed `fetch` proxy in the browser) so the name, the call, and the behaviour are identical wherever you import it. Built for humans *and* the machines that now read and drive software.
|
|
11
6
|
|
|
12
|
-
- One direct dependency (
|
|
13
|
-
are optional peers. Single runtime: Bun ≥ 1.3.0.
|
|
7
|
+
- One direct dependency (TypeScript). Bun is the only runtime — no Node, no separate bundler, no dev server to wire up.
|
|
14
8
|
|
|
15
9
|
## Quick start
|
|
16
10
|
|
|
17
11
|
```sh
|
|
18
|
-
bunx abide scaffold my-app # scaffolds, installs
|
|
12
|
+
bunx abide scaffold my-app # scaffolds the project, installs it, and starts dev
|
|
19
13
|
```
|
|
20
14
|
|
|
21
|
-
Or read the
|
|
15
|
+
Or read the runnable tour — every primitive below, live:
|
|
22
16
|
|
|
23
17
|
```sh
|
|
24
|
-
git clone
|
|
18
|
+
git clone git@github.com:briancray/abide.git
|
|
25
19
|
cd abide/examples/kitchen-sink
|
|
26
20
|
bun install
|
|
27
|
-
bun
|
|
21
|
+
bun dev
|
|
28
22
|
```
|
|
29
23
|
|
|
30
24
|
## RPCs
|
|
31
25
|
|
|
32
|
-
An RPC is one export per file under `src/server/rpc/`. The file path is the
|
|
33
|
-
URL; the export name is the verb. A Standard Schema (zod / valibot / arktype,
|
|
34
|
-
unadapted) validates the args and projects the same shape into the MCP tool,
|
|
35
|
-
the CLI flags, and the OpenAPI operation.
|
|
26
|
+
An RPC is one export per file under `src/server/rpc/`. The file path is the URL (`getMessages.ts` → `/rpc/getMessages`), and a Standard Schema (zod / valibot / arktype, unadapted) validates the args once and projects the same declaration into every surface.
|
|
36
27
|
|
|
37
28
|
```ts
|
|
38
29
|
// src/server/rpc/getMessages.ts
|
|
39
30
|
import { GET } from '@abide/abide/server/GET'
|
|
40
31
|
import { json } from '@abide/abide/server/json'
|
|
41
32
|
import { z } from 'zod'
|
|
42
|
-
import { recent } from '../../chatState.ts'
|
|
43
33
|
|
|
44
|
-
const inputSchema = z.object({ room: z.string()
|
|
34
|
+
const inputSchema = z.object({ room: z.string() })
|
|
45
35
|
|
|
46
|
-
export const getMessages = GET(({ room
|
|
47
|
-
inputSchema,
|
|
48
|
-
})
|
|
36
|
+
export const getMessages = GET(({ room }) => json(db.messages(room)), { inputSchema })
|
|
49
37
|
```
|
|
50
38
|
|
|
51
|
-
|
|
39
|
+
That single declaration fans out:
|
|
52
40
|
|
|
53
41
|
```text
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
SSR call
|
|
59
|
-
cache(fn)()
|
|
60
|
-
proxy
|
|
42
|
+
export const getMessages = GET(fn, { inputSchema })
|
|
43
|
+
│
|
|
44
|
+
┌────────────┬────────────┬────────────┬────────────┐
|
|
45
|
+
▼ ▼ ▼ ▼ ▼
|
|
46
|
+
SSR call browser MCP tool CLI cmd OpenAPI
|
|
47
|
+
cache(fn)() fetch proxy (read-only) abide cli /openapi.json
|
|
61
48
|
```
|
|
62
49
|
|
|
63
|
-
|
|
64
|
-
`HEAD`); a mutating verb never auto-exposes to MCP — it needs an explicit
|
|
65
|
-
`clients: { mcp: true }`. Consume the verb four ways: `cache(getMessages)(args)`
|
|
66
|
-
in-process (warm SSR hydration), the swapped `fetch` proxy in the browser,
|
|
67
|
-
`getMessages.raw(args)` for the untouched `Response`, and
|
|
68
|
-
`getMessages.stream(args)` to iterate a `jsonl`/`sse` body.
|
|
50
|
+
The schema is the gate: it unlocks the CLI and — for read-only methods (GET/HEAD) — the MCP tool automatically. A mutating method (POST/PUT/PATCH/DELETE) never auto-exposes to MCP; it opts in with `clients: { mcp: true }`. Consume a call four ways: `cache(getMessages)()` in-process (warm SSR + hydration), the swapped `fetch` proxy in the browser, `getMessages.raw(args)` for the untouched `Response`, and `getMessages.stream(args)` for a frame stream.
|
|
69
51
|
|
|
70
|
-
> Query args
|
|
71
|
-
> The per-verb `timeout` (504 on every surface) is distinct from the
|
|
72
|
-
> client-side `ABIDE_CLIENT_TIMEOUT`.
|
|
52
|
+
> Query args travel as strings — use `z.coerce.*` for numbers/booleans on GET/DELETE/HEAD. A per-RPC `timeout` (a 504 on every surface) is distinct from the client-wide `ABIDE_CLIENT_TIMEOUT`.
|
|
73
53
|
|
|
74
54
|
## Sockets
|
|
75
55
|
|
|
76
|
-
A socket is one broadcast topic per file under `src/server/sockets/`. A
|
|
77
|
-
`Socket<T>` is an isomorphic `AsyncIterable<T>`; every socket multiplexes onto
|
|
78
|
-
one WebSocket at `/__abide/sockets`.
|
|
56
|
+
A socket is one broadcast topic per file under `src/server/sockets/`. A `Socket<T>` is an isomorphic `AsyncIterable<T>` — `for await (const m of chat)` is the live stream — and every socket multiplexes onto one WebSocket at `/__abide/sockets`.
|
|
79
57
|
|
|
80
58
|
```ts
|
|
81
59
|
// src/server/sockets/chat.ts
|
|
82
60
|
import { socket } from '@abide/abide/server/socket'
|
|
83
61
|
import { z } from 'zod'
|
|
84
62
|
|
|
85
|
-
const schema = z.object({
|
|
63
|
+
const schema = z.object({ from: z.string(), text: z.string(), at: z.number() })
|
|
86
64
|
|
|
87
|
-
// retain the last 100 frames; evict
|
|
65
|
+
// tail: retain the last 100 frames for late joiners; ttl: evict frames older than an hour.
|
|
88
66
|
export const chat = socket({ schema, tail: 100, ttl: 3_600_000 })
|
|
89
67
|
export type ChatMessage = z.infer<typeof schema>
|
|
90
68
|
```
|
|
91
69
|
|
|
92
|
-
|
|
93
|
-
and MCP): `GET /__abide/sockets/chat` returns the retained tail, and
|
|
94
|
-
`POST /__abide/sockets/chat` publishes — gated by `clientPublish` (default off,
|
|
95
|
-
so browsers publish through a validating verb instead).
|
|
70
|
+
Each socket also has an HTTP face at `/__abide/sockets/<name>`: `GET` returns the retained tail, `POST` publishes (only when the socket declares `clientPublish`). `chat.publish(m)` is isomorphic — server-side it fans out in-process and to remote subscribers; client-side it sends a validated `pub` frame.
|
|
96
71
|
|
|
97
|
-
## Components
|
|
72
|
+
## Components
|
|
98
73
|
|
|
99
|
-
A `.abide` component
|
|
100
|
-
exercises the template grammar. `scope`, `props`, `effect`, `html`, and
|
|
101
|
-
`snippet` are ambient — no import needed.
|
|
74
|
+
A component is a `.abide` file: valid HTML with a `<script>`, `{#…}` control-flow blocks, `<template name>` snippets, `{expr}` bindings, and a component-scoped `<style>`. This page imports the RPC and socket above — `cache(getMessages)` seeds the list warm at SSR, `tail(chat)` layers live frames on top after hydration.
|
|
102
75
|
|
|
103
|
-
<!-- prettier-ignore -->
|
|
104
76
|
```html
|
|
105
77
|
<script>
|
|
106
78
|
import { cache } from '@abide/abide/shared/cache'
|
|
107
79
|
import { tail } from '@abide/abide/ui/tail'
|
|
108
80
|
import { getMessages } from '$server/rpc/getMessages.ts'
|
|
109
|
-
import {
|
|
81
|
+
import { createMessage } from '$server/rpc/createMessage.ts'
|
|
110
82
|
import { chat } from '$server/sockets/chat.ts'
|
|
111
83
|
import Avatar from '$ui/Avatar.abide'
|
|
112
84
|
|
|
113
|
-
const { room } = props()
|
|
85
|
+
const { room = 'lobby' } = props()
|
|
86
|
+
const { state, computed } = scope()
|
|
114
87
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const
|
|
88
|
+
let draft = state('')
|
|
89
|
+
let notify = state(true)
|
|
90
|
+
const live = computed(() => tail(chat)) // read-only derived
|
|
91
|
+
const ready = computed(() => draft.trim().length > 0)
|
|
118
92
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
async function send() {
|
|
125
|
-
await publishChat({ from, text })
|
|
126
|
-
text = ''
|
|
93
|
+
async function send(event: SubmitEvent) {
|
|
94
|
+
event.preventDefault()
|
|
95
|
+
await createMessage({ room, text: draft, notify })
|
|
96
|
+
draft = ''
|
|
127
97
|
}
|
|
128
98
|
</script>
|
|
129
99
|
|
|
130
|
-
<template name="
|
|
131
|
-
<li><Avatar name={
|
|
100
|
+
<template name="row" args={{ msg }}>
|
|
101
|
+
<li><Avatar name={msg.from} /> {msg.text}</li>
|
|
132
102
|
</template>
|
|
133
103
|
|
|
134
104
|
<form onsubmit={send}>
|
|
135
|
-
<input bind:value={
|
|
136
|
-
<input bind:
|
|
137
|
-
<
|
|
138
|
-
<label><input type="radio" bind:group={view} value="all" /> all</label>
|
|
139
|
-
<label><input type="radio" bind:group={view} value="mine" /> mine</label>
|
|
140
|
-
<button disabled={!text}>send</button>
|
|
105
|
+
<input bind:value={draft} placeholder="message" />
|
|
106
|
+
<label><input type="checkbox" bind:checked={notify} /> notify</label>
|
|
107
|
+
<button disabled={!ready()}>Send</button>
|
|
141
108
|
</form>
|
|
142
109
|
|
|
143
|
-
{#if latest}
|
|
144
|
-
<p>latest from {latest.from}</p>
|
|
145
|
-
{:else}
|
|
146
|
-
<p>no messages yet</p>
|
|
147
|
-
{/if}
|
|
148
|
-
|
|
149
|
-
{#switch view}
|
|
150
|
-
{:case 'mine'}<p>showing your messages</p>
|
|
151
|
-
{:default}<p>showing every message</p>
|
|
152
|
-
{/switch}
|
|
110
|
+
{#if live()}<p class="muted">latest: {live().text}</p>{/if}
|
|
153
111
|
|
|
154
|
-
{#await
|
|
155
|
-
<p>
|
|
156
|
-
{:then
|
|
112
|
+
{#await cache(getMessages)({ room })}
|
|
113
|
+
<p>Loading…</p>
|
|
114
|
+
{:then messages}
|
|
157
115
|
<ul>
|
|
158
|
-
{#for
|
|
159
|
-
{i}. {line(message)}
|
|
160
|
-
{/for}
|
|
116
|
+
{#for msg of messages by msg.at}{row({ msg })}{/for}
|
|
161
117
|
</ul>
|
|
162
|
-
{:catch
|
|
163
|
-
<p>
|
|
118
|
+
{:catch error}
|
|
119
|
+
<p class="error">{error.message}</p>
|
|
164
120
|
{/await}
|
|
165
121
|
|
|
166
122
|
<style>
|
|
167
|
-
|
|
123
|
+
.muted { color: #64748b; }
|
|
124
|
+
.error { color: #b91c1c; }
|
|
168
125
|
</style>
|
|
169
126
|
```
|
|
170
127
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abide/abide",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "Isomorphic multimodal HTTP framework built for humans and machines in a single Bun runtime",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"./server/AppModule": "./src/lib/server/AppModule.ts",
|
|
55
55
|
"./server/InspectorContext": "./src/lib/server/runtime/types/InspectorContext.ts",
|
|
56
56
|
"./server/agent": "./src/lib/server/agent.ts",
|
|
57
|
-
"./server/rpc/
|
|
57
|
+
"./server/rpc/defineRpc": "./src/lib/server/rpc/defineRpc.ts",
|
|
58
58
|
"./server/sockets/defineSocket": "./src/lib/server/sockets/defineSocket.ts",
|
|
59
59
|
"./server/prompts/definePrompt": "./src/lib/server/prompts/definePrompt.ts",
|
|
60
60
|
"./ui/tail": "./src/lib/ui/tail.ts",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"./shared/pending": "./src/lib/shared/pending.ts",
|
|
63
63
|
"./shared/refreshing": "./src/lib/shared/refreshing.ts",
|
|
64
64
|
"./shared/HttpError": "./src/lib/shared/HttpError.ts",
|
|
65
|
+
"./shared/ValidationErrorData": "./src/lib/shared/types/ValidationErrorData.ts",
|
|
65
66
|
"./shared/html": "./src/lib/shared/html.ts",
|
|
66
67
|
"./shared/snippet": "./src/lib/shared/snippet.ts",
|
|
67
68
|
"./shared/withJsonSchema": "./src/lib/shared/withJsonSchema.ts",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// node:fs existsSync — Bun plugin onResolve is sync-only; Bun.file().exists() is async
|
|
2
2
|
import { existsSync, statSync } from 'node:fs'
|
|
3
|
+
import { dirname, join } from 'node:path'
|
|
3
4
|
import type { BunPlugin } from 'bun'
|
|
4
5
|
import { Glob } from 'bun'
|
|
5
6
|
import { abideImportName } from './lib/shared/abideImportName.ts'
|
|
@@ -80,7 +81,7 @@ function once<T>(produce: () => Promise<T>): () => Promise<T> {
|
|
|
80
81
|
|
|
81
82
|
/*
|
|
82
83
|
Bun plugin that wires every virtual import abide produces at build time:
|
|
83
|
-
- `abide:rpc` — { rpcUrl: () => import(rpc-module) } HTTP-
|
|
84
|
+
- `abide:rpc` — { rpcUrl: () => import(rpc-module) } HTTP-method manifest
|
|
84
85
|
- `abide:sockets` — { socketName: () => import(socket-module) } socket manifest
|
|
85
86
|
- `abide:pages` — { pageUrl: () => import(page.abide) } manifest
|
|
86
87
|
- `abide:prompts` — { promptName: () => import(prompt-module) } manifest
|
|
@@ -91,8 +92,8 @@ Bun plugin that wires every virtual import abide produces at build time:
|
|
|
91
92
|
- `abide:shell` — app.html content (custom or default)
|
|
92
93
|
|
|
93
94
|
Also rewrites modules under src/server/rpc and src/server/sockets:
|
|
94
|
-
- src/server/rpc/<file>.ts: each HTTP-
|
|
95
|
-
implementation —
|
|
95
|
+
- src/server/rpc/<file>.ts: each HTTP-method export is bound to a runtime
|
|
96
|
+
implementation — defineRpc on the server, remoteProxy on the client.
|
|
96
97
|
- src/server/sockets/<file>.ts: each `socket(opts)` export is bound to
|
|
97
98
|
defineSocket on the server (with the socket name + opts) or
|
|
98
99
|
socketProxy on the client (name only — opts are server-side).
|
|
@@ -147,7 +148,7 @@ export function abideResolverPlugin({
|
|
|
147
148
|
scanDir(rpcDir, '**/*.ts').then(async (rpcFiles) => {
|
|
148
149
|
const importName = await abideImportNameOnce()
|
|
149
150
|
await writeRpcDts({ cwd, rpcDir, rpcFiles, importName })
|
|
150
|
-
/* Typed createTestApp `app.rpc.<
|
|
151
|
+
/* Typed createTestApp `app.rpc.<rpc>` surface. */
|
|
151
152
|
await writeTestRpcDts({ cwd, rpcFiles, importName })
|
|
152
153
|
return rpcFiles
|
|
153
154
|
}),
|
|
@@ -194,9 +195,91 @@ export function abideResolverPlugin({
|
|
|
194
195
|
const socketsFilter = new RegExp(`^${escapeRegex(socketsDir)}/.*\\.ts$`)
|
|
195
196
|
const promptsFilter = new RegExp(`^${escapeRegex(promptsDir)}/.*\\.md$`)
|
|
196
197
|
|
|
198
|
+
/*
|
|
199
|
+
Side-crossing guard (client target only). The client bundle must never pull a
|
|
200
|
+
server-only name into the browser. The exception is a registered rpc/socket —
|
|
201
|
+
`$server/rpc/*` and `$server/sockets/*` are replaced with remoteProxy/socketProxy
|
|
202
|
+
stubs by the onLoad hooks below — so only OTHER paths under src/server/ (helpers,
|
|
203
|
+
runtime/) and the public `abide/server/*` names are violations. `importerOf` records
|
|
204
|
+
each resolved edge so a violation can show the import chain back to its client-side
|
|
205
|
+
origin as evidence; it is reset per build in onStart.
|
|
206
|
+
*/
|
|
207
|
+
const importerOf = new Map<string, string>()
|
|
208
|
+
const isProxiedServerModule = (path: string): boolean =>
|
|
209
|
+
path.startsWith(`${rpcDir}/`) || path.startsWith(`${socketsDir}/`)
|
|
210
|
+
const isServerOnlyModule = (path: string): boolean =>
|
|
211
|
+
path.startsWith(`${serverDir}/`) && !isProxiedServerModule(path)
|
|
212
|
+
const showPath = (path: string): string =>
|
|
213
|
+
path.startsWith(`${cwd}/`) ? path.slice(cwd.length + 1) : path
|
|
214
|
+
/* Walks the recorded edges from the offending importer back to a graph root (cycle-safe),
|
|
215
|
+
formatting each module relative to cwd — the evidence a side-crossing throws. */
|
|
216
|
+
function sideCrossingChain(leaf: string, importer: string): string {
|
|
217
|
+
const chain = [leaf, importer]
|
|
218
|
+
const seen = new Set([importer])
|
|
219
|
+
let cursor = importer
|
|
220
|
+
while (importerOf.has(cursor)) {
|
|
221
|
+
cursor = importerOf.get(cursor) as string
|
|
222
|
+
if (seen.has(cursor)) {
|
|
223
|
+
break
|
|
224
|
+
}
|
|
225
|
+
seen.add(cursor)
|
|
226
|
+
chain.push(cursor)
|
|
227
|
+
}
|
|
228
|
+
return chain.reverse().map(showPath).join('\n → ')
|
|
229
|
+
}
|
|
230
|
+
function throwSideCrossing(leaf: string, label: string, importer: string): never {
|
|
231
|
+
throw new Error(
|
|
232
|
+
`[abide] a client module imports the server-only name \`${label}\` — server code must not reach the browser bundle. Move it to src/shared/ or behind an RPC. Import chain:\n ${sideCrossingChain(leaf, importer)}`,
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
/* Records the edge and, on the client target, rejects a non-server module reaching a
|
|
236
|
+
server-only one. Shared by the relative-import and `$server` alias resolvers. */
|
|
237
|
+
function recordAndGuard(resolved: string, label: string, importer: string | undefined): void {
|
|
238
|
+
if (importer === undefined) {
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
importerOf.set(resolved, importer)
|
|
242
|
+
if (target === 'client' && isServerOnlyModule(resolved) && !isServerOnlyModule(importer)) {
|
|
243
|
+
throwSideCrossing(resolved, label, importer)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
197
247
|
return {
|
|
198
248
|
name: 'abide-resolver',
|
|
199
249
|
setup(build) {
|
|
250
|
+
/* Fresh edge graph each build (dev watch reuses the plugin instance).
|
|
251
|
+
onStart is build-time only — absent in the runtime/preload plugin context. */
|
|
252
|
+
build.onStart?.(() => {
|
|
253
|
+
importerOf.clear()
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
/*
|
|
257
|
+
Relative-import edge recorder + side-crossing guard. Matches `./` and `../`
|
|
258
|
+
specifiers, records the edge for the evidence chain, and (client target) throws
|
|
259
|
+
when a non-server module reaches a server-only file. Returns undefined so normal
|
|
260
|
+
resolution proceeds — this only observes and, on violation, aborts.
|
|
261
|
+
*/
|
|
262
|
+
build.onResolve({ filter: /^\.\.?\// }, (args) => {
|
|
263
|
+
if (args.importer) {
|
|
264
|
+
const resolved = resolveExtension(join(dirname(args.importer), args.path))
|
|
265
|
+
recordAndGuard(resolved, args.path, args.importer)
|
|
266
|
+
}
|
|
267
|
+
return undefined
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
/*
|
|
271
|
+
The public `abide/server/*` names are server-only. Importing one into the client
|
|
272
|
+
bundle is a side-crossing (the canonical `abide` and `@abide/abide` specifiers; a
|
|
273
|
+
custom package alias falls through to the relative/`$server` guards). The server
|
|
274
|
+
target resolves these normally.
|
|
275
|
+
*/
|
|
276
|
+
build.onResolve({ filter: /(^|\/)abide\/server\// }, (args) => {
|
|
277
|
+
if (target === 'client' && args.importer && !isServerOnlyModule(args.importer)) {
|
|
278
|
+
throwSideCrossing(args.path, args.path, args.importer)
|
|
279
|
+
}
|
|
280
|
+
return undefined
|
|
281
|
+
})
|
|
282
|
+
|
|
200
283
|
build.onResolve(
|
|
201
284
|
{
|
|
202
285
|
filter: /\/_virtual\/(rpc|sockets|prompts|pages|layouts|app|config|mcp-resources|mcp|assets|public-assets|shell|app-info|cli-manifest|cli-name|cli-chrome|bundle-window|bundle-disconnected-component|bundle-disconnected)\.ts$/,
|
|
@@ -226,7 +309,11 @@ export function abideResolverPlugin({
|
|
|
226
309
|
for (const [alias, baseDir] of Object.entries(dirAliases)) {
|
|
227
310
|
build.onResolve({ filter: new RegExp(`^\\${alias}(\\/.*)?$`) }, (args) => {
|
|
228
311
|
const subpath = args.path.slice(alias.length)
|
|
229
|
-
|
|
312
|
+
const resolved = resolveExtension(subpath ? `${baseDir}${subpath}` : baseDir)
|
|
313
|
+
/* `$server/rpc/*` + `$server/sockets/*` are proxied (allowed); other
|
|
314
|
+
`$server/*` imports into the client bundle are side-crossings. */
|
|
315
|
+
recordAndGuard(resolved, args.path, args.importer)
|
|
316
|
+
return { path: resolved }
|
|
230
317
|
})
|
|
231
318
|
}
|
|
232
319
|
|
|
@@ -262,7 +349,7 @@ export function abideResolverPlugin({
|
|
|
262
349
|
const prepared = prepareRpcModule(source, importName)
|
|
263
350
|
if (!prepared) {
|
|
264
351
|
throw new Error(
|
|
265
|
-
`[abide] src/server/rpc/${relativePath} has no \`export const <name> = <
|
|
352
|
+
`[abide] src/server/rpc/${relativePath} has no \`export const <name> = <METHOD>(...)\` — every $rpc module must declare exactly one remote function`,
|
|
266
353
|
)
|
|
267
354
|
}
|
|
268
355
|
const expectedName = fileStem(relativePath)
|
|
@@ -279,22 +366,25 @@ export function abideResolverPlugin({
|
|
|
279
366
|
so page imports resolve identically on both sides.
|
|
280
367
|
*/
|
|
281
368
|
if (target === 'client') {
|
|
369
|
+
/* A durable rpc (`outbox: true`) gets the third arg so its client proxy
|
|
370
|
+
parks unreachable calls onto the outbox. */
|
|
371
|
+
const durableArg = prepared.durable ? ', { outbox: true }' : ''
|
|
282
372
|
const contents = `import { remoteProxy as __abideRemoteProxy__ } from '${importName}/ui/remoteProxy';
|
|
283
|
-
export const ${prepared.exportName} = __abideRemoteProxy__(${JSON.stringify(prepared.
|
|
373
|
+
export const ${prepared.exportName} = __abideRemoteProxy__(${JSON.stringify(prepared.method)}, ${JSON.stringify(url)}${durableArg});
|
|
284
374
|
`
|
|
285
375
|
return { contents, loader: 'ts' }
|
|
286
376
|
}
|
|
287
377
|
/*
|
|
288
|
-
Server target: strip the user's
|
|
289
|
-
the `<
|
|
378
|
+
Server target: strip the user's rpc import, then rewrite
|
|
379
|
+
the `<METHOD>(` call so the method (from the identifier) and
|
|
290
380
|
the URL (from the file path) are threaded into the
|
|
291
|
-
runtime constructor —
|
|
381
|
+
runtime constructor — defineRpc. The user's handler body
|
|
292
382
|
stays intact between the parens; any generics on the call
|
|
293
383
|
are dropped (they carry no runtime info). Rewriting is
|
|
294
384
|
tokenizer-driven so `GET` mentions inside strings and
|
|
295
385
|
comments are left alone.
|
|
296
386
|
*/
|
|
297
|
-
const banner = `import {
|
|
387
|
+
const banner = `import { defineRpc as __abideDefineRpc__ } from '${importName}/server/rpc/defineRpc';
|
|
298
388
|
`
|
|
299
389
|
return { contents: `${banner}${prepared.rewriteForServer(url)}`, loader: 'ts' }
|
|
300
390
|
})
|
|
@@ -471,7 +561,7 @@ ${optionLines}
|
|
|
471
561
|
The CLI binary's bake-time manifest. Discovery (a
|
|
472
562
|
one-shot script the bundler runs separately) writes
|
|
473
563
|
`${cwd}/dist/cli-manifest.json` from the populated
|
|
474
|
-
|
|
564
|
+
rpcRegistry; this virtual splices that JSON in as a
|
|
475
565
|
default-exported object. Empty manifest when the
|
|
476
566
|
discovery file is missing — the binary still works
|
|
477
567
|
but exposes no subcommands until the user runs the
|
|
@@ -606,7 +696,7 @@ export const footer = ${JSON.stringify(footer)}
|
|
|
606
696
|
if (args.path === 'abide:mcp') {
|
|
607
697
|
/*
|
|
608
698
|
The MCP server is fully framework-generated — tools from
|
|
609
|
-
the
|
|
699
|
+
the rpc registry, prompts from src/mcp/prompts, resources
|
|
610
700
|
from src/mcp/resources. createMcpServer is internal; there
|
|
611
701
|
is no user-authored server module. Server identity comes
|
|
612
702
|
from package.json so the `mcp__<name>__*` permission prefix
|
|
@@ -798,7 +888,7 @@ async function scanPages(pagesDir: string): Promise<PagesScan> {
|
|
|
798
888
|
|
|
799
889
|
/*
|
|
800
890
|
Walks one registry directory once: src/server/rpc (every `.ts` file is an
|
|
801
|
-
HTTP-
|
|
891
|
+
HTTP-method rpc handler), src/server/sockets (each `.ts` file declares one
|
|
802
892
|
socket, loaded lazily on first sub/pub frame), or src/mcp/prompts (each `.md`
|
|
803
893
|
file declares one MCP prompt — frontmatter for metadata, body for the
|
|
804
894
|
template). Returns an empty list when the directory doesn't exist so an app
|
package/src/buildCli.ts
CHANGED
|
@@ -21,7 +21,7 @@ the compiled server beside it, so `/start` can spawn a local instance.
|
|
|
21
21
|
1. Client build (once): `build` produces the platform-independent `dist/_app`
|
|
22
22
|
the server binaries embed. It clears dist first, so it runs before everything.
|
|
23
23
|
2. Discovery: build the discovery entry into a temporary JS bundle and run it. It
|
|
24
|
-
imports every rpc/socket module so
|
|
24
|
+
imports every rpc/socket module so defineRpc / defineSocket populate the
|
|
25
25
|
registries, then prints the CLI manifest to stdout → `dist/cli-manifest.json`.
|
|
26
26
|
3. Per target: a server binary (`compile`, reusing the shared `dist/_app` via
|
|
27
27
|
`buildClient:false`) plus the CLI binary, written side by side. The resolver's
|
package/src/discoveryEntry.ts
CHANGED
|
@@ -3,14 +3,14 @@ import { rpc } from './_virtual/rpc.ts'
|
|
|
3
3
|
// @ts-expect-error virtual module resolved by abideResolverPlugin
|
|
4
4
|
import { sockets } from './_virtual/sockets.ts'
|
|
5
5
|
import type { CliManifestEntry } from './lib/cli/types/CliManifestEntry.ts'
|
|
6
|
-
import {
|
|
6
|
+
import { rpcRegistry } from './lib/server/rpc/rpcRegistry.ts'
|
|
7
7
|
import { socketOperations } from './lib/server/sockets/socketOperations.ts'
|
|
8
8
|
import { socketRegistry } from './lib/server/sockets/socketRegistry.ts'
|
|
9
9
|
import { commandNameForUrl } from './lib/shared/commandNameForUrl.ts'
|
|
10
10
|
import { jsonSchemaForSchema } from './lib/shared/jsonSchemaForSchema.ts'
|
|
11
11
|
|
|
12
12
|
/*
|
|
13
|
-
One-shot script that imports every rpc + socket module so
|
|
13
|
+
One-shot script that imports every rpc + socket module so defineRpc /
|
|
14
14
|
defineSocket populate the process-wide registries, then prints the CLI
|
|
15
15
|
manifest to stdout as JSON. Used by buildCli to bake the manifest into
|
|
16
16
|
the standalone binary at build time without resorting to static source
|
|
@@ -23,7 +23,7 @@ await Promise.all([
|
|
|
23
23
|
|
|
24
24
|
const manifest: Record<string, CliManifestEntry> = {}
|
|
25
25
|
|
|
26
|
-
for (const entry of
|
|
26
|
+
for (const entry of rpcRegistry.values()) {
|
|
27
27
|
if (!entry.clients.cli) {
|
|
28
28
|
continue
|
|
29
29
|
}
|
|
@@ -8,7 +8,7 @@ handles it:
|
|
|
8
8
|
if (event.detail.name === 'sync') syncNow()
|
|
9
9
|
})
|
|
10
10
|
|
|
11
|
-
Emitting an event (rather than calling a
|
|
11
|
+
Emitting an event (rather than calling a rpc directly) is what lets a menu
|
|
12
12
|
drive parameterised work: a click carries no arguments, so the app computes
|
|
13
13
|
them and makes the call itself. `shortcut` is the key for the Cmd-based
|
|
14
14
|
equivalent (e.g. `'r'` → Cmd-R).
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { dispatchRpcInProcess } from '../server/rpc/dispatchRpcInProcess.ts'
|
|
2
|
+
import { findRpcByCommandName } from '../server/rpc/findRpcByCommandName.ts'
|
|
3
3
|
import { buildRpcProxy } from '../shared/buildRpcProxy.ts'
|
|
4
4
|
import { buildRpcRequest } from '../shared/buildRpcRequest.ts'
|
|
5
|
-
import type {
|
|
5
|
+
import type { HttpMethod } from '../shared/types/HttpMethod.ts'
|
|
6
6
|
import type { RpcInvoker } from '../shared/types/RpcInvoker.ts'
|
|
7
7
|
import type { CliManifest } from './types/CliManifest.ts'
|
|
8
8
|
|
|
@@ -24,8 +24,8 @@ decided at construction:
|
|
|
24
24
|
against `<url>/<manifest[name].url>` using the manifest's method.
|
|
25
25
|
Auth header is set from `token` when provided.
|
|
26
26
|
- Without `url`: in-process mode. Each property access looks up the
|
|
27
|
-
|
|
28
|
-
modules) and calls `
|
|
27
|
+
rpc in the registry (populated by importing the project's rpc
|
|
28
|
+
modules) and calls `rpc.fetch(synthesizedRequest)` — same code
|
|
29
29
|
path the HTTP router uses, no network hop.
|
|
30
30
|
|
|
31
31
|
The `manifest` is the bundler-emitted CLI manifest baked into the thin
|
|
@@ -62,9 +62,9 @@ export function createClient<Api extends AnyApi = AnyApi>(opts?: {
|
|
|
62
62
|
undefined when the name is unknown in the active mode. Remote mode (url set)
|
|
63
63
|
resolves method + url from the baked manifest — registry fallback for
|
|
64
64
|
same-project callers — and sends the synthesized Request over the network.
|
|
65
|
-
In-process mode resolves the
|
|
66
|
-
|
|
67
|
-
uses, so the two consumer surfaces can't drift on how a
|
|
65
|
+
In-process mode resolves the rpc from the registry and routes through
|
|
66
|
+
dispatchRpcInProcess, the same synthesize-and-fetch the MCP dispatcher
|
|
67
|
+
uses, so the two consumer surfaces can't drift on how a rpc is invoked.
|
|
68
68
|
*/
|
|
69
69
|
function resolveSend(name: string): ((args?: unknown) => Promise<Response>) | undefined {
|
|
70
70
|
if (url) {
|
|
@@ -83,12 +83,12 @@ export function createClient<Api extends AnyApi = AnyApi>(opts?: {
|
|
|
83
83
|
}),
|
|
84
84
|
)
|
|
85
85
|
}
|
|
86
|
-
const entry =
|
|
86
|
+
const entry = findRpcByCommandName(name)
|
|
87
87
|
if (!entry) {
|
|
88
88
|
return undefined
|
|
89
89
|
}
|
|
90
90
|
return (args) =>
|
|
91
|
-
|
|
91
|
+
dispatchRpcInProcess({
|
|
92
92
|
remote: entry.remote,
|
|
93
93
|
args,
|
|
94
94
|
baseUrl: 'http://localhost/',
|
|
@@ -99,8 +99,8 @@ export function createClient<Api extends AnyApi = AnyApi>(opts?: {
|
|
|
99
99
|
// Remote-mode registry fallback for callers passing a url but no manifest.
|
|
100
100
|
function registryCommand(
|
|
101
101
|
name: string,
|
|
102
|
-
): { method:
|
|
103
|
-
const found =
|
|
102
|
+
): { method: HttpMethod; url: string; accept?: string } | undefined {
|
|
103
|
+
const found = findRpcByCommandName(name)
|
|
104
104
|
return found ? { method: found.remote.method, url: found.remote.url } : undefined
|
|
105
105
|
}
|
|
106
106
|
|
|
@@ -12,7 +12,7 @@ import type { CliManifest } from './types/CliManifest.ts'
|
|
|
12
12
|
Runs one RPC command against a target server and prints the result, returning a
|
|
13
13
|
process exit code. Shared by the one-shot path (runCli) and the interactive
|
|
14
14
|
session (runSession) so a command behaves identically typed at the shell or at
|
|
15
|
-
the session prompt. Streaming responses (sse/jsonl — a streaming
|
|
15
|
+
the session prompt. Streaming responses (sse/jsonl — a streaming rpc or a socket
|
|
16
16
|
`tail`) print frame-by-frame as NDJSON; everything else decodes and prints once.
|
|
17
17
|
*/
|
|
18
18
|
export async function dispatchCommand({
|
|
@@ -5,7 +5,7 @@ import type { CliTarget } from './types/CliTarget.ts'
|
|
|
5
5
|
Prints the session's connection line. A local instance (spawned child) reads as
|
|
6
6
|
"running a local instance"; a remote one reads as "connected to <name>", using the
|
|
7
7
|
name the target already carries from its resolve-time probe and only re-probing
|
|
8
|
-
when it doesn't. No target → the not-connected hint listing the
|
|
8
|
+
when it doesn't. No target → the not-connected hint listing the rpcs.
|
|
9
9
|
*/
|
|
10
10
|
export async function printSessionStatus(target: CliTarget | undefined): Promise<void> {
|
|
11
11
|
if (!target) {
|
|
@@ -11,7 +11,7 @@ const AUTO_START_CEILING_MS = 3000
|
|
|
11
11
|
|
|
12
12
|
/*
|
|
13
13
|
Resolves the connection to resume when the CLI runs without an explicit
|
|
14
|
-
connection
|
|
14
|
+
connection rpc — the terminal analogue of the bundle's resolveLaunchTarget.
|
|
15
15
|
Reads the saved intent:
|
|
16
16
|
- embedded → boot a fresh local instance (bounded; undefined on failure)
|
|
17
17
|
- url, still alive → connect to it
|