@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.
- package/AGENTS.md +234 -294
- package/CHANGELOG.md +82 -0
- package/README.md +51 -95
- package/package.json +3 -2
- package/src/abideLsp.ts +46 -8
- package/src/abideResolverPlugin.ts +107 -19
- 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/devClientFingerprint.ts +2 -1
- 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/escapeRegex.ts +9 -0
- package/src/lib/shared/extraForwardHeaders.ts +1 -1
- package/src/lib/shared/fileName.ts +9 -0
- package/src/lib/shared/fileStem.ts +3 -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/programNameForPackage.ts +3 -1
- 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 +6 -4
- 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/ABIDE_SEMANTIC_TOKENS_LEGEND.ts +45 -0
- package/src/lib/ui/compile/abideUiPlugin.ts +2 -1
- package/src/lib/ui/compile/awaitPlan.ts +48 -0
- package/src/lib/ui/compile/compileShadow.ts +117 -31
- package/src/lib/ui/compile/createShadowLanguageService.ts +48 -0
- package/src/lib/ui/compile/desugarSignals.ts +42 -6
- package/src/lib/ui/compile/encodeSemanticTokens.ts +37 -0
- package/src/lib/ui/compile/generateBuild.ts +45 -62
- package/src/lib/ui/compile/generateSSR.ts +90 -60
- package/src/lib/ui/compile/ifPlan.ts +30 -0
- package/src/lib/ui/compile/makeVarNamer.ts +10 -0
- package/src/lib/ui/compile/offsetToPosition.ts +9 -0
- package/src/lib/ui/compile/parseTemplate.ts +565 -115
- package/src/lib/ui/compile/stripEffects.ts +17 -9
- package/src/lib/ui/compile/structuralBlockTokens.ts +101 -0
- package/src/lib/ui/compile/switchPlan.ts +22 -0
- package/src/lib/ui/compile/tryPlan.ts +29 -0
- package/src/lib/ui/compile/types/SemanticToken.ts +11 -0
- package/src/lib/ui/compile/types/TemplateAttr.ts +8 -2
- package/src/lib/ui/compile/types/TemplateNode.ts +15 -2
- package/src/lib/ui/createScope.ts +20 -2
- package/src/lib/ui/dom/appendText.ts +1 -5
- package/src/lib/ui/dom/applyResolved.ts +1 -5
- package/src/lib/ui/dom/disposeRange.ts +6 -10
- package/src/lib/ui/dom/hydrate.ts +2 -5
- package/src/lib/ui/dom/isComment.ts +6 -0
- 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/runtime/createDoc.ts +3 -3
- package/src/lib/ui/runtime/pathSegments.ts +10 -0
- package/src/lib/ui/socketChannel.ts +7 -1
- package/src/lib/ui/types/Scope.ts +20 -5
- package/template/.zed/settings.json +4 -0
- package/template/CLAUDE.md +1 -1
- package/template/src/server/rpc/getHello.ts +3 -3
- package/template/src/ui/pages/page.abide +4 -4
- 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
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { OutboxEntry } from './OutboxEntry.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
The `.outbox` face on a durable RPC's client proxy: callable to read the reactive list
|
|
5
|
+
of undelivered entries (`rpc.outbox()`), with an awaitable `retry()` that drains the whole
|
|
6
|
+
queue and resolves when the replay settles. Server-side there is no client queue, so the
|
|
7
|
+
face is absent.
|
|
8
|
+
*/
|
|
9
|
+
export type Outbox<Args> = (() => OutboxEntry<Args>[]) & { retry: () => Promise<void> }
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/* The lifecycle status of a parked mutation: waiting to (re)send, or currently sending.
|
|
2
|
+
A write lives in the outbox ONLY while it can't reach the server; the instant the server
|
|
3
|
+
responds at all — a 2xx OR a real 4xx/500 — the entry leaves the queue, so there is no
|
|
4
|
+
terminal `error` state. Only the unreachable family (transport failure / 502/503/504/52x)
|
|
5
|
+
keeps it `queued`. */
|
|
6
|
+
export type OutboxStatus = 'queued' | 'sending'
|
|
7
|
+
|
|
8
|
+
/* One durable, replayable mutation in an RPC's outbox — parked because the original
|
|
9
|
+
call couldn't reach the server (transport failure, or a 502/503/504/52x). `controller`
|
|
10
|
+
is the entry's own abort handle (cancel = `controller.abort()`); `request` is the
|
|
11
|
+
synthesized, persisted Request the drain re-sends; `args` is the typed input (for
|
|
12
|
+
rendering); `error` is why it's parked (the `kind: 'queued'` HttpError from the
|
|
13
|
+
unreachable attempt); `retry()` kicks a FIFO drain. `settled` is the eventual outcome
|
|
14
|
+
of THIS write as if the original call had reached the server — it resolves with the
|
|
15
|
+
decoded result on delivery and rejects with the real HttpError on a server refusal (or
|
|
16
|
+
an AbortError on cancel). It may stay pending indefinitely if the write is never
|
|
17
|
+
replayed; reading it is what arms it (a never-read `settled` never rejects). */
|
|
18
|
+
export type OutboxEntry<Args> = {
|
|
19
|
+
id: string
|
|
20
|
+
controller: AbortController
|
|
21
|
+
request: Request
|
|
22
|
+
args: Args
|
|
23
|
+
status: OutboxStatus
|
|
24
|
+
error?: unknown
|
|
25
|
+
retry: () => Promise<void>
|
|
26
|
+
settled: Promise<unknown>
|
|
27
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { HttpMethod } from './HttpMethod.ts'
|
|
2
2
|
import type { RemoteCallable } from './RemoteCallable.ts'
|
|
3
3
|
|
|
4
4
|
/*
|
|
@@ -9,6 +9,6 @@ so callers that need status / headers / body streaming or want to
|
|
|
9
9
|
implement custom error handling can opt out of the decode.
|
|
10
10
|
*/
|
|
11
11
|
export type RawRemoteFunction<Args> = RemoteCallable<Args, Response> & {
|
|
12
|
-
readonly method:
|
|
12
|
+
readonly method: HttpMethod
|
|
13
13
|
readonly url: string
|
|
14
14
|
}
|
|
@@ -2,8 +2,8 @@ import type { RpcOptions } from './RpcOptions.ts'
|
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
4
|
Call signature shared by RemoteFunction and RawRemoteFunction. The base
|
|
5
|
-
signature keeps `args` required so a schema'd
|
|
6
|
-
input; when `Args` admits undefined (no-input
|
|
5
|
+
signature keeps `args` required so a schema'd rpc can't silently drop its
|
|
6
|
+
input; when `Args` admits undefined (no-input rpcs) an intersected
|
|
7
7
|
optional-arg signature lets call sites write `fn()` instead of
|
|
8
8
|
`fn(undefined)`. Intersection rather than a bare conditional so the type
|
|
9
9
|
stays callable while `Args` is still generic (cache() invokes producers
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { ClientFlags } from './ClientFlags.ts'
|
|
2
|
-
import type {
|
|
2
|
+
import type { ErrorSpec } from './ErrorSpec.ts'
|
|
3
|
+
import type { HttpMethod } from './HttpMethod.ts'
|
|
4
|
+
import type { Outbox } from './Outbox.ts'
|
|
3
5
|
import type { RawRemoteFunction } from './RawRemoteFunction.ts'
|
|
4
6
|
import type { RemoteCallable } from './RemoteCallable.ts'
|
|
7
|
+
import type { RpcErrorGuard } from './RpcErrorGuard.ts'
|
|
5
8
|
import type { Subscribable } from './Subscribable.ts'
|
|
6
9
|
|
|
7
10
|
/*
|
|
@@ -25,23 +28,35 @@ HTTP rpc isn't the place for long-lived multi-publisher subscriptions.
|
|
|
25
28
|
`.fetch(req)` is the framework's request-dispatch entry point — used by
|
|
26
29
|
the router to invoke the handler from an incoming HTTP request, not
|
|
27
30
|
for user code.
|
|
28
|
-
`crossOrigin` (server-side only, set via the
|
|
29
|
-
mutating
|
|
31
|
+
`crossOrigin` (server-side only, set via the rpc's opts) exempts a
|
|
32
|
+
mutating rpc from the router's same-origin CSRF gate, accepting browser
|
|
30
33
|
requests whose Origin doesn't match the app's own host.
|
|
31
34
|
*/
|
|
32
35
|
/*
|
|
33
|
-
A body
|
|
36
|
+
A body rpc (POST/PUT/PATCH) also accepts a FormData in place of typed args:
|
|
34
37
|
buildRpcRequest ships it as a multipart body and the server splits text fields
|
|
35
38
|
into args (still schema-validated) and File parts into files(). FormData is
|
|
36
39
|
stringly-typed, so this is the upload escape hatch — typed object args remain
|
|
37
40
|
the default for everything else.
|
|
38
41
|
*/
|
|
39
|
-
export type RemoteFunction<
|
|
40
|
-
|
|
42
|
+
export type RemoteFunction<
|
|
43
|
+
Args,
|
|
44
|
+
Return,
|
|
45
|
+
Errors extends ErrorSpec = Record<never, never>,
|
|
46
|
+
> = RemoteCallable<Args, Return> & {
|
|
47
|
+
readonly method: HttpMethod
|
|
41
48
|
readonly url: string
|
|
42
49
|
readonly clients: ClientFlags
|
|
43
50
|
readonly crossOrigin?: boolean
|
|
44
51
|
readonly raw: RawRemoteFunction<Args>
|
|
45
52
|
stream(args?: Args | FormData): Subscribable<Return>
|
|
46
53
|
fetch(request: Request): Promise<Response>
|
|
54
|
+
/* Type-guard a caught error against this rpc's declared `errors` (plus the framework
|
|
55
|
+
`'validation'` / `'queued'`): narrows `.kind` and, for a known kind, `.data` — the
|
|
56
|
+
per-rpc replacement for a global guard, since the error name → data type mapping
|
|
57
|
+
lives in the rpc's own spec. */
|
|
58
|
+
readonly isError: RpcErrorGuard<Errors>
|
|
59
|
+
/* Present only on a durable (`outbox`) RPC's client proxy: callable for the reactive
|
|
60
|
+
list of undelivered entries, with `retry()` to drain the queue. Undefined otherwise. */
|
|
61
|
+
readonly outbox?: Outbox<Args>
|
|
47
62
|
}
|
|
@@ -3,7 +3,7 @@ import type { TraceContext } from './TraceContext.ts'
|
|
|
3
3
|
/*
|
|
4
4
|
The request-scope facts the shared layer reads through requestScopeSlot:
|
|
5
5
|
trace position, elapsed ms since the scope opened (request start on the
|
|
6
|
-
server, navigation start in the browser), and the
|
|
6
|
+
server, navigation start in the browser), and the method+path that anchors log
|
|
7
7
|
lines. Resolved fresh per read so `elapsedMs` is current at the call.
|
|
8
8
|
*/
|
|
9
9
|
export type RequestScopeInfo = {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { HttpError } from '../HttpError.ts'
|
|
2
|
+
import type { ErrorSpec } from './ErrorSpec.ts'
|
|
3
|
+
import type { OutboxEntry } from './OutboxEntry.ts'
|
|
4
|
+
import type { StandardSchemaV1 } from './StandardSchemaV1.ts'
|
|
5
|
+
import type { ValidationErrorData } from './ValidationErrorData.ts'
|
|
6
|
+
|
|
7
|
+
/* The payload a declared error name carries: its data schema's inferred input, or
|
|
8
|
+
`unknown` for a nullary error (no data schema). */
|
|
9
|
+
type DeclaredErrorData<
|
|
10
|
+
Errors extends ErrorSpec,
|
|
11
|
+
Name extends keyof Errors,
|
|
12
|
+
> = Errors[Name]['data'] extends StandardSchemaV1
|
|
13
|
+
? StandardSchemaV1.InferInput<Errors[Name]['data']>
|
|
14
|
+
: unknown
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
A rpc's `isError` guard — branch a caught error on a kind THIS rpc knows, with the
|
|
18
|
+
data type narrowed from the rpc's own `errors` spec. `rpc.isError(err, 'outOfStock')`
|
|
19
|
+
narrows `.data` to that error's payload; the framework-reserved `'validation'` /
|
|
20
|
+
`'queued'` to their fixed shapes; any other string narrows `.kind` only (`.data`
|
|
21
|
+
stays `unknown` — it belongs to a different rpc, whose type isn't in scope here).
|
|
22
|
+
|
|
23
|
+
The declared-name overload comes first so an app error shadows the reserved ones on a
|
|
24
|
+
name collision, and so the kind argument autocompletes to the rpc's declared names.
|
|
25
|
+
*/
|
|
26
|
+
export interface RpcErrorGuard<Errors extends ErrorSpec> {
|
|
27
|
+
<Name extends keyof Errors & string>(
|
|
28
|
+
error: unknown,
|
|
29
|
+
kind: Name,
|
|
30
|
+
): error is HttpError & { kind: Name; data: DeclaredErrorData<Errors, Name> }
|
|
31
|
+
(
|
|
32
|
+
error: unknown,
|
|
33
|
+
kind: 'validation',
|
|
34
|
+
): error is HttpError & { kind: 'validation'; data: ValidationErrorData }
|
|
35
|
+
(
|
|
36
|
+
error: unknown,
|
|
37
|
+
kind: 'queued',
|
|
38
|
+
): error is HttpError & { kind: 'queued'; data: OutboxEntry<unknown> }
|
|
39
|
+
<Kind extends string>(error: unknown, kind: Kind): error is HttpError & { kind: Kind }
|
|
40
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* A
|
|
1
|
+
/* A rpc invoker mirrors the rpc's own call: a plain call decodes the body
|
|
2
2
|
(throws HttpError on non-2xx), `.raw` returns the Response untouched. Shared
|
|
3
3
|
by every name→callable RPC proxy (the CLI client, the test harness). */
|
|
4
4
|
export type RpcInvoker = ((args?: unknown) => Promise<unknown>) & {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Mirror of the Standard Schema v1 spec interface (standardschema.dev). Any
|
|
3
3
|
library that implements the spec — zod, valibot, arktype, etc. — produces
|
|
4
4
|
values whose `~standard` property structurally matches this shape, so users
|
|
5
|
-
can pass their existing schemas to
|
|
5
|
+
can pass their existing schemas to rpc helpers without an adapter.
|
|
6
6
|
|
|
7
7
|
Kept inline (no `@standard-schema/spec` dep) because the spec is type-only
|
|
8
8
|
and tiny; adding a package for ~30 lines of interface would be churn. The
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from './StandardSchemaV1.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
The `data` payload an input/file validation failure carries on its HttpError —
|
|
5
|
+
`kind: 'validation'`, status 422. `issues` is the raw Standard Schema issue list
|
|
6
|
+
(every message + full path); `fields` is the form-friendly top-level-field →
|
|
7
|
+
first-message map (fieldErrorsFromIssues). Because a throw can't carry the rpc's
|
|
8
|
+
per-kind type to the catch, `HttpError.data` is typed `unknown` — narrow it with
|
|
9
|
+
this when `err.kind === 'validation'`:
|
|
10
|
+
|
|
11
|
+
if (err instanceof HttpError && err.kind === 'validation') {
|
|
12
|
+
const { fields } = err.data as ValidationErrorData
|
|
13
|
+
// fields.email === 'Required', …
|
|
14
|
+
}
|
|
15
|
+
*/
|
|
16
|
+
// @documentation response
|
|
17
|
+
export type ValidationErrorData = {
|
|
18
|
+
readonly issues: readonly StandardSchemaV1.Issue[]
|
|
19
|
+
readonly fields: Record<string, string>
|
|
20
|
+
}
|
package/src/lib/shared/url.ts
CHANGED
|
@@ -11,7 +11,7 @@ type Query = Record<string, QueryValue>
|
|
|
11
11
|
/*
|
|
12
12
|
Augmentable rpc map. Codegen emits a `declare module '<importName>/shared/url'`
|
|
13
13
|
block filling this with `'/rpc/<file>': args` for each query-carrying (GET)
|
|
14
|
-
|
|
14
|
+
rpc, so url('/rpc/search', { q }) types its args against the rpc's own
|
|
15
15
|
signature. Empty by default — an absent entry falls through to the page/asset
|
|
16
16
|
branch, so the helper works before the generated d.ts lands.
|
|
17
17
|
*/
|
|
@@ -47,7 +47,7 @@ no bracket segments yields {}, which collapses the params slot away. The
|
|
|
47
47
|
catch-all branch recurses on its head too, so `[name]` segments before a
|
|
48
48
|
`[...rest]` are kept.
|
|
49
49
|
*/
|
|
50
|
-
type PathParams<P extends string> = P extends `${infer Head}[...${infer Rest}]${infer Tail}`
|
|
50
|
+
export type PathParams<P extends string> = P extends `${infer Head}[...${infer Rest}]${infer Tail}`
|
|
51
51
|
? PathParams<Head> & { [K in Rest]: string | number } & PathParams<Tail>
|
|
52
52
|
: P extends `${string}[${infer Name}]${infer Tail}`
|
|
53
53
|
? { [K in Name]: string | number } & PathParams<Tail>
|
|
@@ -59,7 +59,7 @@ Resolves any in-app URL to its base-correct, typed form — the single chokepoin
|
|
|
59
59
|
so a project mounted under APP_URL's subpath (e.g. /v2) generates links, asset
|
|
60
60
|
refs, and rpc hrefs that stay within the mount. Three disjoint URL kinds, keyed
|
|
61
61
|
off the path:
|
|
62
|
-
- rpc (flat /rpc/*, present in RpcRoutes): the
|
|
62
|
+
- rpc (flat /rpc/*, present in RpcRoutes): the rpc's args, serialised to query.
|
|
63
63
|
- page route (has [name] segments): path params, then optional query.
|
|
64
64
|
- asset / paramless / raw: bare path, then optional query.
|
|
65
65
|
External URLs (scheme-qualified or protocol-relative) skip the base untouched,
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { carriesBodyArgs } from './carriesBodyArgs.ts'
|
|
2
|
-
import {
|
|
2
|
+
import { detectRpcMethod } from './detectRpcMethod.ts'
|
|
3
3
|
import { fileStem } from './fileStem.ts'
|
|
4
4
|
import { rpcUrlForFile } from './rpcUrlForFile.ts'
|
|
5
5
|
import { writeDts } from './writeDts.ts'
|
|
6
6
|
|
|
7
7
|
/*
|
|
8
8
|
Emits a `.d.ts` that augments abide's `RpcRoutes` interface with one entry per
|
|
9
|
-
query-carrying $rpc
|
|
10
|
-
the
|
|
11
|
-
a url() can carry a query string but not a request body, so a POST
|
|
12
|
-
URL form. `RpcArgs` lifts the args type out of the
|
|
9
|
+
query-carrying $rpc rpc, so `url('/rpc/search', { q })` types its args against
|
|
10
|
+
the rpc's own signature. Only GET/DELETE/HEAD (non-body) rpcs are included —
|
|
11
|
+
a url() can carry a query string but not a request body, so a POST rpc has no
|
|
12
|
+
URL form. `RpcArgs` lifts the args type out of the rpc's RemoteFunction
|
|
13
13
|
(dropping the FormData upload variant); the file path resolves the export by
|
|
14
14
|
its filename, the abide one-export-per-file convention. Written to
|
|
15
15
|
`src/.abide/rpc.d.ts` so the consumer's src tsconfig include picks it up, keyed
|
|
@@ -28,8 +28,8 @@ export async function writeRpcDts({
|
|
|
28
28
|
}): Promise<void> {
|
|
29
29
|
const lines = await Promise.all(
|
|
30
30
|
rpcFiles.map(async (file) => {
|
|
31
|
-
const method =
|
|
32
|
-
// A body
|
|
31
|
+
const method = detectRpcMethod(await Bun.file(`${rpcDir}/${file}`).text())
|
|
32
|
+
// A body rpc's args can't ride a URL — leave it out of the url() rpc map.
|
|
33
33
|
if (!method || carriesBodyArgs(method)) {
|
|
34
34
|
return undefined
|
|
35
35
|
}
|
|
@@ -5,9 +5,9 @@ import { writeDts } from './writeDts.ts'
|
|
|
5
5
|
|
|
6
6
|
/*
|
|
7
7
|
Emits a `.d.ts` that augments createTestApp's `RpcClient` interface with one
|
|
8
|
-
entry per $rpc
|
|
9
|
-
resolves at runtime). Each entry lifts the
|
|
10
|
-
its RemoteFunction so `app.rpc.getProduct({ id })` types against the
|
|
8
|
+
entry per $rpc rpc, keyed by command name (the same key `app.rpc.<name>`
|
|
9
|
+
resolves at runtime). Each entry lifts the rpc's args + resolved return out of
|
|
10
|
+
its RemoteFunction so `app.rpc.getProduct({ id })` types against the rpc's own
|
|
11
11
|
signature — args in, decoded body out, plus `.raw` for the Response. `RpcArgs`
|
|
12
12
|
drops the FormData upload variant exactly as writeRpcDts does; `RpcReturn`
|
|
13
13
|
reads the resolved body type. Written to `src/.abide/testRpc.d.ts` so the
|
|
@@ -25,7 +25,7 @@ import { rpc } from '../../_virtual/rpc.ts'
|
|
|
25
25
|
import { shell } from '../../_virtual/shell.ts'
|
|
26
26
|
// @ts-expect-error virtual module resolved by abideResolverPlugin
|
|
27
27
|
import { sockets } from '../../_virtual/sockets.ts'
|
|
28
|
-
import {
|
|
28
|
+
import { rpcRegistry } from '../server/rpc/rpcRegistry.ts'
|
|
29
29
|
import { createServer } from '../server/runtime/createServer.ts'
|
|
30
30
|
import { ensureRegistriesLoaded } from '../server/runtime/registryManifests.ts'
|
|
31
31
|
import { requestContext } from '../server/runtime/requestContext.ts'
|
|
@@ -46,8 +46,8 @@ import type { RemoteFunction } from '../shared/types/RemoteFunction.ts'
|
|
|
46
46
|
import { createTestSocketChannel } from './createTestSocketChannel.ts'
|
|
47
47
|
|
|
48
48
|
/*
|
|
49
|
-
Augmentable
|
|
50
|
-
build's writeTestRpcDts / writeTestSocketsDts emit one entry per
|
|
49
|
+
Augmentable rpc/socket maps for `app.rpc.<rpc>` / `app.sockets.<name>`. The
|
|
50
|
+
build's writeTestRpcDts / writeTestSocketsDts emit one entry per rpc/socket
|
|
51
51
|
(into src/.abide/), so the keys + signatures are the project's real surface
|
|
52
52
|
with no imports. Empty here; types arrive once the app has been built. Mirrors
|
|
53
53
|
url's RpcRoutes / health's AppHealthMap.
|
|
@@ -58,7 +58,7 @@ export interface RpcClient {}
|
|
|
58
58
|
// biome-ignore lint/suspicious/noEmptyInterface: augmented by the generated testSockets.d.ts
|
|
59
59
|
export interface SocketClient {}
|
|
60
60
|
|
|
61
|
-
/* The booted app under test. Every named subsystem is reachable as the
|
|
61
|
+
/* The booted app under test. Every named subsystem is reachable as the rpc
|
|
62
62
|
you call, the socket you iterate, or a path you fetch — over the real
|
|
63
63
|
server, so the full pipeline (CSRF, cookies, base path) runs. */
|
|
64
64
|
export type TestApp = {
|
|
@@ -69,7 +69,7 @@ export type TestApp = {
|
|
|
69
69
|
matches routes at raw paths — the APP_URL mount base is stripped by an
|
|
70
70
|
external proxy in production, absent here — so paths carry no base. */
|
|
71
71
|
fetch: (path: string, init?: RequestInit) => Promise<Response>
|
|
72
|
-
/*
|
|
72
|
+
/* Rpc calls over HTTP, keyed by command name: `app.rpc.getProduct({ id })`. */
|
|
73
73
|
rpc: RpcClient
|
|
74
74
|
/* Sockets keyed by name: `app.sockets.ticker` is the Socket — iterate it for
|
|
75
75
|
the live stream, `.tail(n)` to seed, `.publish(m)` to send. */
|
|
@@ -88,7 +88,7 @@ export type TestApp = {
|
|
|
88
88
|
Boots the real app on an ephemeral port — the same wiring serverEntry performs,
|
|
89
89
|
minus the standalone-binary env layers. Imports the framework's virtual
|
|
90
90
|
manifests (resolved by abideResolverPlugin, registered via `abide/preload`
|
|
91
|
-
in the consumer's bunfig), so the routes,
|
|
91
|
+
in the consumer's bunfig), so the routes, rpcs, and sockets are the project's
|
|
92
92
|
real surface, not a fixture. Pass nothing: `await createTestApp()` is the app
|
|
93
93
|
exactly as `bun start` would serve it.
|
|
94
94
|
|
|
@@ -138,19 +138,19 @@ export async function createTestApp(): Promise<TestApp> {
|
|
|
138
138
|
return fetch(`${origin}${path}`, init)
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
/*
|
|
141
|
+
/* Rpc modules loaded once so the registry holds every RemoteFunction; the
|
|
142
142
|
proxy maps command name → an HTTP call against the booted server. */
|
|
143
143
|
await ensureRegistriesLoaded()
|
|
144
144
|
const remotes = new Map<string, RemoteFunction<unknown, unknown>>(
|
|
145
|
-
Array.from(
|
|
145
|
+
Array.from(rpcRegistry.values()).map((entry) => [
|
|
146
146
|
commandNameForUrl(entry.remote.url),
|
|
147
147
|
entry.remote,
|
|
148
148
|
]),
|
|
149
149
|
)
|
|
150
150
|
|
|
151
151
|
function send(remote: RemoteFunction<unknown, unknown>, args: unknown): Promise<Response> {
|
|
152
|
-
/* Same-origin Origin header so the CSRF gate admits mutating
|
|
153
|
-
server serves
|
|
152
|
+
/* Same-origin Origin header so the CSRF gate admits mutating rpcs; the
|
|
153
|
+
server serves rpcs at their raw url (no mount base applied here). */
|
|
154
154
|
const request = buildRpcRequest({
|
|
155
155
|
method: remote.method,
|
|
156
156
|
url: remote.url,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
The LSP semantic-tokens legend abide lsp advertises, and the decoder from
|
|
3
|
+
TypeScript's encoded classifications to legend names. TypeScript encodes a
|
|
4
|
+
classification as `((tokenType + 1) << 8) + modifierBitset`; its TokenType and
|
|
5
|
+
TokenModifier enums fix the orders below. `keyword`/`operator` carry the `{#…}`
|
|
6
|
+
block framing the structural tokenizer emits.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/* TS TokenType order (class=0 … member=11) → LSP token-type name. */
|
|
10
|
+
const TS_TYPE_TO_LSP = [
|
|
11
|
+
'class',
|
|
12
|
+
'enum',
|
|
13
|
+
'interface',
|
|
14
|
+
'namespace',
|
|
15
|
+
'typeParameter',
|
|
16
|
+
'type',
|
|
17
|
+
'parameter',
|
|
18
|
+
'variable',
|
|
19
|
+
'enumMember',
|
|
20
|
+
'property',
|
|
21
|
+
'function',
|
|
22
|
+
'method',
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
/* TS TokenModifier order (declaration=bit 0 … local=bit 5). */
|
|
26
|
+
const TS_MODIFIERS = ['declaration', 'static', 'async', 'readonly', 'defaultLibrary', 'local']
|
|
27
|
+
|
|
28
|
+
export const ABIDE_SEMANTIC_TOKENS_LEGEND = {
|
|
29
|
+
tokenTypes: [...TS_TYPE_TO_LSP, 'keyword', 'operator'],
|
|
30
|
+
tokenModifiers: TS_MODIFIERS,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Decodes one TypeScript encoded classification into legend names. */
|
|
34
|
+
export function mapTsClassification(
|
|
35
|
+
classification: number,
|
|
36
|
+
): { type: string; modifiers: string[] } | undefined {
|
|
37
|
+
const tokenType = (classification >> 8) - 1
|
|
38
|
+
const type = TS_TYPE_TO_LSP[tokenType]
|
|
39
|
+
if (type === undefined) {
|
|
40
|
+
return undefined
|
|
41
|
+
}
|
|
42
|
+
const modifierSet = classification & 255
|
|
43
|
+
const modifiers = TS_MODIFIERS.filter((_, bit) => (modifierSet & (1 << bit)) !== 0)
|
|
44
|
+
return { type, modifiers }
|
|
45
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { relative } from 'node:path'
|
|
2
2
|
import type { BunPlugin } from 'bun'
|
|
3
|
+
import { fileName } from '../../shared/fileName.ts'
|
|
3
4
|
import { messageFromError } from '../../shared/messageFromError.ts'
|
|
4
5
|
import { AbideCompileError } from './AbideCompileError.ts'
|
|
5
6
|
import { analyzeComponent } from './analyzeComponent.ts'
|
|
@@ -42,7 +43,7 @@ export const abideUiPlugin: BunPlugin = {
|
|
|
42
43
|
build.onLoad({ filter: /\.abide$/ }, async (args) => {
|
|
43
44
|
const source = await Bun.file(args.path).text()
|
|
44
45
|
const moduleId = relative(nearestProjectRoot(args.path, process.cwd()), args.path)
|
|
45
|
-
const isLayout = (args.path
|
|
46
|
+
const isLayout = fileName(args.path) === 'layout.abide'
|
|
46
47
|
/* Bun frames a plugin throw at `<file>:0` regardless of the real spot, so
|
|
47
48
|
carry the component path + resolved line:col in the message — otherwise a
|
|
48
49
|
control-flow / compile error reads as `:0` and (in deep imports) can look
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { resolveBranches } from './resolveBranches.ts'
|
|
2
|
+
import type { TemplateNode } from './types/TemplateNode.ts'
|
|
3
|
+
|
|
4
|
+
/* The structural shape of an `await` block, resolved ONCE from the node so the build and
|
|
5
|
+
SSR back-ends share one reading of it and only own emission. Both previously recomputed
|
|
6
|
+
branch resolution, the `finallyChildren` default, the blocking-vs-streaming split of the
|
|
7
|
+
resolved content/binding, and the surface-the-rejection rule — the parts that can silently
|
|
8
|
+
diverge. Same single-source-of-truth model the positional walks already use. */
|
|
9
|
+
export type AwaitPlan = {
|
|
10
|
+
/* `then` on the tag → blocking: no pending branch, the children ARE the resolved content
|
|
11
|
+
(settled before first flush). Absent → streaming: pending flushes, `then` resolves later. */
|
|
12
|
+
blocking: boolean
|
|
13
|
+
/* The pending content (streaming) — `[]` when blocking. */
|
|
14
|
+
pending: TemplateNode[]
|
|
15
|
+
/* The resolved content + its bound name: the children-minus-branch bound to `node.as`
|
|
16
|
+
when blocking, the `then` branch bound to its `as` when streaming. `_value` default. */
|
|
17
|
+
resolvedChildren: TemplateNode[]
|
|
18
|
+
resolvedAs: string
|
|
19
|
+
/* The catch content + its bound name (`_error` default); empty when no catch branch. */
|
|
20
|
+
catchChildren: TemplateNode[]
|
|
21
|
+
catchAs: string
|
|
22
|
+
finallyChildren: TemplateNode[]
|
|
23
|
+
/* Neither catch nor finally → a rejection must surface (re-throw / `undefined` catch thunk)
|
|
24
|
+
instead of rendering an empty branch. */
|
|
25
|
+
surfaceRejection: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Resolves an `await` node's branches into the shared structural plan. */
|
|
29
|
+
export function awaitPlan(node: Extract<TemplateNode, { kind: 'await' }>): AwaitPlan {
|
|
30
|
+
const [thenBranch, catchBranch, finallyBranch] = resolveBranches(
|
|
31
|
+
node,
|
|
32
|
+
'then',
|
|
33
|
+
'catch',
|
|
34
|
+
'finally',
|
|
35
|
+
)
|
|
36
|
+
const finallyChildren = finallyBranch?.children ?? []
|
|
37
|
+
const nonBranch = node.children.filter((child) => child.kind !== 'branch')
|
|
38
|
+
return {
|
|
39
|
+
blocking: node.blocking,
|
|
40
|
+
pending: node.blocking ? [] : nonBranch,
|
|
41
|
+
resolvedChildren: node.blocking ? nonBranch : (thenBranch?.children ?? []),
|
|
42
|
+
resolvedAs: (node.blocking ? node.as : thenBranch?.as) ?? '_value',
|
|
43
|
+
catchChildren: catchBranch?.children ?? [],
|
|
44
|
+
catchAs: catchBranch?.as ?? '_error',
|
|
45
|
+
finallyChildren,
|
|
46
|
+
surfaceRejection: catchBranch === undefined && finallyChildren.length === 0,
|
|
47
|
+
}
|
|
48
|
+
}
|