@abide/abide 0.49.0 → 0.50.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 +70 -52
- package/CHANGELOG.md +177 -0
- package/README.md +12 -7
- package/package.json +14 -5
- package/src/abideLsp.ts +5 -6
- package/src/abideResolverPlugin.ts +197 -169
- package/src/build.ts +79 -56
- package/src/buildDisconnected.ts +0 -2
- package/src/checkAbide.ts +12 -2
- package/src/devEntry.ts +113 -28
- package/src/discoveryEntry.ts +3 -2
- package/src/lib/bundle/disconnected.abide +69 -73
- package/src/lib/bundle/infoPlist.ts +13 -7
- package/src/lib/bundle/spawnEmbeddedServer.ts +13 -4
- package/src/lib/bundle/waitForServer.ts +6 -1
- package/src/lib/mcp/mcpTools.ts +15 -6
- package/src/lib/server/error.ts +6 -6
- package/src/lib/server/json.ts +17 -3
- package/src/lib/server/rpc/defineRpc.ts +58 -24
- package/src/lib/server/rpc/parseArgs.ts +98 -13
- package/src/lib/server/rpc/resolveRpcJsonSchema.ts +28 -0
- package/src/lib/server/rpc/types/RpcHelper.ts +75 -105
- package/src/lib/server/rpc/types/RpcRegistryEntry.ts +24 -9
- package/src/lib/server/rpc/validationError.ts +1 -1
- package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +0 -15
- package/src/lib/server/runtime/buildCacheSnapshot.ts +10 -9
- package/src/lib/server/runtime/buildInspectorSurface.ts +6 -4
- package/src/lib/server/runtime/buildOpenApiSpec.ts +55 -11
- package/src/lib/server/runtime/buildPreloadManifest.ts +12 -10
- package/src/lib/server/runtime/createAppAssetServer.ts +18 -15
- package/src/lib/server/runtime/createAppRouteResolver.ts +145 -0
- package/src/lib/server/runtime/createPlumbingRouter.ts +212 -0
- package/src/lib/server/runtime/createRouteDispatcher.ts +4 -10
- package/src/lib/server/runtime/createServer.ts +120 -324
- package/src/lib/server/runtime/createUiPageRenderer.ts +137 -26
- package/src/lib/server/runtime/devClientFingerprint.ts +19 -34
- package/src/lib/server/runtime/installAmbientScopeStore.ts +30 -9
- package/src/lib/server/runtime/logExposedSurfaces.ts +8 -7
- package/src/lib/server/runtime/pathStore.ts +15 -0
- package/src/lib/server/runtime/registryManifests.ts +1 -1
- package/src/lib/server/runtime/renderCellBarrierStore.ts +15 -0
- package/src/lib/server/runtime/runWithRequestScope.ts +3 -0
- package/src/lib/server/runtime/serializeCacheSnapshot.ts +6 -4
- package/src/lib/server/runtime/snapshotEntryFromCache.ts +14 -14
- package/src/lib/server/runtime/streamCacheResolutions.ts +14 -7
- package/src/lib/server/runtime/streamFromIterator.ts +30 -2
- package/src/lib/server/runtime/textResponse.ts +23 -0
- package/src/lib/server/runtime/types/DevReloadStamp.ts +6 -10
- package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
- package/src/lib/server/runtime/types/RequestStore.ts +27 -0
- package/src/lib/server/runtime/warnUnguardedMcp.ts +15 -5
- package/src/lib/server/sockets/createSocketDispatcher.ts +9 -11
- package/src/lib/server/sockets/defineSocket.ts +5 -4
- package/src/lib/server/sse.ts +5 -1
- package/src/lib/shared/ASYNC_CELL.ts +5 -0
- package/src/lib/shared/DEV_REBUILD_PATH.ts +6 -0
- package/src/lib/shared/HTTP_METHODS.ts +6 -0
- package/src/lib/shared/HttpError.ts +1 -5
- package/src/lib/shared/MCP_PATH.ts +6 -0
- package/src/lib/shared/PROXIED_SERVER_SUBDIRS.ts +15 -0
- package/src/lib/shared/REF_JSON_TAGS.ts +13 -1
- package/src/lib/shared/RPC_SHIM_GLOBALS.ts +9 -0
- package/src/lib/shared/activeCacheStore.ts +1 -0
- package/src/lib/shared/activePage.ts +1 -0
- package/src/lib/shared/buildArtifact.ts +4 -1
- package/src/lib/shared/buildSocketOverChannel.ts +4 -3
- package/src/lib/shared/bundleGraphFromMetafile.ts +68 -0
- package/src/lib/shared/cache.ts +156 -131
- package/src/lib/shared/changeAffectsClient.ts +12 -7
- package/src/lib/shared/createCacheStore.ts +31 -8
- package/src/lib/shared/createLifecycleChannel.ts +7 -1
- package/src/lib/shared/createReachable.ts +47 -78
- package/src/lib/shared/createRemoteFunction.ts +52 -30
- package/src/lib/shared/createRpcServerProgram.ts +820 -0
- package/src/lib/shared/decodeRefJson.ts +4 -4
- package/src/lib/shared/decodeResponse.ts +2 -2
- package/src/lib/shared/decodeWireBody.ts +35 -0
- package/src/lib/shared/detectRpcMethod.ts +8 -1
- package/src/lib/shared/done.ts +8 -2
- package/src/lib/shared/encodeRefJson.ts +4 -4
- package/src/lib/shared/encodeWireBody.ts +18 -0
- package/src/lib/{server/rpc → shared}/fieldErrorsFromIssues.ts +5 -1
- package/src/lib/shared/generateDeclarations.ts +72 -0
- package/src/lib/shared/hasSeedableRequest.ts +25 -0
- package/src/lib/shared/hydrationWindow.ts +53 -0
- package/src/lib/shared/isAsyncCell.ts +11 -0
- package/src/lib/shared/isAsyncIterable.ts +8 -0
- package/src/lib/shared/isSubscribable.ts +3 -3
- package/src/lib/shared/isThenable.ts +9 -0
- package/src/lib/shared/jsonSchemaForType.ts +258 -0
- package/src/lib/shared/lenientDecode.ts +15 -0
- package/src/lib/shared/loadProjectTsConfig.ts +28 -0
- package/src/lib/shared/markFrameworkSourcesIgnored.ts +1 -1
- package/src/lib/shared/matchRoute.ts +4 -14
- package/src/lib/shared/peek.ts +14 -0
- package/src/lib/shared/pending.ts +9 -6
- package/src/lib/shared/pendingAsyncCellsSlot.ts +13 -0
- package/src/lib/shared/prepareRpcModule.ts +91 -93
- package/src/lib/shared/prepareSocketModule.ts +3 -2
- package/src/lib/shared/probeRegistries.ts +5 -18
- package/src/lib/shared/reachable.ts +7 -10
- package/src/lib/shared/refresh.ts +12 -2
- package/src/lib/shared/refreshing.ts +8 -2
- package/src/lib/shared/resolvedCellsSlot.ts +13 -0
- package/src/lib/shared/reviveWireField.ts +49 -0
- package/src/lib/shared/reviveWireOutput.ts +36 -0
- package/src/lib/shared/rpcServerForRoot.ts +25 -0
- package/src/lib/shared/scanPages.ts +25 -0
- package/src/lib/shared/snapshotShippable.ts +8 -7
- package/src/lib/shared/streamedCellsSlot.ts +14 -0
- package/src/lib/shared/subscribableFromResponse.ts +4 -4
- package/src/lib/shared/subscribableProbes.ts +1 -1
- package/src/lib/shared/tailProbeSlot.ts +1 -1
- package/src/lib/shared/types/AsyncComputed.ts +20 -0
- package/src/lib/shared/types/AsyncState.ts +13 -0
- package/src/lib/shared/types/CacheEntry.ts +7 -7
- package/src/lib/shared/types/CacheOptions.ts +23 -37
- package/src/lib/shared/types/CachePolicy.ts +25 -0
- package/src/lib/shared/types/CacheSnapshotEntry.ts +6 -5
- package/src/lib/shared/types/ErrorJsonSchemas.ts +8 -0
- package/src/lib/shared/types/HttpMethod.ts +3 -1
- package/src/lib/shared/types/InputCoercion.ts +17 -0
- package/src/lib/shared/types/{Subscribable.ts → NamedAsyncIterable.ts} +1 -1
- package/src/lib/shared/types/OutputWirePlan.ts +17 -0
- package/src/lib/shared/types/PagesScan.ts +7 -0
- package/src/lib/shared/types/PendingAsyncCells.ts +10 -0
- package/src/lib/shared/types/RawRemoteFunction.ts +6 -0
- package/src/lib/shared/types/RemoteCallable.ts +8 -7
- package/src/lib/shared/types/RemoteFunction.ts +16 -15
- package/src/lib/shared/types/ResolvedCells.ts +11 -0
- package/src/lib/shared/types/ReturnBody.ts +15 -0
- package/src/lib/shared/types/RpcBuildStamps.ts +23 -0
- package/src/lib/shared/types/RpcErrorGuard.ts +3 -8
- package/src/lib/shared/types/Socket.ts +4 -4
- package/src/lib/shared/types/SsrPayload.ts +5 -1
- package/src/lib/shared/types/StreamPolicy.ts +11 -0
- package/src/lib/shared/types/StreamedCells.ts +19 -0
- package/src/lib/shared/types/StreamedResolution.ts +24 -6
- package/src/lib/shared/types/TailHooks.ts +1 -1
- package/src/lib/shared/types/WireKind.ts +11 -0
- package/src/lib/shared/validationHttpError.ts +33 -0
- package/src/lib/shared/warmSeedKey.ts +16 -0
- package/src/lib/shared/wireJsonReplacer.ts +30 -0
- package/src/lib/shared/writeRpcDts.ts +11 -1
- package/src/lib/shared/writeTestSocketsDts.ts +1 -1
- package/src/lib/test/createTestApp.ts +19 -1
- package/src/lib/ui/README.md +1 -1
- package/src/lib/ui/activePendingCells.ts +14 -0
- package/src/lib/ui/compile/BLOCK_KEYWORDS.ts +21 -0
- package/src/lib/ui/compile/SSR_ESCAPE.ts +4 -1
- package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +35 -8
- package/src/lib/ui/compile/abideUiPlugin.ts +19 -2
- package/src/lib/ui/compile/analyzeComponent.ts +89 -6
- package/src/lib/ui/compile/assignmentTargetNames.ts +54 -0
- package/src/lib/ui/compile/asyncInterpolationFields.ts +152 -0
- package/src/lib/ui/compile/asyncValuePositionError.ts +21 -0
- package/src/lib/ui/compile/asyncValuePositionInterpolations.ts +64 -0
- package/src/lib/ui/compile/attrLiftPosition.ts +18 -0
- package/src/lib/ui/compile/cachedSourceFile.ts +40 -0
- package/src/lib/ui/compile/catchBinding.ts +7 -5
- package/src/lib/ui/compile/classifyInterpolationType.ts +46 -0
- package/src/lib/ui/compile/collectAbideDiagnostics.ts +96 -0
- package/src/lib/ui/compile/compileComponent.ts +19 -3
- package/src/lib/ui/compile/compileModule.ts +27 -58
- package/src/lib/ui/compile/compileSSR.ts +51 -12
- package/src/lib/ui/compile/compileShadow.ts +145 -18
- package/src/lib/ui/compile/composeProps.ts +12 -2
- package/src/lib/ui/compile/createShadowLanguageService.ts +116 -68
- package/src/lib/ui/compile/createShadowProgram.ts +35 -6
- package/src/lib/ui/compile/declaredNames.ts +36 -0
- package/src/lib/ui/compile/desugarSignals.ts +418 -36
- package/src/lib/ui/compile/expressionIsPrefixEvaluable.ts +19 -0
- package/src/lib/ui/compile/generateBuild.ts +52 -9
- package/src/lib/ui/compile/generateSSR.ts +242 -43
- package/src/lib/ui/compile/hoistableAwaits.ts +193 -0
- package/src/lib/ui/compile/hoistableChildRenders.ts +112 -0
- package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +3 -1
- package/src/lib/ui/compile/interpolationClassifierForRoot.ts +37 -0
- package/src/lib/ui/compile/isSpuriousAsyncReadDiagnostic.ts +174 -0
- package/src/lib/ui/compile/liftAsyncSubExpressions.ts +166 -0
- package/src/lib/ui/compile/lowerAsyncInterpolations.ts +92 -0
- package/src/lib/ui/compile/lowerContext.ts +10 -0
- package/src/lib/ui/compile/lowerDocAccess.ts +26 -23
- package/src/lib/ui/compile/lowerScript.ts +40 -4
- package/src/lib/ui/compile/nodeAtShadowOffset.ts +28 -0
- package/src/lib/ui/compile/parseTemplate.ts +18 -1092
- package/src/lib/ui/compile/parseTemplateRecovering.ts +1385 -0
- package/src/lib/ui/compile/referencedIdentifiers.ts +35 -0
- package/src/lib/ui/compile/renameSignalRefs.ts +21 -22
- package/src/lib/ui/compile/seedTypeClassifierForRoot.ts +65 -0
- package/src/lib/ui/compile/shadowInterpolationClassifier.ts +47 -0
- package/src/lib/ui/compile/sourceFileOptionsSignature.ts +20 -0
- package/src/lib/ui/compile/structuralHeadTokens.ts +107 -0
- package/src/lib/ui/compile/templateSemanticTokens.ts +21 -0
- package/src/lib/ui/compile/templateStartOffset.ts +15 -0
- package/src/lib/ui/compile/tryPlan.ts +4 -3
- package/src/lib/ui/compile/types/AnalyzedComponent.ts +4 -0
- package/src/lib/ui/compile/types/AsyncInterpolationField.ts +19 -0
- package/src/lib/ui/compile/types/InterpolationClassifier.ts +12 -0
- package/src/lib/ui/compile/types/InterpolationKind.ts +7 -0
- package/src/lib/ui/compile/types/ParseDiagnostic.ts +8 -0
- package/src/lib/ui/compile/types/SeedTypeClassifier.ts +15 -0
- package/src/lib/ui/compile/types/TemplateNode.ts +36 -4
- package/src/lib/ui/compile/types/ValuePositionInterpolation.ts +13 -0
- package/src/lib/ui/compile/writtenTemplateNames.ts +84 -0
- package/src/lib/ui/computed.ts +28 -1
- package/src/lib/ui/createScope.ts +35 -68
- package/src/lib/ui/dom/anchoredBranch.ts +142 -0
- package/src/lib/ui/dom/appendText.ts +8 -3
- package/src/lib/ui/dom/awaitBlock.ts +43 -94
- package/src/lib/ui/dom/bindProp.ts +22 -0
- package/src/lib/ui/dom/bindableProp.ts +47 -0
- package/src/lib/ui/dom/cellPending.ts +24 -0
- package/src/lib/ui/dom/disposeRange.ts +2 -1
- package/src/lib/ui/dom/each.ts +26 -4
- package/src/lib/ui/dom/eachAsync.ts +39 -3
- package/src/lib/ui/dom/fillBoundary.ts +2 -3
- package/src/lib/ui/dom/fillRange.ts +4 -4
- package/src/lib/ui/dom/hydrate.ts +6 -12
- package/src/lib/ui/dom/isComment.ts +1 -1
- package/src/lib/ui/dom/matchingRangeClose.ts +29 -0
- package/src/lib/ui/dom/mount.ts +1 -2
- package/src/lib/ui/dom/mountChild.ts +9 -40
- package/src/lib/ui/dom/mountRange.ts +2 -3
- package/src/lib/ui/dom/mountStreamedChild.ts +84 -0
- package/src/lib/ui/dom/mountSwappableRange.ts +46 -4
- package/src/lib/ui/dom/mutateDocContainer.ts +65 -0
- package/src/lib/ui/dom/on.ts +2 -2
- package/src/lib/ui/dom/readCell.ts +40 -0
- package/src/lib/ui/dom/switchBlock.ts +30 -7
- package/src/lib/ui/dom/tryBlock.ts +230 -66
- package/src/lib/ui/dom/types/SwitchCase.ts +5 -1
- package/src/lib/ui/dom/when.ts +12 -2
- package/src/lib/ui/dom/withScope.ts +2 -2
- package/src/lib/ui/finalizeStreamedChildren.ts +89 -0
- package/src/lib/ui/flight.ts +56 -0
- package/src/lib/ui/html.ts +12 -1
- package/src/lib/ui/isolateCellBarrier.ts +17 -0
- package/src/lib/ui/linked.ts +48 -2
- package/src/lib/ui/remoteProxy.ts +84 -159
- package/src/lib/ui/renderChain.ts +49 -13
- package/src/lib/ui/renderToStream.ts +22 -18
- package/src/lib/ui/resumeSeedScript.ts +1 -1
- package/src/lib/ui/router.ts +63 -36
- package/src/lib/ui/runtime/AsyncCellError.ts +20 -0
- package/src/lib/ui/runtime/CELL_SEED.ts +14 -0
- package/src/lib/ui/runtime/CURRENT_BOUNDARY.ts +11 -0
- package/src/lib/ui/runtime/CURRENT_PATH.ts +29 -0
- package/src/lib/ui/runtime/RENDER.ts +10 -7
- package/src/lib/ui/runtime/RESUME.ts +4 -4
- package/src/lib/ui/runtime/STREAMED_CELLS.ts +57 -0
- package/src/lib/ui/runtime/ambientPathBacking.ts +32 -0
- package/src/lib/ui/runtime/applyPatchToTree.ts +1 -1
- package/src/lib/ui/runtime/blockId.ts +31 -0
- package/src/lib/ui/runtime/boundaryFor.ts +11 -0
- package/src/lib/ui/runtime/cellBarrierBacking.ts +30 -0
- package/src/lib/ui/runtime/createAsyncCell.ts +300 -0
- package/src/lib/ui/runtime/createDoc.ts +3 -42
- package/src/lib/ui/runtime/createEffectNode.ts +9 -0
- package/src/lib/ui/runtime/enterRenderPass.ts +5 -4
- package/src/lib/ui/runtime/flushEffects.ts +16 -6
- package/src/lib/ui/runtime/isAsyncFunction.ts +14 -0
- package/src/lib/ui/runtime/nextBlockId.ts +8 -7
- package/src/lib/ui/runtime/renderPath.ts +16 -0
- package/src/lib/ui/runtime/types/Boundary.ts +9 -0
- package/src/lib/ui/runtime/types/RenderContext.ts +9 -7
- package/src/lib/ui/runtime/types/SsrRender.ts +9 -2
- package/src/lib/ui/runtime/types/UiComponent.ts +1 -5
- package/src/lib/ui/runtime/withOptionalPath.ts +8 -0
- package/src/lib/ui/runtime/withPath.ts +16 -0
- package/src/lib/ui/runtime/withPathFrom.ts +20 -0
- package/src/lib/ui/seedStreamedResolution.ts +31 -6
- package/src/lib/ui/settleAsyncCells.ts +24 -0
- package/src/lib/ui/socketProxy.ts +1 -1
- package/src/lib/ui/startClient.ts +16 -31
- package/src/lib/ui/trackedComputed.ts +68 -0
- package/src/lib/ui/types/Scope.ts +8 -24
- package/src/lib/ui/watch.ts +3 -3
- package/src/serverEntry.ts +14 -0
- package/template/src/server/rpc/getHello.ts +15 -13
- package/template/test/app.test.ts +1 -1
- package/src/lib/server/runtime/devHotModuleResponse.ts +0 -41
- package/src/lib/shared/DEV_HOT_PREFIX.ts +0 -7
- package/src/lib/shared/UNREACHABLE_STATUSES.ts +0 -13
- package/src/lib/shared/hasReplayableRequest.ts +0 -17
- package/src/lib/shared/hydratingSlot.ts +0 -12
- package/src/lib/shared/outboxProbeSlot.ts +0 -20
- package/src/lib/shared/types/Outbox.ts +0 -9
- package/src/lib/shared/types/OutboxEntry.ts +0 -27
- package/src/lib/shared/types/SmartReadOptions.ts +0 -36
- package/src/lib/shared/wakeHydrationPeeks.ts +0 -20
- package/src/lib/ui/COMPONENT_WRAPPER_PREFIX.ts +0 -5
- package/src/lib/ui/compile/REACTIVE_CALLEES.ts +0 -11
- package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +0 -80
- package/src/lib/ui/compile/markupTokens.ts +0 -313
- package/src/lib/ui/compile/structuralBlockTokens.ts +0 -100
- package/src/lib/ui/dom/applyResolved.ts +0 -87
- package/src/lib/ui/dom/mutateDocArray.ts +0 -38
- package/src/lib/ui/dom/scopeLabel.ts +0 -21
- package/src/lib/ui/dom/text.ts +0 -20
- package/src/lib/ui/history.ts +0 -108
- package/src/lib/ui/installHotBridge.ts +0 -95
- package/src/lib/ui/installInspectorBridge.ts +0 -140
- package/src/lib/ui/outbox.ts +0 -35
- package/src/lib/ui/persist.ts +0 -115
- package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +0 -281
- package/src/lib/ui/rpcOutbox/outboxRegistry.ts +0 -69
- package/src/lib/ui/runtime/PATCH_BUS.ts +0 -28
- package/src/lib/ui/runtime/captureModelDoc.ts +0 -43
- package/src/lib/ui/runtime/hotInstances.ts +0 -10
- package/src/lib/ui/runtime/hotReloadEnabled.ts +0 -8
- package/src/lib/ui/runtime/hotReplace.ts +0 -38
- package/src/lib/ui/runtime/liveScopes.ts +0 -15
- package/src/lib/ui/runtime/localStoragePersistence.ts +0 -47
- package/src/lib/ui/runtime/registerHotInstance.ts +0 -23
- package/src/lib/ui/runtime/seedModelDoc.ts +0 -26
- package/src/lib/ui/runtime/types/HotInstance.ts +0 -22
- package/src/lib/ui/runtime/types/PatchEvent.ts +0 -16
- package/src/lib/ui/seedResolved.ts +0 -28
- package/src/lib/ui/sync.ts +0 -48
- package/src/lib/ui/types/History.ts +0 -14
- package/src/lib/ui/types/PersistHandle.ts +0 -11
- package/src/lib/ui/types/PersistenceStore.ts +0 -12
- package/src/lib/ui/types/ResolvedFrame.ts +0 -15
- package/src/lib/ui/types/SyncTransport.ts +0 -13
package/AGENTS.md
CHANGED
|
@@ -26,7 +26,7 @@ src/server/rpc/getMessages.ts
|
|
|
26
26
|
└─ OpenAPI operation in /openapi.json
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
A `schemas.input` (any Standard Schema library — zod, valibot, arktype,
|
|
30
30
|
unadapted) is the gate: it unlocks the CLI, and for read-only methods
|
|
31
31
|
(GET/HEAD) the MCP tool. A mutating method (POST/PUT/PATCH/DELETE) never
|
|
32
32
|
auto-exposes to MCP — it requires explicit `clients: { mcp: true }`.
|
|
@@ -76,35 +76,43 @@ For tests, add `preload = ["@abide/abide/preload"]` under `[test]` in
|
|
|
76
76
|
## Authoring contracts
|
|
77
77
|
|
|
78
78
|
**RPC** — the handler receives the schema-validated args
|
|
79
|
-
(`InferOutput<
|
|
79
|
+
(`InferOutput<schemas.input>`); typed generics on the helper are a compile error
|
|
80
80
|
— type the parameter, let the body infer. Inside it, `request()` / `cookies()`
|
|
81
81
|
/ `server()` read the request scope. Return `json(data)` (or `jsonl` / `sse`
|
|
82
|
-
for streams, `error` / `redirect`, or a raw `Response`). Options
|
|
83
|
-
`
|
|
84
|
-
kept out of the JSON-Schema projection)
|
|
85
|
-
`crossOrigin` (exempts a mutating rpc from the same-origin CSRF gate)
|
|
82
|
+
for streams, `error` / `redirect`, or a raw `Response`). Options are namespaced
|
|
83
|
+
(ADR-0020): `schemas: { input, output, files }` (`files` validates uploaded
|
|
84
|
+
`File` parts, kept out of the JSON-Schema projection); `clients: { browser, mcp,
|
|
85
|
+
cli }`; `crossOrigin` (exempts a mutating rpc from the same-origin CSRF gate);
|
|
86
86
|
`timeout` (handler deadline in ms → 504 on every surface, composed into
|
|
87
|
-
`request().signal`)
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
`request().signal`); `maxBodySize` (per-rpc 413 cap); on read helpers (GET/HEAD)
|
|
88
|
+
`cache: { ttl, tags, throttle, debounce, shared }` (the endpoint's
|
|
89
|
+
retention/refetch policy) and `stream: { n }` (replay depth). Kind-scoped by type:
|
|
90
|
+
`cache`/`stream` on a write is a compile error. Query args on GET/HEAD/DELETE travel as
|
|
91
|
+
strings — coerce in the schema (`z.coerce.number()`). Beyond scalars, a
|
|
92
|
+
type-directed wire codec (ADR-0028/0029) revives a top-level arg field into the
|
|
93
|
+
runtime value its declared type names — a numeric string → `number`/`bigint`, a
|
|
94
|
+
`Date` from an ISO string, a `Set` from a JSON array, a `Map` from an entries
|
|
95
|
+
array/object — resolved through the warm server program; reviving is fail-open
|
|
96
|
+
(an unrevivable value passes through as its JSON form). A body rpc also accepts
|
|
90
97
|
a `FormData` in place of typed args (the upload escape hatch): text fields
|
|
91
|
-
validate as args, `File` parts validate against `
|
|
92
|
-
|
|
93
|
-
**Consuming an rpc** — the bare call `fn(args
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
refresh)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
a no-op (one tab store). A read with no request in flight (e.g.
|
|
104
|
-
job)
|
|
105
|
-
resolves
|
|
106
|
-
in-process and its value is baked into the HTML so hydration starts warm
|
|
107
|
-
|
|
98
|
+
validate as args, `File` parts validate against `schemas.files`.
|
|
99
|
+
|
|
100
|
+
**Consuming an rpc** — the bare call `fn(args)` IS the smart read: cached,
|
|
101
|
+
coalesced, reactive, stale-while-revalidate for replayable (GET/HEAD) reads.
|
|
102
|
+
There are no call-site options (ADR-0020) — all retention/refetch policy is
|
|
103
|
+
declared once on the endpoint's `cache`/`stream`. `ttl` defaults to `Infinity`:
|
|
104
|
+
an entry is retained for its store's lifetime — the request on the server (a
|
|
105
|
+
non-shared read dies with the request), the tab on the client (until
|
|
106
|
+
invalidate/refresh); a write coalesces only (ttl 0, the mutation idiom).
|
|
107
|
+
`shared` selects the process-level store instead of the request-scoped default
|
|
108
|
+
(server); with the default `Infinity` ttl it memoises across requests (an
|
|
109
|
+
explicit `ttl` bounds it) — for an external endpoint, never per-user data; on
|
|
110
|
+
the client it is a no-op (one tab store). A read with no request in flight (e.g.
|
|
111
|
+
a background job) resolves against the process-level store and coalesces only
|
|
112
|
+
(so it can't leak forever). During SSR the same call — any method — resolves
|
|
113
|
+
in-process and its value is baked into the HTML so hydration starts warm without
|
|
114
|
+
a re-fetch (an inline write seeds too, ADR-0036; only unprompted refetch stays
|
|
115
|
+
GET/HEAD) — there is no `cache()` wrapper; the bare call carries the caching. Around it:
|
|
108
116
|
`fn.raw(args, init?)` returns the raw `Response` (per-call transport options —
|
|
109
117
|
`signal`, `headers`, `keepalive`, … — live here); `fn.refresh(args?)`
|
|
110
118
|
refetches keeping the stale value visible; `fn.patch(args?, updater)` mutates
|
|
@@ -113,24 +121,16 @@ it synchronously; `fn.pending(args?)` / `fn.refreshing(args?)` are reactive
|
|
|
113
121
|
probes; `fn.error(args?)` is the rpc's last typed error; `fn.watch(args?,
|
|
114
122
|
handler)` pipes each resolved value to a handler (client-only; SSR-inert);
|
|
115
123
|
`fn.isError(e, kind?)` type-guards a caught error against the rpc's declared
|
|
116
|
-
error kinds
|
|
124
|
+
error kinds. A
|
|
117
125
|
handler that returns `jsonl()`/`sse()` makes the bare call return a
|
|
118
|
-
`
|
|
126
|
+
`NamedAsyncIterable` (`for await` it) — detected at build, nothing to declare;
|
|
119
127
|
awaiting a streaming call is a compile error.
|
|
120
128
|
|
|
121
129
|
**Typed errors** — declare a constructor with
|
|
122
130
|
`error.typed(name, status, schema?)` and `return` it from the handler; the
|
|
123
131
|
client's `HttpError` then carries `kind` (the name) and `data` (the schema's
|
|
124
132
|
payload), narrowed via `rpc.isError`. The framework reserves
|
|
125
|
-
`kind: 'validation'` (422, `data: ValidationErrorData`)
|
|
126
|
-
(a durable call parked while unreachable; `data` is the parked `OutboxEntry`).
|
|
127
|
-
|
|
128
|
-
**Durable outbox** — an `outbox: true` (mutating) rpc still fetches and throws
|
|
129
|
-
normally, but an unreachable server (transport failure or 502/503/504/52x)
|
|
130
|
-
parks the request for replay as a side-effect and throws `kind: 'queued'`;
|
|
131
|
-
once a backlog exists new calls park straight to the tail so writes stay
|
|
132
|
-
ordered. Nothing auto-drains: replay via `rpc.outbox.retry()` or the global
|
|
133
|
-
`outbox.retry()`; cancel via an entry's `controller.abort()`.
|
|
133
|
+
`kind: 'validation'` (422, `data: ValidationErrorData`).
|
|
134
134
|
|
|
135
135
|
**Socket** — `socket<T>(opts)` or `socket({ schema, … })` (with a schema, `T`
|
|
136
136
|
infers and publishes validate). Options: `tail` (retained frames, default 1 —
|
|
@@ -138,8 +138,9 @@ infers and publishes validate). Options: `tail` (retained frames, default 1 —
|
|
|
138
138
|
`clientPublish` (accept browser/HTTP publishes, off by default), `clients`
|
|
139
139
|
(mcp/cli exposure; a schema flips both on by default). The socket IS the
|
|
140
140
|
`AsyncIterable` — iterating is the live stream, no replay. Members:
|
|
141
|
-
`
|
|
142
|
-
subscribers, client sends a validated `pub` frame),
|
|
141
|
+
`publish(msg)` (isomorphic — mirrors Bun's `server.publish()`; server fans out
|
|
142
|
+
in-process + to remote subscribers, client sends a validated `pub` frame),
|
|
143
|
+
`tail(count?)` (a
|
|
143
144
|
subscription seeded with retained frames), `peek()` (latest retained frame),
|
|
144
145
|
`refresh()` (drop local frames and re-pull the server tail; server-side
|
|
145
146
|
no-op), `watch(handler)` ≡ `watch(socket, handler)` (client-only, SSR-inert),
|
|
@@ -195,7 +196,8 @@ Bindings and directives (the attribute kinds `readAttributes` parses):
|
|
|
195
196
|
|
|
196
197
|
| Form | Spec |
|
|
197
198
|
| --- | --- |
|
|
198
|
-
| `{expr}` | Text interpolation, escaped; a snippet or `html`-branded value mounts as nodes |
|
|
199
|
+
| `{expr}` | Text interpolation, escaped; a snippet or `html`-branded value mounts as nodes. Type-directed (ADR-0032): a `Promise`/`AsyncIterable`-typed `{expr}` (or an async sub-expression) lifts to a peek-cell — `undefined` while pending (composes with `??`/`?.`), then the resolved value / latest frame; a plain value binds directly |
|
|
200
|
+
| `{await expr}` | Explicit **blocking** await — renders the awaited value inline during SSR. Valid in every position (ADR-0032): content, an attribute, an `{#if}`/`{#switch}` subject, a `{#for}` source |
|
|
199
201
|
| `name={expr}` | Attribute/prop bound to an expression |
|
|
200
202
|
| `name="a {expr} b"` | Interpolated attribute — a literal `{` in a quoted value always interpolates (write `{` for a literal brace) |
|
|
201
203
|
| `{...expr}` | Spread — props onto a component, attributes onto a native element (rejected on `<template>`) |
|
|
@@ -203,6 +205,7 @@ Bindings and directives (the attribute kinds `readAttributes` parses):
|
|
|
203
205
|
| `bind:value={cell}` | Two-way input/select/textarea binding. `<input type="number"/"range">` writes back a number; `<select>` re-applies against late-mounting options and `<select multiple>` binds an array of selected values |
|
|
204
206
|
| `bind:value={{ get, set }}` | Writable-computed binding: read via `get()`, write via `set(next)` |
|
|
205
207
|
| `bind:checked={cell}` / `bind:group={cell}` | Checkbox boolean / radio-group value (SSR emits boolean attributes bare — `checked`, `open`, `selected` on the matching option) |
|
|
208
|
+
| `bind:prop={target}` (on a component) | Two-way prop binding — the same `target` forms as an element bind (an lvalue or `{ get, set }`). The child reads `prop` normally; if it writes `prop` or forwards it to another `bind:`, those writes flow back to `target`. Bindability is usage-inferred (no child-side marker): a prop only read stays read-only, and a `bind:prop` whose child never writes is simply one-way |
|
|
206
209
|
| `class:name={cond}` | Toggles a class; merges with a reactive `class` base in one effect |
|
|
207
210
|
| `style:property={value}` | Sets one style property; merges with a reactive `style` base |
|
|
208
211
|
| `attach={fn}` | Runs `fn(element)` at build time; an optional returned teardown runs on dispose |
|
|
@@ -220,6 +223,15 @@ error):
|
|
|
220
223
|
| `{#try}…{:catch e}…{:finally}…{/try}` | Synchronous error boundary around a build/reactive throw |
|
|
221
224
|
| `{#snippet name(args)}…{/snippet}` | Declares a reusable builder, called as an interpolation: `{name(args)}`; a snippet value passes through props like any other value |
|
|
222
225
|
|
|
226
|
+
A `Promise`/`AsyncIterable` (or an async sub-expression) lifts to a peek-cell in
|
|
227
|
+
**every** position (ADR-0032) — content, an attribute, an `{#if}`/`{#switch}`
|
|
228
|
+
subject, a plain `{#for}` source — reading `undefined` while pending, so
|
|
229
|
+
`{getFoo() ?? 'Loading…'}` shows the fallback, `{#if getFoo()}` takes the else
|
|
230
|
+
branch, and a pending `{#for}` renders empty. A leading `await` makes it
|
|
231
|
+
SSR-blocking (resolved inline); no `await` streams (pending shell, resolves on the
|
|
232
|
+
client). The one rejection is a raw `AsyncIterable` driving a plain `{#for}` —
|
|
233
|
+
iterate its frames with `{#for await}`.
|
|
234
|
+
|
|
223
235
|
Components are capitalised tags (`<Panel prop={x}>…</Panel>`); nested content
|
|
224
236
|
becomes the component's `children` prop — an ordinary declared prop of type
|
|
225
237
|
`Snippet`, read with `const { children } = props<{ children: Snippet }>()`
|
|
@@ -248,7 +260,7 @@ if/each/await/switch/…>` control flow (use `{#…}` blocks).
|
|
|
248
260
|
### RPC — `@documentation rpc`
|
|
249
261
|
|
|
250
262
|
- `@abide/abide/server/GET` — GET rpc helper: `export const x = GET(handler, opts?)` inside `src/server/rpc/`; the bundler rewrites it to the server dispatcher or the browser proxy — calling it outside an rpc module throws.
|
|
251
|
-
- `@abide/abide/server/POST` — POST rpc helper (mutating:
|
|
263
|
+
- `@abide/abide/server/POST` — POST rpc helper (mutating: JSON/FormData body).
|
|
252
264
|
- `@abide/abide/server/PUT` — PUT rpc helper (mutating).
|
|
253
265
|
- `@abide/abide/server/PATCH` — PATCH rpc helper (mutating).
|
|
254
266
|
- `@abide/abide/server/DELETE` — DELETE rpc helper (mutating; args travel in the query string).
|
|
@@ -300,7 +312,7 @@ if/each/await/switch/…>` control flow (use `{#…}` blocks).
|
|
|
300
312
|
|
|
301
313
|
Probes report, never act — reading one opens no fetch and no stream.
|
|
302
314
|
|
|
303
|
-
- `@abide/abide/shared/pending` — `pending(selector?, args?)`: reactive "no value yet" probe over calls and streams (global, per-rpc, per-call, tagged, per-subscribable
|
|
315
|
+
- `@abide/abide/shared/pending` — `pending(selector?, args?)`: reactive "no value yet" probe over calls and streams (global, per-rpc, per-call, tagged, per-subscribable).
|
|
304
316
|
- `@abide/abide/shared/refreshing` — `refreshing(selector?, args?)`: "holding a value while a fresher one is in flight" — the SWR reload / stream-reconnect badge.
|
|
305
317
|
- `@abide/abide/shared/peek` — `peek(fn, args?)` / `peek(socket)`: the retained value (or latest frame), synchronously, `T | undefined`; reactive inside a tracking scope.
|
|
306
318
|
- `@abide/abide/shared/done` — `done(subscribable)`: true once a stream closed (stream-only; a cache read's "done" is `!pending && !refreshing`).
|
|
@@ -308,7 +320,7 @@ Probes report, never act — reading one opens no fetch and no stream.
|
|
|
308
320
|
|
|
309
321
|
### Errors — `@documentation response`
|
|
310
322
|
|
|
311
|
-
- `@abide/abide/shared/HttpError` — thrown by rpc calls on non-2xx; carries `status`, `statusText`, the raw `response`, and — for typed/validation
|
|
323
|
+
- `@abide/abide/shared/HttpError` — thrown by rpc calls on non-2xx; carries `status`, `statusText`, the raw `response`, and — for typed/validation errors — `kind` + `data`.
|
|
312
324
|
- `@abide/abide/shared/ValidationErrorData` — the `data` shape of a `kind: 'validation'` failure: the raw Standard Schema `issues` plus a `fields` (field → first message) map.
|
|
313
325
|
|
|
314
326
|
### Schema projection — `@documentation rpc`
|
|
@@ -354,10 +366,6 @@ Probes report, never act — reading one opens no fetch and no stream.
|
|
|
354
366
|
|
|
355
367
|
- `@abide/abide/ui/navigate` — `navigate(path, params?, options?)`: typed programmatic navigation off the route map; params interpolate through `url()` (base-correct). Options `{ replace, keepScroll }`. The module also exports `navigatePath(path, options?)` (already-resolved paths — the router's own entry, no re-basing) and the `NavigateOptions` type.
|
|
356
368
|
|
|
357
|
-
### Outbox — `@documentation ui`
|
|
358
|
-
|
|
359
|
-
- `@abide/abide/ui/outbox` — the global reactive outbox: `outbox()` lists every durable rpc's undelivered entries (each tagged with its `rpc`), member `outbox.retry()` drains every queue. Empty list server-side. Types `GlobalOutbox`, `GlobalOutboxEntry`.
|
|
360
|
-
|
|
361
369
|
### UI plumbing — `@documentation plumbing`
|
|
362
370
|
|
|
363
371
|
Compiler/runtime machinery — published so generated code, the type shadow, and
|
|
@@ -370,14 +378,18 @@ tests can import it, not for app code.
|
|
|
370
378
|
- `@abide/abide/ui/router` — `router(...)`: the client router — fills layout/page chains into comment-marker outlet boundaries, intercepts in-app links, buckets/restores scroll per history entry.
|
|
371
379
|
- `@abide/abide/ui/startClient` — `startClient(...)`: the client entry — reads every `__SSR__` field into its shared slot (cache seed, health seed, client timeout, resume manifest), hydrates the chain, starts the router.
|
|
372
380
|
- `@abide/abide/ui/renderToStream` — `renderToStream(render)`: out-of-order SSR streaming — shell first, then one `<abide-resolve>` fragment per streaming await block in completion order; blocking (`then`-head) awaits render inline.
|
|
373
|
-
- `@abide/abide/ui/remoteProxy` — `remoteProxy(method, url, opts?)`: the browser-side rpc stub the bundler emits (fetch, decode, HttpError,
|
|
381
|
+
- `@abide/abide/ui/remoteProxy` — `remoteProxy(method, url, opts?)`: the browser-side rpc stub the bundler emits (fetch, decode, HttpError, streaming); the `RemoteProxyOptions` type rides along.
|
|
374
382
|
- `@abide/abide/ui/socketProxy` — `socketProxy(name)`: the browser-side socket stub — the identical `Socket<T>` shape over the page's lazily-opened multiplexed ws channel.
|
|
375
383
|
- `@abide/abide/ui/runtime/escapeKey` — JSON-Pointer-escapes one reactive-doc path key (`~`→`~0`, `/`→`~1`).
|
|
384
|
+
- `@abide/abide/ui/runtime/withPath` — pushes one `escapeKey`-escaped render-path segment for the duration of a synchronous `build`, relative to the ambient path (the render-path identity a layout layer / child mount composes); restores after. A reactively-rebuilt block uses `withPathFrom` with a captured base instead.
|
|
385
|
+
- `@abide/abide/ui/runtime/renderPath` — the render-path a `<Child/>` mounts under: composes a child's ordinal onto the ambient path (`withPath(ordinal, …)`) to produce the `abide:await:CHILDPATH` boundary id, computed identically on both sides so the streamed-child adopter never drifts. Server-emit-only.
|
|
386
|
+
- `@abide/abide/ui/runtime/blockId` — allocates an await/try block id namespaced by the ambient render-path (`${path}:${n}`, per-path document-order counter), so sibling child renders can run concurrently during SSR without their block ids interleaving; the bare (`path === ''`) case keeps the plain `0,1,2…` form.
|
|
376
387
|
- `@abide/abide/ui/runtime/nextBlockId` — the next await/try block id in the current render pass (document order, shared across inlined children).
|
|
377
388
|
- `@abide/abide/ui/runtime/enterRenderPass` — marks entry into a render/mount; the outermost resets the block-id counter.
|
|
378
389
|
- `@abide/abide/ui/runtime/exitRenderPass` — unwinds `enterRenderPass`'s depth.
|
|
379
390
|
- `@abide/abide/ui/dom/mount` — mounts a top-level page/layout into a host under an ownership scope; returns the unmount.
|
|
380
391
|
- `@abide/abide/ui/dom/mountChild` — mounts a nested child component as a comment-marker range (dev builds also register it with the hot bridge).
|
|
392
|
+
- `@abide/abide/ui/dom/mountStreamedChild` — the client mount for a HOISTABLE child (ADR-0039): a dual-mode adopter that probes the hydration cursor to tell whether the server inlined the child (settled) or streamed it (`abide:await:CHILDPATH` boundary, already swapped in), then adopts the range in place — falling back to a create-mount on a client navigation or an unfilled boundary. Registers with the hot bridge like `mountChild`.
|
|
381
393
|
- `@abide/abide/ui/dom/mountSlot` — mounts a component's passed-children content as a marker-bounded range.
|
|
382
394
|
- `@abide/abide/ui/dom/outlet` — a layout's outlet: an empty `<!--abide:outlet-->…<!--/abide:outlet-->` boundary the router fills.
|
|
383
395
|
- `@abide/abide/ui/dom/hydrate` — adopts server-rendered DOM instead of rebuilding: runs the build with a claim cursor over the existing nodes.
|
|
@@ -388,7 +400,6 @@ tests can import it, not for app code.
|
|
|
388
400
|
- `@abide/abide/ui/dom/appendText` — a reactive `{expr}` text node under a parent.
|
|
389
401
|
- `@abide/abide/ui/dom/appendTextAt` — a reactive text node mounted at a skeleton anchor (text interleaved with element siblings).
|
|
390
402
|
- `@abide/abide/ui/dom/appendSnippet` — mounts a `{snippet(args)}` interpolation's builder into a marker-bounded range.
|
|
391
|
-
- `@abide/abide/ui/dom/text` — a text node whose content tracks a reactive read.
|
|
392
403
|
- `@abide/abide/ui/dom/attr` — binds an element attribute to a read (boolean true → bare attribute, false/nullish → removed).
|
|
393
404
|
- `@abide/abide/ui/dom/on` — attaches an event listener whose removal is registered with the ownership scope.
|
|
394
405
|
- `@abide/abide/ui/dom/attach` — runs an `attach={fn}` attachment and registers its optional teardown.
|
|
@@ -399,12 +410,20 @@ tests can import it, not for app code.
|
|
|
399
410
|
- `@abide/abide/ui/dom/switchBlock` — `{#switch}` runtime (also `{#if}` chains with `{:else if}` branches).
|
|
400
411
|
- `@abide/abide/ui/dom/awaitBlock` — `{#await}` runtime: pending → resolved/error branch swap, teardown-generation guarded.
|
|
401
412
|
- `@abide/abide/ui/dom/tryBlock` — `{#try}` runtime: synchronous error boundary around a subtree build.
|
|
402
|
-
- `@abide/abide/ui/dom/applyResolved` — consumes a streamed SSR `<abide-resolve>` chunk, swapping it into its await boundary (the bundle-side counterpart of the doc stream's inline scripts).
|
|
403
413
|
- `@abide/abide/ui/dom/mergeProps` — composes a child's props from explicit thunk runs, spread layers, and the trailing children layer.
|
|
404
414
|
- `@abide/abide/ui/dom/spreadProps` — wraps a `{...source}` spread layer so every key resolves to a live value thunk.
|
|
405
415
|
- `@abide/abide/ui/dom/restProps` — the live unconsumed-props object behind `const { …, ...rest } = props()`.
|
|
416
|
+
- `@abide/abide/ui/dom/bindProp` — the parent half of a component `bind:prop`: annotates a prop's value thunk with a `set` write-back channel.
|
|
417
|
+
- `@abide/abide/ui/dom/bindableProp` — the child half: the writable cell a component gets for a prop it writes or forwards (pass-through to the parent when bound, a local reseeding cell when not).
|
|
406
418
|
- `@abide/abide/ui/dom/spreadAttrs` — spreads an object's keys onto a native element (`<div {...rest}>`), keys enumerated once.
|
|
419
|
+
- `@abide/abide/ui/dom/mutateDocContainer` — the lowering for an in-place mutating container method on a reactive doc (`model.items.splice(…)`, `.sort()`, a Set `.add()`, a Map `.set()`, …): clones the array/Map/Set, applies the mutation to the copy, and writes it back through `replace` so a real patch fires (readers wake, the render tree re-derives); returns the native method's result unchanged.
|
|
407
420
|
- `@abide/abide/ui/dom/readCall` — guarded method call on a reactive-doc read (the `model.draft.trim()` lowering).
|
|
421
|
+
- `@abide/abide/ui/dom/readCell` — unified read for a `linked`/async-`computed` reference (the `$$readCell(NAME)` lowering): peeks an async cell, reads `.value` off a sync one.
|
|
422
|
+
- `@abide/abide/ui/dom/cellPending` — whether a control-flow subject (`{#if}`/`{#switch}`) is a still-loading async cell (no value, no error) so the block renders no branch while pending instead of flashing its `{:else}`; a plain/settled value is never pending.
|
|
423
|
+
- `@abide/abide/ui/settleAsyncCells` — the SSR Tier-2 await-barrier (the `await $$settleAsyncCells()` lowering emitted between a component's cell declarations and its template): drains + awaits the request-scoped in-flight async-cell promises so their resolved values bake into the first-pass HTML.
|
|
424
|
+
- `@abide/abide/ui/flight` — the SSR flight-starter (`$$flight(() => expr)`): hoists a hoistable await's promise into the synchronous render prefix so independent flights overlap instead of serializing; normalises a synchronous loader throw to a rejected promise and carries a synchronous `.settled` snapshot for `finalizeStreamedChildren`. Server-emit-only.
|
|
425
|
+
- `@abide/abide/ui/isolateCellBarrier` — runs a hoisted child render (`$$isolateCellBarrier`) under its own async-cell barrier list (ALS-backed on the server) so its cell registrations and `$$settleAsyncCells` drain stay isolated from concurrent siblings and the page; an inert passthrough on the client. Server-emit-only.
|
|
426
|
+
- `@abide/abide/ui/finalizeStreamedChildren` — the ADR-0039 when-to-stream decision run once after a component's body walk (`await $$finalizeStreamedChildren(...)`): fills each hoistable child's reserved output slot — inlining a settled flight byte-identically to the pre-ADR path, rethrowing a rejected one, or emitting an `abide:await:CHILDPATH` boundary + streaming `SsrAwait` for a still-pending one. Server-emit-only.
|
|
408
427
|
|
|
409
428
|
## Build / tooling
|
|
410
429
|
|
|
@@ -443,7 +462,7 @@ tests can import it, not for app code.
|
|
|
443
462
|
|
|
444
463
|
| Route | Serves |
|
|
445
464
|
| --- | --- |
|
|
446
|
-
| `/openapi.json` | The OpenAPI document projected from every rpc's method, URL, and schemas |
|
|
465
|
+
| `/openapi.json` | The OpenAPI document projected from every rpc's method, URL, and schemas. The 200 response body comes from `schemas.output` or, absent one, the handler's return type projected to JSON Schema (ADR-0030 D2); each `error.typed(...)` branch the handler can return surfaces as its own status response (ADR-0030) |
|
|
447
466
|
| `/__abide/mcp` | The MCP endpoint (tools from rpcs/sockets, prompts, resources); auth flows from the inbound request |
|
|
448
467
|
| `/__abide/health` | Liveness + identity JSON: framework version, app name/version, plus the app `health(request)` hook's fields; answered ahead of `app.handle` |
|
|
449
468
|
| `/__abide/identity` | Compatibility alias for the same payload with the legacy `abide: true` marker |
|
|
@@ -451,7 +470,6 @@ tests can import it, not for app code.
|
|
|
451
470
|
| `/__abide/sockets/<name>` | A socket's HTTP face: `GET` = retained tail as JSON (SSE stream under `Accept: text/event-stream`; `?tail=N` caps/seeds), `POST` = publish gated by `clientPublish`; 404 unless the socket is exposed to mcp/cli |
|
|
452
471
|
| `/__abide/cli` | `GET` = shell install script; `/__abide/cli/<platform>` streams the thin-CLI tarball (cli + server binaries, `.env` baked with `ABIDE_APP_URL`/`ABIDE_APP_TOKEN`) |
|
|
453
472
|
| `/__abide/inspector` | The `@abide/inspector` UI, mounted only under `ABIDE_ENABLE_INSPECTOR=true` |
|
|
454
|
-
| `/__abide/hot/<moduleId>` | Dev-only component hot-module endpoint backing `.abide` HMR |
|
|
455
473
|
|
|
456
474
|
## Environment variables
|
|
457
475
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,182 @@
|
|
|
1
1
|
# abide
|
|
2
2
|
|
|
3
|
+
## 0.50.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8fb06cf: Async values — `state` holds, `computed`/`linked` track, and a reactive `{#try}` (ADR-0019)
|
|
8
|
+
|
|
9
|
+
`state` is now a pure value-taker; `computed`/`linked` track an async source. A `computed(await …)` / `linked(await …)` seed unwraps its promise into an async cell, and a seed producing a stream (`NamedAsyncIterable`) auto-tracks its frames. Async cells wear the probe family (`peek`/`pending`/`refreshing`/`error`/`refresh`, standalone + instance) and have no `.value` — sync → `.value`, async → probes. `AsyncState` adds `set()`, latching a local write until the next reseed.
|
|
10
|
+
|
|
11
|
+
`computed(EXPR)` / `linked(EXPR)` now accept a bare expression (wrapped into a thunk unless it is already a `() => …`); a top-level `await` lowers to an eager `async () => …` so independent cells load in parallel, not in a waterfall.
|
|
12
|
+
|
|
13
|
+
`{#try}` is a fully reactive error boundary — it catches a throw from a _later_ re-run (where async errors live), not just the initial render, and heals back to the guarded content when the failing cell recovers (`refresh()` or a dependency change), no manual retry.
|
|
14
|
+
|
|
15
|
+
Also renames the internal `Subscribable<T>` type to `NamedAsyncIterable<T>` (shape unchanged).
|
|
16
|
+
|
|
17
|
+
- b790726: Async template interpolation — `{await foo()}`, `{foo()}`, `{getStream()}` (ADR-0019 follow-up)
|
|
18
|
+
|
|
19
|
+
A template interpolation now understands async expressions, chosen by the expression's type (resolved through the shadow TypeChecker at build time — zero runtime cost, dev/prod consistent):
|
|
20
|
+
|
|
21
|
+
- `{await foo()}` — awaits the promise, **blocking** (bakes into the SSR HTML). Syntactic; desugars to a blocking `{#await … then}` block.
|
|
22
|
+
- `{foo()}` where `foo()` is a `Promise<T>` — **streaming** (real Tier-3 out-of-order SSR); desugars to a streaming `{#await}` block.
|
|
23
|
+
- `{getStream()}` where the expression is any `AsyncIterable<T>` (a socket, a streaming rpc, or a plain async generator) — renders the **latest frame**, live, as a stream cell.
|
|
24
|
+
- A `Promise`/`AsyncIterable` in a value slot that can't be rendered (an attribute, an `{#if}`/`{#switch}` head, a sync `{#each}` iterable) is a **compile error** with a hint, never a silent `[object Promise]`. `{#for await}` stays the sanctioned async-iterable position.
|
|
25
|
+
|
|
26
|
+
Errors compose with `{#try}`: a catch-less `{#await}` / `{#for await}` (and these new bare forms) now bubbles a rejection to the nearest enclosing `{#try}` instead of an unhandled rejection.
|
|
27
|
+
|
|
28
|
+
The type-directed lowering fails open to a plain read on any type-resolution hiccup, so it can never break a build; a component with no async interpolations is unaffected.
|
|
29
|
+
|
|
30
|
+
- 53ad08f: Endpoint-declared cache policy; namespace the rpc opts (ADR-0020). All cache/stream policy moves onto the rpc definition and the smart bare call becomes `fn(args)` with no call-site options. Opts are namespaced and kind-scoped by type: `schemas: { input, output, files }`, `cache: { ttl, tags, throttle, debounce, shared }` and `stream: { n }` on read helpers, `outbox` on mutating helpers (a `cache` on a write, or `outbox` on a read, is a compile error). `cache.tags` accepts an arg-derived function. `swr` is removed — SWR is unconditional for replayable reads. `ttl` now defaults to Infinity: an entry is retained for its store's lifetime (the request on the server, the tab on the client), and `shared` alone memoises across requests; a write coalesces only. Endpoint policy also ships to the client so client-side reads honor the declared ttl/refetch clock/tags.
|
|
31
|
+
|
|
32
|
+
BREAKING: `inputSchema`/`outputSchema`/`filesSchema` → `schemas: { … }`; call-site cache options (`fn(args, { ttl, tags, … })`) → endpoint `cache: { … }`, and the smart bare call drops its second argument (`fn(args)`). `.raw(args, init)` keeps per-call transport options unchanged.
|
|
33
|
+
|
|
34
|
+
- 5b2df3f: Type-directed async-cell classification (ADR-0023). A no-marker `computed`/`linked` stream seed now routes by the seed's checker type instead of a syntax heuristic: a stream produced by _any_ expression shape (`computed(obj.stream)`, a conditional) auto-tracks, where before only a bare call/identifier did, and a provably-sync seed skips the runtime probe. Resolved through the warm shadow program and **fails open** to the previous syntactic routing when no program is available; the `await`-marker path is unchanged.
|
|
35
|
+
- 8a61f5b: SSR auto-streaming for pending bare async reads (ADR-0024). A triggered point read still pending at render-return now streams its value into the HTML (shell first, then a resolve chunk) instead of shipping buffered — the server triggers the read and drains it as it settles. A pending read is bounded by its own endpoint `timeout` (which 504s the in-process handler during SSR, shipping a `{ key, miss }` so the client refetches) — there is no separate SSR-stream deadline. A page with no async reads still ships buffered; the Tier-2 blocking barrier and Tier-3 `{#await}` streaming are unchanged.
|
|
36
|
+
- bc83c22: Surface a handler's typed error branches into the generated OpenAPI (ADR-0030)
|
|
37
|
+
|
|
38
|
+
A handler's `error.typed(name, status, schema?)` return branches now flow into the generated OpenAPI spec the same way the success body reaches the 200. The warm server program's new `errorSchemasForModule` query walks the handler's return union, reads each `TypedError` brand's numeric status and projects its `data` type to JSON Schema; errors sharing a status combine under `anyOf`, and a nullary error surfaces its status with no body. The resolved map is baked at build time as `errorJsonSchemas` (paralleling `outputJsonSchema`) and `buildOpenApiSpec` merges each into `responses[status]` — never clobbering the 200. So one plainly-typed handler return documents the full contract: the 200 success body plus every typed error's status and payload, nothing declared twice.
|
|
39
|
+
|
|
40
|
+
`error.typed` now captures the status as a literal type (`Status extends number`) so the brand carries the exact code — a backward-compatible tightening of its return type.
|
|
41
|
+
|
|
42
|
+
- 43289e5: Handler-input-typed RPC surface (ADR-0030 input side). The input-side mirror of the return-typed output surface: the warm server program (ADR-0025) gains an `inputSchemaForModule` query that projects the endpoint's input args to JSON Schema through `jsonSchemaForType` — the SAME `Args` bag `inputCoercionForModule` reads (the exported RemoteFunction's first call-signature parameter, `FormData` dropped), with File/Blob members excluded exactly as `filesSchema` stays out of `inputSchema`. The resolved schema is baked into `RpcRegistryEntry.inputJsonSchema`, and the OpenAPI parameters/request body, MCP `inputSchema`, and inspector input surface fall back to it when the author declared no `schemas.input` VALIDATOR (which still overrides it on every surface). This is a SHAPE description only — it is never wired into runtime validation, so it can't produce a 422; an author who wants runtime narrowing still declares `schemas.input`. A build-projected `inputJsonSchema` also makes a plainly-typed read handler advertisable to MCP/CLI without a hand-written `schemas.input` (shape-only, no runtime gate) — the same role a declared input schema plays. Fails open to `undefined` (no warm program / unresolvable Args / File-only body), so a surface behaves exactly as before.
|
|
43
|
+
- fcbdeb2: error-recovering template parser ([`13244a4`](https://github.com/briancray/abide/commit/13244a43f0d73f7873cafc7e351285b89db2b245))
|
|
44
|
+
- fcbdeb2: render-path survives a render's awaits via a server ALS pathStore (ADR-0033) ([`16d76fc`](https://github.com/briancray/abide/commit/16d76fc8c52d047740e9b1b520a8d045c5fb7ac8))
|
|
45
|
+
- fcbdeb2: hoist independent SSR awaits to the render prefix (ADR-0034) ([`20f017c`](https://github.com/briancray/abide/commit/20f017ceb0646c71d8e9d1f352665801b259419f))
|
|
46
|
+
- fcbdeb2: asyncIterable interpolation → stream cell (ADR-0019 stage D) ([`3293e45`](https://github.com/briancray/abide/commit/3293e45355ded950d487c63d1b5e6c9868e8fadc))
|
|
47
|
+
- fcbdeb2: async-cell classify accepts any async iterable (ADR-0019) ([`41d78e7`](https://github.com/briancray/abide/commit/41d78e791d4260f23ceefc691c7504f3a7f4edec))
|
|
48
|
+
- fcbdeb2: catch→catch updates err in place in reactive {#try} (ADR-0019 D3) ([`439dbd3`](https://github.com/briancray/abide/commit/439dbd3843845a9e74c0334b5cbc3d990897bdcb))
|
|
49
|
+
- fcbdeb2: catch-less {#await}/{#for await} bubbles to enclosing {#try} (ADR-0019) ([`5f8dea8`](https://github.com/briancray/abide/commit/5f8dea8912af37ef20c8eb2a684cb20d4584ff89))
|
|
50
|
+
- fcbdeb2: blocking Tier-2 barrier bakes async-cell values into HTML (ADR-0019) ([`659ed41`](https://github.com/briancray/abide/commit/659ed41cecadd8d5c2e74d2d3a11556d5ac9ff63))
|
|
51
|
+
- fcbdeb2: async (sub)expressions lift to a peek-cell in every template position (ADR-0032) ([`72aaef6`](https://github.com/briancray/abide/commit/72aaef680dffd12b8ff9596c7ec9064b651b3445))
|
|
52
|
+
- fcbdeb2: value-position guard for async interpolations (ADR-0019 stage E) ([`748fcb0`](https://github.com/briancray/abide/commit/748fcb00b6eb35b50650e0320f512e43e419a0ef))
|
|
53
|
+
- fcbdeb2: Breaking: ttl defaults to Infinity — store lifetime, not ttl, ends a read (ADR-0020) ([`7715fd8`](https://github.com/briancray/abide/commit/7715fd8d368c8bd65a533452cbf8ba782355c26f))
|
|
54
|
+
- fcbdeb2: render-path identity for async cells — warm-seed across SSR→client ([`8f123b3`](https://github.com/briancray/abide/commit/8f123b37cf07f6f836dfe034a4b2f68f8559d013))
|
|
55
|
+
- fcbdeb2: type-directed interpolation — warm checker + promise→streaming (ADR-0019 stages B+C) ([`91cb6c6`](https://github.com/briancray/abide/commit/91cb6c65670709f53b2b90d300fcb4b84a95540a))
|
|
56
|
+
- fcbdeb2: interpolation type classifier (ADR-0019, type-directed lowering stage A) ([`9351b18`](https://github.com/briancray/abide/commit/9351b18eadfe6cc4b6e9a690a84204a67e58a964))
|
|
57
|
+
- fcbdeb2: Breaking: endpoint-declared cache policy; namespace rpc opts (ADR-0020) ([`95fa41c`](https://github.com/briancray/abide/commit/95fa41c81ccb62b8f16881a96a878ab6be2872bd))
|
|
58
|
+
- fcbdeb2: reactive {#try} error boundary — both directions (ADR-0019 D3) ([`9b38fe6`](https://github.com/briancray/abide/commit/9b38fe65cc512e245eb6fe6a32ec8cd705a5227d))
|
|
59
|
+
- fcbdeb2: warm-seed a streamed child's late async cells (ADR-0039) ([`a8acee3`](https://github.com/briancray/abide/commit/a8acee3ccf8a1c8f5d6813a844c7129615b3beb5))
|
|
60
|
+
- fcbdeb2: ship endpoint cache/stream policy to the client stub (ADR-0020) ([`ae56080`](https://github.com/briancray/abide/commit/ae5608044e92057be224ee7967ce209bf9dc1005))
|
|
61
|
+
- fcbdeb2: source-agnostic async cells for computed/linked (ADR-0019 D1) ([`bc04cbe`](https://github.com/briancray/abide/commit/bc04cbef933d72243c4d5d082431453e418ef2ee))
|
|
62
|
+
- fcbdeb2: value-aware throwing peek for async-cell reads (ADR-0019 D3.2) ([`c19af9b`](https://github.com/briancray/abide/commit/c19af9b7e18e8d03cc24f8f2dd527ac346ff3476))
|
|
63
|
+
- fcbdeb2: computed/linked wrap + await lowering (ADR-0019 D2) ([`c1dc57d`](https://github.com/briancray/abide/commit/c1dc57dcfab12bd11081e157a653b97cd951db70))
|
|
64
|
+
- fcbdeb2: revive Date/Set/Map/bigint input args via a type-directed wire codec (ADR-0029) ([`c70e2f7`](https://github.com/briancray/abide/commit/c70e2f73d9946763f5fa8a1853942d6174cccb0e))
|
|
65
|
+
- fcbdeb2: {await expr} interpolation → blocking await block (ADR-0019) ([`d11cbc2`](https://github.com/briancray/abide/commit/d11cbc2e995fdce6c1003b1db4ead6996431078e))
|
|
66
|
+
- fcbdeb2: client rpc transform via module graph + reachability guard (ADR-0022 D2/D3) ([`ddf1fd2`](https://github.com/briancray/abide/commit/ddf1fd215cca169421cb0d5bcfba0a728ec196d8))
|
|
67
|
+
- fcbdeb2: computed(getStream()) compile auto-track via trackedComputed (ADR-0019 D1) ([`e964253`](https://github.com/briancray/abide/commit/e964253dbb9d21e9a57acf0f2f94026561cdca86))
|
|
68
|
+
- fcbdeb2: stream non-cache streaming-cell values, adopt post-hydration (ADR-0035 phase 1) ([`f553731`](https://github.com/briancray/abide/commit/f5537310af6396ea9359e5dc8f7fddafe301c4a1))
|
|
69
|
+
- fcbdeb2: render-boundary streaming foundation + ADR-0039 ([`f83d828`](https://github.com/briancray/abide/commit/f83d82865eeb6a3e4ccc1cb824f24a9180755a89))
|
|
70
|
+
- fcbdeb2: project handler input type into mcp/cli/openapi surfaces (ADR-0030 input side) ([`fd13fa1`](https://github.com/briancray/abide/commit/fd13fa185ba2617f350d416199016bd523c14d0d))
|
|
71
|
+
- 6749824: Client-side input validation, always on (ADR-0026). When an rpc endpoint declares `schemas: { input }`, the client `remoteProxy` now validates the typed args against that schema before the fetch and, on a definitive failure, throws an `HttpError` shaped identically to the server's 422 (`kind: 'validation'`, the same field-error `data`) — no round-trip. A validator that _throws_ (a non-portable / async-resource refinement that can't run in the browser) falls through to the server instead of failing the call, so adding a schema can never break a request. Server validation stays authoritative and unconditional — the client check is a UX optimization only. No configuration: it runs whenever an input schema is present.
|
|
72
|
+
- b0cb79a: Remove the durable outbox and other unreachable surface — a tighter core
|
|
73
|
+
|
|
74
|
+
**Breaking:** the durable-delivery outbox is gone. The `@abide/abide/ui/outbox` export (`outbox()`, `outbox.retry()`, `GlobalOutbox`), the `outbox: true` rpc option (POST/PUT/PATCH/DELETE), the reserved `kind: 'queued'` error, and `OutboxEntry` are all removed. A mutating rpc now simply fetches and throws; durable local-first delivery is left to userspace (wrap the call, park failures in your own persisted queue via `fn.raw`). `RemoteFunction` loses its `Durable` type parameter and `.outbox` face; `remoteProxy`'s `DurableOptions` is renamed `RemoteProxyOptions`.
|
|
75
|
+
|
|
76
|
+
Removing outbox also drops the client persistence stack it was the only caller of (`persist`, `localStoragePersistence`, `PersistenceStore`/`PersistHandle`) and a whole build-time type-query (`outboxForModule`) from the warm server program.
|
|
77
|
+
|
|
78
|
+
Alongside it, several never-wired internals were deleted: the withdrawn `Scope` capability methods (`record`/`persist`/`broadcast`/`undo`/`redo`/`canUndo`/`canRedo`) and their `history`/`sync` backends, the unused `Scope.child`/`Scope.root` tree helpers, and the per-patch `PatchEvent.inverse` pre-image (computed on every mutation but read by nothing since the journal left). No behavioural change for app code that didn't use these.
|
|
79
|
+
|
|
80
|
+
### Patch Changes
|
|
81
|
+
|
|
82
|
+
- d5c3a04: Route the rpc method + `outbox` build transforms through the warm server program (ADR-0025 phase 2)
|
|
83
|
+
|
|
84
|
+
Phase 1 warmed one per-root `ts.Program` over the server module graph and used it only for streaming detection (gated on a build-cost budget). Phase 2 lifts that budget — the ~0.5 s cold cost is paid once per root, amortized, and always fail-open — and routes the two remaining text-scanners through the same warm program:
|
|
85
|
+
|
|
86
|
+
- **HTTP method** now resolves off the export helper's _symbol_ (following import aliases and re-exports), so `export const x = read(fn)` where `import { GET as read }` correctly types as `GET` in the generated `rpc.d.ts`, where the `RPC_EXPORT` regex read nothing.
|
|
87
|
+
- **`outbox`** now resolves off the opts object's property _type_, lifting "must be an inline literal" to "must be statically known" — an imported const (`outbox: OUTBOX_ENABLED`) resolves to its literal `true` instead of erroring, and an `outbox:` mention inside the handler body is ignored.
|
|
88
|
+
|
|
89
|
+
Every query fails open to today's regex/char-scan (no warm program, an unresolvable node, or a checker throw → byte-identical to before), so this can only harden, never break, a build. The hand-rolled scanners remain as that fallback and as the residual span-finder for splicing.
|
|
90
|
+
|
|
91
|
+
Also adds a one-line build-start log (`[abide] building client bundle…`, one-shot builds only) so the program-warming pause at the start of `abide build`/`compile` isn't mistaken for a hang.
|
|
92
|
+
|
|
93
|
+
- 831ae88: Streaming rpc detection resolves through a warm `ts.Program` (ADR-0025). Whether a handler streams (returns `jsonl()`/`sse()`) is now decided by the handler's **return type** via a warm per-project-root program, instead of scanning the handler body for the call name — which fixes the wrapper-indirection blind spot where a handler returning a stream through a helper function was misclassified as a point read on the client. Built once per project root (a one-time boot cost, reused across every rpc in the build) and **fails open** to the previous char-scan.
|
|
94
|
+
- 95b4b13: Server-side type-directed query/form coercion (ADR-0028). A GET or form-encoded request delivers every field as a string, so a plain `z.object({ n: z.number() })` used to 422 on `?n=2`. The warm server program (ADR-0025) now reads the endpoint's wire `Args` type and stamps a build-time `coerce` plan of exactly which fields are numeric/boolean; `parseArgs` coerces those string values to their typed value before validation. Only fields the type declares numeric/boolean are coerced, so a string field that merely looks numeric (an id, a zip code, `'1.0'`) stays a string; a self-coercing schema's loose input is left for the schema to coerce; and with no warm program every field stays a string exactly as before (fail-open). Closes the long-standing `parseArgs` query-coercion TODO and completes the server half of client-side validation (ADR-0026).
|
|
95
|
+
- aa21b7c: Type-directed wire codec — output/response path (ADR-0029)
|
|
96
|
+
|
|
97
|
+
A handler's structured RETURN values now round-trip to the client instead of being lost or crashing. Previously `json()` serialized via plain `JSON.stringify`, which dropped a `Set`/`Map` to `{}` and threw a 500 on a `bigint`.
|
|
98
|
+
|
|
99
|
+
- **Server encode (all clients).** `json()` now serializes through a value-directed wire replacer: a `Set` crosses as a JSON array, a `Map` as an array of `[key, value]` entries, a `bigint` as a digit string, a `Date` as an ISO string. The wire stays honest, tag-free JSON, so curl / OpenAPI SDKs read it and it matches the generated schema.
|
|
100
|
+
- **Client decode (abide clients).** The warm server program resolves the handler's success-body type to a per-field output plan (`date`/`bigint`/`set`/`map`) and bakes it onto the client `remoteProxy` stub. The proxy revives those fields off a decoded response, so a `Set`/`Map`/`bigint`/`Date` return arrives as the real runtime type. A genuine array is untouched; no plan / an unrevivable value fails open to the honest-JSON form.
|
|
101
|
+
- **Projector coherence.** The `ts.Type` → JSON Schema projection now maps `Set<T>` → `array` and `Map<K,V>` → an array of `[K,V]` tuples, so the generated OpenAPI 200 matches the actual bytes.
|
|
102
|
+
|
|
103
|
+
Deferred: nested/recursive descent, streaming-frame (`jsonl`/`sse`) encoding, and server-side in-process revival.
|
|
104
|
+
|
|
105
|
+
- 52a7e5c: Type-directed structured input wire codec (ADR-0029). Generalizes ADR-0028's scalar query coercion to structured value kinds: the same warm server program now classifies a top-level `Args` field as a `Date`/`Set`/`Map` (by symbol identity through the checker) or a `bigint` (by type flag) and stamps it into the endpoint's build-time `coerce` plan. `parseArgs` revives each declared field from its plain-JSON wire form before validation — an ISO string → `Date`, a numeric string → `bigint`, a JSON array → `Set`, a JSON entries array (or object) → `Map` — so a `Date` GET argument or a non-abide JSON client's structured body reaches the handler as the right runtime type instead of a raw string/array. Top-level fields only; the wire stays tag-free (type-directed, not tag-directed) so a plain-JSON / OpenAPI client still reads it. Fail-open throughout: no plan, an unrevivable value, or an already-typed value (the abide client's own ref-json body, which already round-trips these kinds) passes through as today's behavior — never a throw. The symmetric response/output codec is deferred (the response path serializes with plain `Response.json`, which needs a server-side encode step first).
|
|
106
|
+
- 8b9505d: Handler-return-typed RPC output surface (ADR-0030 D2). A `ts.Type` → JSON-Schema projector (`jsonSchemaForType`) generates an rpc's OpenAPI 200 / MCP `outputSchema` / inspector output shape from the handler's return type when no `schemas.output` validator is declared — the complement of `jsonSchemaForSchema` (which projects a runtime validator). It covers the subset abide's `json()`/`jsonl()` emit (primitives, string-literal `const`/`enum`, objects with `required`, `Record` → `additionalProperties`, arrays, tuples, unions with optional-stripping, `Date` → `date-time`, `bigint` → string per ADR-0029) and fails OPEN — an unsupported/unresolvable type projects to permissive `{}` (omitted at the top level), with a mandatory cycle guard so a recursive type can't infinite-loop; it never throws, so a projection gap can't break a build.
|
|
107
|
+
|
|
108
|
+
The warm server program gains a `returnBodySchemaForModule` query that resolves the same success-body `ts.Type`(s) as `returnBodyForModule` and runs the projector. Since the OpenAPI/MCP/inspector surfaces render off the runtime rpc registry (not the build-time program), the resolver plugin BAKES the projected schema into the server `defineRpc` call — stamped as `outputJsonSchema`, exactly like the ADR-0028 `coerce` plan — so the registry entry carries it at runtime. A declared `schemas.output` validator still overrides the projection (a runtime narrowing the type can't express); with neither, the surface omits the response schema exactly as before.
|
|
109
|
+
|
|
110
|
+
- 32a2203: Handler-return-typed RPC surface foundation (ADR-0030). The warm server program (ADR-0025) gains a `returnBodyForModule` query: it reads the endpoint's success-BODY type off the handler's return type the same way streaming detection already reads it — unwrap `Promise`, drop the `TypedError` branches, take each success `TypedResponse<Body>` body (the type-side mirror of `RpcHelper`'s `SuccessBody<R>`). A streaming endpoint reports its per-FRAME type (the `AsyncIterable` element) and `streaming: true`, so a surface can describe one streamed item. Like every other server-program query it fails open to `undefined`. This is the output-side sibling of ADR-0028's `inputCoercionForModule` — the handler's return becomes the single source for the generated surfaces so a plainly-typed handler needs no redundant `schemas.output`. Consumer wiring (OpenAPI 200 / MCP `outputSchema` / `.d.ts`) is deferred: those surfaces want JSON Schema and the only projector today (`jsonSchemaForSchema`) runs off a runtime Standard-Schema object, not a TS type — a TS-type→JSON-Schema projector is the follow-up. Client typing needs nothing here: the caller's `Return` already flows natively from the imported rpc module (ADR-0022).
|
|
111
|
+
- 3160daa: Factor the post-DCE metafile walk into a reusable analysis seam and add a bundle-budget diagnostic (ADR-0031 D2). The client build's `build.onEnd` reachability pass is now `bundleGraphFromMetafile` — one walk of `Bun.build`'s metafile into a `BundleGraph` (`{ modules, importerChain }`) that both the side-crossing guard (unchanged behavior) and a new consumer read. First consumer: a non-blocking bundle-budget warning — a project `src/` module that survives tree-shaking into the client bundle over 512 KiB earns an `abideLog.warn` with its import chain, so an accidentally-huge input becomes observable without a new bundler pass. Never fails the build; the only hard `onEnd` failure remains a real client-reachable `$server/*` violation. ADR-0031 D1 (sharpening the post-await tracking lint with the script-region classifier) is deferred: the spike found the classifier precise but the current warning already never fires on the inert captures it was meant to stop flagging.
|
|
112
|
+
- 43289e5: Render-path identity now holds for async cells nested in control flow and across a render's awaits (ADR-0033), fixing warm-seed misses that made such cells refetch and flash on hydrate. Two parts. First, `generateSSR` pushes the same render-path segment the client pushes inside `{#each}`/`{#if}`/`{#switch}` (keyed key / keyless row index for each; `then`/`else` or switch branch-index) — previously only child components got a segment, so a cell in a loop/branch composed a divergent scope id server-vs-client. Second, the render-path base itself now survives a render's `await`s: `withPath`/`withPathFrom` set `CURRENT_PATH` and restored it synchronously, but an SSR render's `build()` returns a pending promise awaited outside the wrapper, so the base was restored the instant a render yielded — any scope created past that await (a 2nd sibling child, anything after a page-level cell barrier, a child two awaits deep) composed against an ancestor's path. The swappable `ambientPathBacking` widens from `{get,set}` to `{run,get}`; the server backs `run` with a dedicated `AsyncLocalStorage` (`pathStore`) whose value is inherited by the render's post-await continuations, and `CURRENT_PATH.current` becomes read-only. The client keeps its synchronous save/restore (its mount is synchronous). No codegen change; `CURRENT_SCOPE` was audited and does not share the bug.
|
|
113
|
+
- a6c5749: Independent SSR awaits now render in parallel, not in series (ADR-0034). An independent blocking `{await X}` and streaming `{#await Y}` used to render in the SUM of their latencies: the blocking await halts the sequential render walk, and the streaming flight is a deferred thunk drained only after `render()` returns, so a streaming read behind a blocking await couldn't start until it settled. The SSR back-end now hoists each _hoistable_ await's promise-START into the synchronous render prefix (server-only, via a new plumbing `$$flight` helper) so independent flights overlap — an await is hoistable when its promise references only component-scope names (no `{#for}`/`{:then}`/snippet binder, no async cell), is not inside a conditional branch, and is statically-single. The block's boundary markers, `$ctx` block-id, the RESUME/streaming wire, and the entire client build are untouched, so hydration is byte-identical and the client stays fully reactive. On the kitchen-sink `templating/async` page this cuts SSR from ~842ms to ~427ms (max, not sum). Transparent: no code change for app authors; a page with a single or no async await is unaffected.
|
|
114
|
+
- a6c5749: Non-cache streaming peeks stop flashing `loading…` on hydrate (ADR-0035). A bare async peek whose loader is a plain (non-cache) promise — `{loadProfile(attempt)?.name}` — shipped pending in the SSR shell and the client cold-re-ran its seed on hydrate, so it showed `loading…` for the full latency even though the server had already computed the value. Unlike a `{#await}` block (streams its resolved HTML fragment) or a cache-backed peek (streams its cache entry via `__abideResolve`), a plain-promise peek had no server→client value channel. It now does: a streaming cell records its settled value in a per-request partition, the page renderer streams it after the shell as an `__abideResolve({ cellKey, value })` chunk keyed by the cell's render-path id, and the client adopts it as a POST-hydration reactive update (deferred a microtask so it lands after the pending markup is claimed — no `assertClaimedText` desync). Values (not promises) are recorded so a cell that stays pending all request can never hang the response; only cells settled by the drain are streamed (a page with a blocking `{await}` or barrier — the common case — is covered; a pure-streaming-peek page cold-runs as before). Reactivity and the RESUME/`$resume` wire are untouched. Transparent behaviour improvement; no API or config change.
|
|
115
|
+
- 56b948e: An inline (bare smart read) rpc call now seeds the SSR warm snapshot regardless of HTTP method, so it hydrates warm instead of re-firing after render (ADR-0036). Previously only GET entries rode the warm snapshot: a non-GET rpc consumed inline in render position — e.g. `{html(highlightCode({ code, lang })?.html)}` where `highlightCode` is a POST (POST so the large `code` payload rides the body, not a multi-KB query string) — ran the handler in-process during SSR **and** re-fetched on the client after hydration, firing the call twice per page load (a second POST can 500 on a unique constraint / double-mutate). The `{ GET }` gate conflated two concerns: _seeding_ the SSR-computed body (safe for any method — the value already exists) and _re-firing_ the stored request unprompted on invalidate/refresh/staleness (safe only for GET). They're now decoupled: seeding is gated on "the entry carries a wire request" (any method), while re-firing stays GET-only — `REPLAYABLE_METHODS` is unchanged, so a write is still coalesce-only (re-submittable), never SWR-retained, and never auto-replayed from the snapshot. A seeded write is read warm once during hydration; a later reactive re-read re-issues it live with real args. Keying already includes canonical-JSON body args, so each inline write seeds under its own distinct key. Verified: an inline POST hydrates warm with zero post-hydration refetch. No API or config change.
|
|
116
|
+
- 126474d: SSR await/try block ids are now keyed by render-path instead of a single flat counter (ADR-0037), making block-id allocation congruent SSR↔client even under concurrent renders. Previously every `await`/`try` block drew from one monotonic counter shared across a component and the children it inlines (`$ctx.next++` server / `RENDER.blockId` client), which only stayed congruent because sibling child renders ran strictly sequentially — the blocker to parallelizing them. A block id is now `${render-path}:${n}` (a per-path counter), reusing the ADR-0033 AsyncLocalStorage render-path so each child's ids live in their own namespace; a pathless render (a top-level page with an empty route key, or a bare test component) keeps the plain `0,1,2…` form, so the common case is unchanged. DOM adoption was already positional (boundary-marker ids are not compared, only the `RESUME` manifest key is), so the id widened `number → string` with no parser change. This is a transparent refactor — id allocation order is unchanged, so behaviour is identical and the full hydration suite stays green. On that foundation, **sibling `<Child/>` renders now run in parallel during SSR** (ADR-0037 Phase 2): a hoistable top-level-spine child render (childless, prefix-evaluable props, no `bind:`) starts in the render prefix as an isolated `$$flight` and is awaited at its position, so independent cards render in ~max not ~sum of their latencies while the HTML still assembles in source order. Each hoisted render runs under its own async-cell barrier list (`isolateCellBarrier`, a server-only per-render AsyncLocalStorage) so concurrent siblings never `splice(0)`-drain each other's cells. Transparent — same HTML, same hydration; only independent child work overlaps. No API or config change.
|
|
117
|
+
- dc064ac: A route's layout layers now render in parallel during SSR (ADR-0038). Previously `renderChain` rendered the layout chain (outermost layout → … → page) sequentially, so a route with N layouts each doing an independent I/O read flushed its shell in the sum of their latencies. Now the layers render via `Promise.all`, each under `isolateCellBarrier` (so their async-cell barriers don't cross-drain) and its distinct route-key `withPath` (so path-keyed block ids stay collision-free) — the html fold and state/awaits/resume aggregation run after all settle and are order-independent, so output and hydration are byte-identical, just faster. A lone page with no layouts still renders directly (no wrapping overhead, same bare-read streaming timing). Server-only; reuses the ADR-0037 primitives. Verified with a timing test (three layers ~40ms each render in ~max not ~sum) and the full hydration suite. No API or config change.
|
|
118
|
+
- 3bfdcb6: A slow hoistable child component now STREAMS its fragment during SSR instead of blocking the shell (ADR-0039). Previously every hoistable `<Child/>` was awaited inline, so a page's shell couldn't flush until its slowest child settled. Now `generateSSR` reserves each hoistable child's output slot and, after the walk, `finalizeStreamedChildren` decides per render: a child whose isolated flight has already SETTLED inlines its html byte-identically to before (so an all-fast page is completely unchanged — one microtask, no macrotask, same wire), while a still-PENDING child gets an empty `abide:await:CHILDPATH` boundary in the shell and streams its `<abide-resolve>` fragment when it settles. The boundary id is the child's render-path, congruent with the client. The client emits a dual-mode `mountStreamedChild` for the same children: it probes the hydration cursor and adopts either an inlined `[ … ]` range or a swapped-in streamed boundary, with a plain create-mode mount on client navigation. Verified: three ~40ms sibling cards stream in ~max not ~sum, a streamed child hydrates with no desync, and fast/existing children stay byte-identical. Remaining refinement: warm-seeding a streamed child's late-resolving async cells (a blocking `{#await}` inside it already adopts via its resume delta). No API change.
|
|
119
|
+
- fcbdeb2: clear stale rows on eachAsync reseed ([`0173073`](https://github.com/briancray/abide/commit/01730732141c7a7f54001a4a9ba35b6749757412))
|
|
120
|
+
- fcbdeb2: reap embedded server on failed boot, bound readiness probe, escape plist XML ([`04cee1c`](https://github.com/briancray/abide/commit/04cee1c0f60098b55f69321d407886bbf23fae53))
|
|
121
|
+
- fcbdeb2: green up main — biome lint errors block the release run ([`0833487`](https://github.com/briancray/abide/commit/08334878be6191f7994543997a4b8403f36c4ebe))
|
|
122
|
+
- fcbdeb2: single source of truth for the proxied server subdirs ([`094892b`](https://github.com/briancray/abide/commit/094892b7de07751277bf735271080971daaa2384))
|
|
123
|
+
- fcbdeb2: guard streamed cancel race that logged a stray "Controller is already closed" ([`0bbc93c`](https://github.com/briancray/abide/commit/0bbc93cea4192cb2104e6ff83dd231c858d25fd4))
|
|
124
|
+
- fcbdeb2: one schema resolver for all surfaces — fixes CLI drift ([`1083327`](https://github.com/briancray/abide/commit/10833276b5ddb00d1267f6736cc375de732c15a2))
|
|
125
|
+
- fcbdeb2: stop the SSE face dropping Set/Map/bigint; unify the wire codec ([`17ca2d2`](https://github.com/briancray/abide/commit/17ca2d219450af1300ef25182eb16c4db6f5c7ca))
|
|
126
|
+
- fcbdeb2: mint the warm-seed cell key through one shared warmSeedKey ([`233787e`](https://github.com/briancray/abide/commit/233787e0c330e8c43926fcc46df6de77ece3116c))
|
|
127
|
+
- fcbdeb2: rename Socket.broadcast() to publish() to match Bun ([`250c883`](https://github.com/briancray/abide/commit/250c883a035cf9748129d4aa3e17b65a5a5a6189))
|
|
128
|
+
- fcbdeb2: fold REACTIVE_CALLEES into signal-ref lowering ([`269b762`](https://github.com/briancray/abide/commit/269b7623d422f4b1e556e46e8f8c7b68017f874d))
|
|
129
|
+
- fcbdeb2: rename Subscribable to NamedAsyncIterable ([`2d17ed2`](https://github.com/briancray/abide/commit/2d17ed23b9c3c5c18adb56feadaf1882c4d991c3))
|
|
130
|
+
- fcbdeb2: share one lenient percent-decode helper ([`3b7f320`](https://github.com/briancray/abide/commit/3b7f32088b514f49dd088893ebd7318c4cc0b8bb))
|
|
131
|
+
- fcbdeb2: drive LSP tokens from the parse, delete both lexers ([`4301e97`](https://github.com/briancray/abide/commit/4301e97abd8b612ebcfb6639426447947046c6b5))
|
|
132
|
+
- fcbdeb2: swap streamed SSR fragments via an inline script, dropping the resolved-frame runtime path ([`431aee3`](https://github.com/briancray/abide/commit/431aee3c406b8181e039a86b04c74192b4222964))
|
|
133
|
+
- fcbdeb2: drop the separate SSR-stream deadline; endpoint timeout governs (ADR-0024) ([`48e45d9`](https://github.com/briancray/abide/commit/48e45d9cf7c922cb9a846829f7ef5b2acee527de))
|
|
134
|
+
- fcbdeb2: thread rpc build stamps as one named RpcBuildStamps payload ([`493a6ac`](https://github.com/briancray/abide/commit/493a6ac0467e6d77ef180e807d769747270c1fd4))
|
|
135
|
+
- fcbdeb2: one-shot cell warm-seed + structural render-path key alignment ([`4a5383f`](https://github.com/briancray/abide/commit/4a5383f64ebadc511a8f55809f899b98d0a734d2))
|
|
136
|
+
- fcbdeb2: migrate disconnected.abide to {#if}/{#for} block syntax ([`57bd904`](https://github.com/briancray/abide/commit/57bd904fd84250c7e1cfd97d84483907fc25cbf3))
|
|
137
|
+
- fcbdeb2: pin the runtime and shadow front-ends to one async-lift plan ([`58e9691`](https://github.com/briancray/abide/commit/58e9691eea870650bf219a33cef2b523f3c5dc10))
|
|
138
|
+
- fcbdeb2: replace reachable's poll registry with a TTL memoize ([`5bd426e`](https://github.com/briancray/abide/commit/5bd426ef4701b2da11531cc30779bd2a953cac20))
|
|
139
|
+
- fcbdeb2: restore in-place HMR for streamed children; review cleanups ([`5eec25d`](https://github.com/briancray/abide/commit/5eec25d0fb27d688e2caa0f180968de5fbefe9c2))
|
|
140
|
+
- fcbdeb2: remove the inspector Reactive tab + PATCH_BUS ([`64e4da9`](https://github.com/briancray/abide/commit/64e4da957144fea6f7a6ecbd4415bf1db219f7c3))
|
|
141
|
+
- fcbdeb2: sync AGENTS.md/README to the ADR-0020 rpc API; format touched files ([`6dcc3dd`](https://github.com/briancray/abide/commit/6dcc3dd5a21cb334bf742e0fd6ef461013eb3cfb))
|
|
142
|
+
- fcbdeb2: refresh getHello templates for NamedAsyncIterable + schemas.input/output ([`7233a9b`](https://github.com/briancray/abide/commit/7233a9b11e663655f87f32e08b5ea98d20cdba7d))
|
|
143
|
+
- fcbdeb2: cache parsed SourceFiles across shadow programs ([`7241ca2`](https://github.com/briancray/abide/commit/7241ca244e1c6bd84dfb94d3c20ff1cfe70cc752))
|
|
144
|
+
- fcbdeb2: remove component hot-module replacement ([`7469c1d`](https://github.com/briancray/abide/commit/7469c1d1b1f2c175c2facfe4288ad81b0f89d063))
|
|
145
|
+
- fcbdeb2: stop stale-worker 500s; move codegen off the boot hot path ([`7c5b241`](https://github.com/briancray/abide/commit/7c5b2417c3ba6737d897c264837ca66a79282a99))
|
|
146
|
+
- fcbdeb2: sort imports ([`7fcd071`](https://github.com/briancray/abide/commit/7fcd0714ce337359bc11347fa243dff64ed682de))
|
|
147
|
+
- fcbdeb2: share block-keyword vocabulary so LSP highlighting can't drift ([`95ac19a`](https://github.com/briancray/abide/commit/95ac19a058574f8bba36aae64d20791c84a4e820))
|
|
148
|
+
- fcbdeb2: drop the runtime-helper binding backstop ([`9ba3513`](https://github.com/briancray/abide/commit/9ba3513432dd5639f93c0a61555d70ba510ff359))
|
|
149
|
+
- fcbdeb2: share the anchored-branch skeleton across await and try blocks ([`a301f0e`](https://github.com/briancray/abide/commit/a301f0e101e314ef904f4f26cd157937de2a8bd2))
|
|
150
|
+
- fcbdeb2: green up main CI — biome lint + strict-consumer typecheck ([`a39e030`](https://github.com/briancray/abide/commit/a39e0307e69e738717d9827464141a85a99dd1e4))
|
|
151
|
+
- fcbdeb2: remove the client→inspector BroadcastChannel bridge ([`a4920ea`](https://github.com/briancray/abide/commit/a4920ea3d969bd0812e4146acb80b775ba8807cf))
|
|
152
|
+
- fcbdeb2: surface diagnostics when the .abide source can't be read ([`a6b1881`](https://github.com/briancray/abide/commit/a6b188138e354fee2b4adb6ae80c4882d78c28c4))
|
|
153
|
+
- fcbdeb2: log streaming rpc handler errors server-side ([`a81d99d`](https://github.com/briancray/abide/commit/a81d99d442ae1f1ba73a98e5c5dc2a51078ba2d2))
|
|
154
|
+
- fcbdeb2: bound the stream-swap node removal to the matching close marker ([`aa3bce6`](https://github.com/briancray/abide/commit/aa3bce6bcb5ddfe1d29f76d8abae3e58d2b4a89d))
|
|
155
|
+
- fcbdeb2: revive wire fields in cache()/peek(); evict idle lifecycle channels ([`b1ffe5a`](https://github.com/briancray/abide/commit/b1ffe5a7676066e71af4ce3bfc7124ddb9ab4bd9))
|
|
156
|
+
- fcbdeb2: consolidate framework plain-text responses ([`b8cbb72`](https://github.com/briancray/abide/commit/b8cbb721e7cb6fbe86016db197d942ec413fc211))
|
|
157
|
+
- fcbdeb2: hoist three duplicated constants to shared origins ([`d0ee9df`](https://github.com/briancray/abide/commit/d0ee9dfeb35582646e33bb317f4c219141fad4d1))
|
|
158
|
+
- fcbdeb2: push render-path segments for each/if/switch rows and branches ([`d2d927a`](https://github.com/briancray/abide/commit/d2d927ac814622e4b258db944d75c930d64733c7))
|
|
159
|
+
- fcbdeb2: guard linked seed arg for noUncheckedIndexedAccess (consumer tsconfig) ([`d643b2b`](https://github.com/briancray/abide/commit/d643b2b467a1ec03b493af8f38a4e3e1f5b74bde))
|
|
160
|
+
- fcbdeb2: satisfy noUncheckedIndexedAccess in the template parser ([`dad539d`](https://github.com/briancray/abide/commit/dad539dd039f2ae44e159c2b544a9d27953b780b))
|
|
161
|
+
- fcbdeb2: fold hydratingSlot + wakeHydrationPeeks into hydrationWindow ([`dd9c682`](https://github.com/briancray/abide/commit/dd9c682d732f9d89c3412018f9c5656b58025ddc))
|
|
162
|
+
- fcbdeb2: move URL-shape route decisions behind createAppRouteResolver ([`e639324`](https://github.com/briancray/abide/commit/e63932426d81268f9fbc75806a7e26263f310ca0))
|
|
163
|
+
- fcbdeb2: refresh AGENTS/README for the wire codec, typed-error OpenAPI surface, and inline {await} ([`eecfe14`](https://github.com/briancray/abide/commit/eecfe145389e8c11f7e83c8d2babbc4ea3e92732))
|
|
164
|
+
- fcbdeb2: remove the dead scope-label plumbing ([`f068887`](https://github.com/briancray/abide/commit/f0688875b77677b670b145ce90982e93e9a1a545))
|
|
165
|
+
- fcbdeb2: gate refresh/invalidate reload on a live tracking reader ([`f0af782`](https://github.com/briancray/abide/commit/f0af782936a87a18cbb4f70c3926315cf6180b31))
|
|
166
|
+
- fcbdeb2: one origin for the rpc/socket shim global names ([`f0baaaf`](https://github.com/briancray/abide/commit/f0baaaffc04d55b32b906fff454221f27b190366))
|
|
167
|
+
- fcbdeb2: one wire-field decoder for input and output ([`f16f005`](https://github.com/briancray/abide/commit/f16f005933509dc5b1e1ee824a3bdca5cf036acd))
|
|
168
|
+
- fcbdeb2: extract the /__abide/* plumbing router from createServer ([`f8f5e22`](https://github.com/briancray/abide/commit/f8f5e227eff0149766281cd254193544099a638b))
|
|
169
|
+
- fcbdeb2: props type honors the destructure annotation — types work like TS (ADR-0022 D4) ([`fd3f74f`](https://github.com/briancray/abide/commit/fd3f74ffa9e938f1c87ec648fe1e4ccad20bde9b))
|
|
170
|
+
- afccb13: `abide check` now counts slotted content toward a required `children` prop. The shadow's completeness check treated `children` like any other prop, so `<Card>text</Card>` mounting a component that declares `children: Snippet` was falsely flagged as missing `children` — forcing `children?` optional as a workaround. Slotted content now satisfies the requirement (matching the runtime, which lowers it to the `children` layer), while a childless `<Card />` is still flagged. All other completeness and excess-prop checks are unchanged.
|
|
171
|
+
- 7e4309e: `bind:prop` now works on components, mirroring `bind:attr` on elements. `<Child bind:value={target} />` passes the prop with a two-way write-back channel — `target` takes the same forms an element bind does (an lvalue, or a `{ get, set }` accessor). The child reads the prop as usual; if it writes the prop (`value += 1`) or forwards it to another `bind:` (`<input bind:value={value} />`), those writes flow back to the parent's `target`.
|
|
172
|
+
|
|
173
|
+
There is no child-side marker: bindability is inferred from usage. A prop the child only reads stays a cheap read-only derive (unchanged); one it writes/forwards is upgraded to a writable cell that is a pass-through to the parent when bound, and a local reseeding cell when not (so a component still works standalone). Previously `bind:` on a component was a silent no-op passed through under a `bind:`-prefixed prop key.
|
|
174
|
+
|
|
175
|
+
- 4020519: Reactive-doc `Set`/`Map` mutations now emit a patch. The doc codec already serializes `Map` and `Set`, but a mutating method on a doc-held collection (`model.tags.add(x)`, `model.byId.set(k, v)`, `.delete`/`.clear`) lowered to a bare in-place call that mutated the live tree by reference and fired no patch — so readers never re-rendered and undo/persistence/sync never saw the change. These now route through the same clone-apply-replace path the in-place array methods use (`$$mutateDocArray` generalized to `$$mutateDocContainer`, covering Array, Map and Set); the mutating method names are disjoint across the three kinds, so the container kind is decided at runtime with no compile-time type check.
|
|
176
|
+
- eaa1829: `done()` / `peek()` are null-tolerant, so a probe fed a pending peek-lift no longer 500s
|
|
177
|
+
|
|
178
|
+
Under ADR-0032 a promise/iterable subexpression peek-lifts to `undefined` while pending, so writing a probe inline in a template — `{done(getFeed())}` / `{peek(getFeed())}` — hands the probe `undefined` on the first render pass. `done()` now returns `false` and `peek()` returns `undefined` on a nullish argument instead of throwing on `subscribable.name`, turning that pending render from an SSR 500 into a graceful empty state. Probing in script (`const closed = state.computed(() => done(feed))`) remains the correct idiom for an accurate stream probe, since a lifted `AsyncIterable` peek yields the latest frame, not the source.
|
|
179
|
+
|
|
3
180
|
## 0.49.0
|
|
4
181
|
|
|
5
182
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ cd examples/kitchen-sink && bun run dev
|
|
|
30
30
|
|
|
31
31
|
An RPC is one exported handler per file under `src/server/rpc/` — the file path
|
|
32
32
|
is the URL. The export wraps the handler in an HTTP-method helper (`GET`,
|
|
33
|
-
`POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`); a Standard Schema `
|
|
33
|
+
`POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`); a Standard Schema `schemas.input`
|
|
34
34
|
(zod, valibot, arktype — no adapter) validates the args and projects the CLI
|
|
35
35
|
flags, the MCP tool, and the OpenAPI operation from the one declaration.
|
|
36
36
|
|
|
@@ -44,10 +44,12 @@ import { listMessages } from '$server/store'
|
|
|
44
44
|
export const getMessages = GET(
|
|
45
45
|
async ({ room, limit }) => json(await listMessages(room, limit)),
|
|
46
46
|
{
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
schemas: {
|
|
48
|
+
input: z.object({
|
|
49
|
+
room: z.string(),
|
|
50
|
+
limit: z.coerce.number().default(50),
|
|
51
|
+
}),
|
|
52
|
+
},
|
|
51
53
|
},
|
|
52
54
|
)
|
|
53
55
|
```
|
|
@@ -83,7 +85,7 @@ over `fetch` in the browser. Around it:
|
|
|
83
85
|
- `getMessages.pending(args?)` / `.refreshing(args?)` / `.error(args?)` —
|
|
84
86
|
reactive probes
|
|
85
87
|
- a handler that returns `jsonl()`/`sse()` makes the bare call return a
|
|
86
|
-
`
|
|
88
|
+
`NamedAsyncIterable` you `for await` — detected at build time, nothing to declare
|
|
87
89
|
|
|
88
90
|
> Query args travel as strings — use `z.coerce.*` for numbers and booleans on
|
|
89
91
|
> GET/HEAD. The per-RPC `timeout` option is a server-side handler deadline
|
|
@@ -110,7 +112,7 @@ export const chat = socket({
|
|
|
110
112
|
})
|
|
111
113
|
```
|
|
112
114
|
|
|
113
|
-
`chat.
|
|
115
|
+
`chat.publish(msg)` publishes from either side — schema-validated on the
|
|
114
116
|
server, and client publishes are gated by `clientPublish`. `for await (const
|
|
115
117
|
msg of chat)` is the live stream; `chat.peek()` reads the latest retained
|
|
116
118
|
frame; `chat.refresh()` re-pulls the server tail after a reconnect. A schema
|
|
@@ -205,6 +207,9 @@ template grammar:
|
|
|
205
207
|
{room} — {shout}
|
|
206
208
|
</h1>
|
|
207
209
|
|
|
210
|
+
<!-- {await expr} awaits inline (blocking); a Promise-typed {expr} would stream -->
|
|
211
|
+
<p>total messages: {await getMessages({ room }).then((all) => all.length)}</p>
|
|
212
|
+
|
|
208
213
|
{#await getMessages({ room })}
|
|
209
214
|
<p>loading…</p>
|
|
210
215
|
{:then messages}
|