@abide/abide 0.48.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 +85 -59
- package/CHANGELOG.md +216 -0
- package/README.md +21 -10
- package/package.json +15 -4
- package/src/abideLsp.ts +5 -6
- package/src/abideResolverPlugin.ts +197 -169
- package/src/build.ts +79 -56
- package/src/buildCli.ts +3 -5
- package/src/buildDisconnected.ts +2 -5
- package/src/bundleApp.ts +2 -3
- package/src/checkAbide.ts +12 -2
- package/src/compile.ts +2 -4
- 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/installDownloads.ts +13 -3
- package/src/lib/bundle/installMacMenu.ts +13 -3
- package/src/lib/bundle/spawnEmbeddedServer.ts +13 -4
- package/src/lib/bundle/waitForServer.ts +6 -1
- package/src/lib/cli/parseArgvForRpc.ts +36 -9
- package/src/lib/cli/tokenizeArgvFlags.ts +4 -2
- package/src/lib/mcp/createMcpResourceServer.ts +13 -2
- package/src/lib/mcp/mcpTools.ts +15 -6
- package/src/lib/mcp/toolResultFromResponse.ts +40 -12
- 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 +113 -14
- package/src/lib/server/rpc/resolveRpcJsonSchema.ts +28 -0
- package/src/lib/server/rpc/runWithRpcTimeout.ts +12 -1
- 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/finalizeResponse.ts +11 -1
- 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 +24 -16
- 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 +20 -0
- package/src/lib/shared/buildRpcRequest.ts +6 -2
- 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/canonicalJson.ts +24 -1
- package/src/lib/shared/changeAffectsClient.ts +12 -7
- package/src/lib/shared/createCacheStore.ts +31 -8
- package/src/lib/shared/createChannelLog.ts +24 -0
- 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/shared/exitOnBuildFailure.ts +4 -3
- 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/parseEnv.ts +17 -6
- 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/serializeEnv.ts +18 -6
- package/src/lib/shared/snapshotShippable.ts +8 -7
- package/src/lib/shared/snippet.ts +11 -6
- 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/url.ts +5 -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 -7
- 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/bindListenEvent.ts +5 -6
- 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 +52 -13
- package/src/lib/ui/compile/compileShadow.ts +265 -42
- package/src/lib/ui/compile/composeProps.ts +15 -4
- 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 +419 -37
- package/src/lib/ui/compile/expressionIsPrefixEvaluable.ts +19 -0
- package/src/lib/ui/compile/generateBuild.ts +74 -17
- package/src/lib/ui/compile/generateSSR.ts +286 -75
- 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/isWhitespaceText.ts +10 -2
- 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 +51 -10
- 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 -1090
- 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 +26 -40
- package/src/lib/ui/compile/resolveReactiveExport.ts +4 -7
- package/src/lib/ui/compile/scopeCss.ts +27 -4
- 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 +62 -75
- 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/discardBoundary.ts +24 -6
- package/src/lib/ui/dom/disposeRange.ts +2 -1
- package/src/lib/ui/dom/each.ts +41 -12
- package/src/lib/ui/dom/eachAsync.ts +39 -3
- package/src/lib/ui/dom/fillBefore.ts +6 -7
- 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/mergeProps.ts +1 -1
- 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/mountSlot.ts +1 -1
- 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/restProps.ts +2 -2
- package/src/lib/ui/dom/spreadProps.ts +3 -4
- package/src/lib/ui/dom/switchBlock.ts +30 -7
- package/src/lib/ui/dom/tryBlock.ts +233 -70
- 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 +9 -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/props.ts +17 -0
- package/src/lib/ui/remoteProxy.ts +84 -159
- package/src/lib/ui/renderChain.ts +53 -13
- package/src/lib/ui/renderToStream.ts +22 -18
- package/src/lib/ui/resumeSeedScript.ts +1 -1
- package/src/lib/ui/router.ts +86 -53
- package/src/lib/ui/runtime/AsyncCellError.ts +20 -0
- package/src/lib/ui/runtime/CELL_SEED.ts +14 -0
- package/src/lib/ui/runtime/CHILD_PRESENT.ts +2 -2
- 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 +17 -4
- 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 +31 -54
- 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 +33 -1
- 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/scope.ts +11 -0
- package/src/lib/ui/runtime/toTeardown.ts +10 -3
- 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/types/UiProps.ts +6 -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/runtime/withoutHydration.ts +22 -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/state.ts +9 -3
- 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/scopeLabel.ts +0 -21
- package/src/lib/ui/dom/text.ts +0 -20
- package/src/lib/ui/history.ts +0 -101
- package/src/lib/ui/installHotBridge.ts +0 -93
- 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 -28
- 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
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ErrorJsonSchemas } from '../../../shared/types/ErrorJsonSchemas.ts'
|
|
2
2
|
import type { RemoteFunction } from '../../../shared/types/RemoteFunction.ts'
|
|
3
3
|
import type { StandardSchemaV1 } from '../../../shared/types/StandardSchemaV1.ts'
|
|
4
4
|
|
|
5
5
|
/*
|
|
6
6
|
Per-rpc registry record on the server side. MCP and CLI enumerate this
|
|
7
|
-
to discover
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
to discover what shapes an rpc expects/returns. The schemas stay off the
|
|
8
|
+
public RemoteFunction shape so the browser-side proxy doesn't need to carry
|
|
9
|
+
server-only state; the resolved `clients` and `crossOrigin` ride on
|
|
10
|
+
`entry.remote` (ADR-0020 sweep finding #4 — no duplicate fields), so readers
|
|
11
|
+
reach them via `entry.remote.clients` / `entry.remote.crossOrigin`.
|
|
11
12
|
|
|
12
13
|
`inputSchema` validates the argument bag and feeds the MCP tool
|
|
13
14
|
`inputSchema` / OpenAPI parameters; `outputSchema` describes the success
|
|
@@ -23,13 +24,27 @@ multipart body advertises the file parts generically as binary.
|
|
|
23
24
|
export type RpcRegistryEntry = {
|
|
24
25
|
remote: RemoteFunction<unknown, unknown>
|
|
25
26
|
inputSchema: StandardSchemaV1 | undefined
|
|
27
|
+
/* The handler's input args projected to JSON Schema at build time (ADR-0030 input side) — the
|
|
28
|
+
input surface's fallback when no `inputSchema` VALIDATOR is declared, mirroring how
|
|
29
|
+
`outputJsonSchema` backs the output surface. Already JSON Schema (not a validator), so surfaces
|
|
30
|
+
use it verbatim; `inputSchema`, when present, overrides it. It also makes the endpoint
|
|
31
|
+
advertisable (MCP/CLI) — the same role a declared `inputSchema` plays — but purely as a SHAPE
|
|
32
|
+
description: it is never run as runtime validation, so it can't produce a 422. */
|
|
33
|
+
inputJsonSchema: Record<string, unknown> | undefined
|
|
26
34
|
outputSchema: StandardSchemaV1 | undefined
|
|
35
|
+
/* The handler's return type projected to JSON Schema at build time (ADR-0030 D2) — the output
|
|
36
|
+
surface's fallback when no `outputSchema` VALIDATOR is declared. Already JSON Schema (not a
|
|
37
|
+
validator), so surfaces use it verbatim; `outputSchema`, when present, overrides it. */
|
|
38
|
+
outputJsonSchema: Record<string, unknown> | undefined
|
|
39
|
+
/* The handler's typed-error branches projected to a status-keyed JSON-Schema map at build time
|
|
40
|
+
(ADR-0030) — each `error.typed(...)` return branch's status + `data` payload. Feeds the OpenAPI
|
|
41
|
+
`responses[status]` entries alongside the 200; undefined when the handler declares no typed
|
|
42
|
+
errors or the build couldn't resolve them, so the surface omits the error responses. */
|
|
43
|
+
errorJsonSchemas: ErrorJsonSchemas | undefined
|
|
27
44
|
filesSchema: StandardSchemaV1 | undefined
|
|
28
|
-
clients: ClientFlags
|
|
29
45
|
/* The rpc's declared opts, recorded so introspection (inspector) can report
|
|
30
|
-
the deadline/body-cap
|
|
31
|
-
|
|
46
|
+
the deadline/body-cap a handler runs under. Undefined = the framework default
|
|
47
|
+
(no deadline, Bun's server-wide body ceiling). */
|
|
32
48
|
timeout: number | undefined
|
|
33
49
|
maxBodySize: number | undefined
|
|
34
|
-
crossOrigin: boolean | undefined
|
|
35
50
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { fieldErrorsFromIssues } from '../../shared/fieldErrorsFromIssues.ts'
|
|
1
2
|
import type { StandardSchemaV1 } from '../../shared/types/StandardSchemaV1.ts'
|
|
2
3
|
import type { ValidationErrorData } from '../../shared/types/ValidationErrorData.ts'
|
|
3
4
|
import { typedErrorResponse } from '../runtime/typedErrorResponse.ts'
|
|
4
|
-
import { fieldErrorsFromIssues } from './fieldErrorsFromIssues.ts'
|
|
5
5
|
|
|
6
6
|
/*
|
|
7
7
|
The framework-reserved `validation` typed error a 422 carries: the raw Standard
|
|
@@ -21,11 +21,9 @@ slept still reloads it. The initial connect runs even when the page loads
|
|
|
21
21
|
hidden (the baseline must be the serving worker's stamp, captured before a swap
|
|
22
22
|
can replace it) and releases itself once that first event lands.
|
|
23
23
|
*/
|
|
24
|
-
import { DEV_HOT_PREFIX } from '../../shared/DEV_HOT_PREFIX.ts'
|
|
25
24
|
import { DEV_RELOAD_PATH } from '../../shared/DEV_RELOAD_PATH.ts'
|
|
26
25
|
|
|
27
26
|
export const DEV_RELOAD_CLIENT_SCRIPT = `<script>
|
|
28
|
-
window.__abideDev = true;
|
|
29
27
|
;(() => {
|
|
30
28
|
let stamp
|
|
31
29
|
let source
|
|
@@ -48,18 +46,6 @@ window.__abideDev = true;
|
|
|
48
46
|
}
|
|
49
47
|
}
|
|
50
48
|
}
|
|
51
|
-
function swapComponents(next, prev) {
|
|
52
|
-
const components = next.components || {}
|
|
53
|
-
const before = prev.components || {}
|
|
54
|
-
for (const id in components) {
|
|
55
|
-
if (components[id] !== before[id]) {
|
|
56
|
-
// The hot module sources its runtime from window.__abide and self-invokes
|
|
57
|
-
// hotReplace; a load/compile failure or a component with no live instance
|
|
58
|
-
// falls back to a reload.
|
|
59
|
-
import('${DEV_HOT_PREFIX}' + id + '?v=' + components[id]).catch(() => location.reload())
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
49
|
function connect() {
|
|
64
50
|
if (source) {
|
|
65
51
|
return
|
|
@@ -84,7 +70,6 @@ window.__abideDev = true;
|
|
|
84
70
|
if (next.cssHref && next.cssHref !== stamp.cssHref) {
|
|
85
71
|
swapCss(next.cssHref)
|
|
86
72
|
}
|
|
87
|
-
swapComponents(next, stamp)
|
|
88
73
|
stamp = next
|
|
89
74
|
}
|
|
90
75
|
source.onerror = () => {
|
|
@@ -18,20 +18,20 @@ function preview(value: unknown): string | undefined {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
/* The entry's armed
|
|
22
|
-
|
|
21
|
+
/* The entry's armed refetch (stale-while-revalidate) policy as a label, if it declared
|
|
22
|
+
one. A windowless policy refetches immediately, so it labels as plain `refetch`. */
|
|
23
23
|
function policyLabel(entry: CacheEntry): string | undefined {
|
|
24
24
|
const policy = entry.invalidation
|
|
25
25
|
if (!policy) {
|
|
26
26
|
return undefined
|
|
27
27
|
}
|
|
28
28
|
if (policy.debounce !== undefined) {
|
|
29
|
-
return `
|
|
29
|
+
return `refetch debounce ${policy.debounce}ms`
|
|
30
30
|
}
|
|
31
31
|
if (policy.throttle !== undefined) {
|
|
32
|
-
return `
|
|
32
|
+
return `refetch throttle ${policy.throttle}ms`
|
|
33
33
|
}
|
|
34
|
-
return '
|
|
34
|
+
return 'refetch'
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function projectEntry(entry: CacheEntry, now: number): InspectorCacheEntry {
|
|
@@ -48,10 +48,11 @@ function projectEntry(entry: CacheEntry, now: number): InspectorCacheEntry {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/*
|
|
51
|
-
Snapshots the process-level cache store (the persistent one
|
|
52
|
-
true, ttl
|
|
53
|
-
|
|
54
|
-
excluded — they're ephemeral and
|
|
51
|
+
Snapshots the process-level cache store (the persistent one an endpoint declaring
|
|
52
|
+
`cache: { shared: true, ttl }` — or a producer `cache(fn, args, { shared: true, ttl })`
|
|
53
|
+
— writes to) for the inspector. Read at call time, so it reflects the store as it
|
|
54
|
+
stands; request-scoped stores are deliberately excluded — they're ephemeral and
|
|
55
|
+
already visible as per-request cache tallies.
|
|
55
56
|
*/
|
|
56
57
|
export function buildCacheSnapshot(): InspectorCacheSnapshot {
|
|
57
58
|
const now = Date.now()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { jsonSchemaForSchema } from '../../shared/jsonSchemaForSchema.ts'
|
|
2
1
|
import { promptRegistry } from '../prompts/promptRegistry.ts'
|
|
2
|
+
import { resolveInputJsonSchema, resolveOutputJsonSchema } from '../rpc/resolveRpcJsonSchema.ts'
|
|
3
3
|
import { rpcRegistry } from '../rpc/rpcRegistry.ts'
|
|
4
4
|
import { socketOperations } from '../sockets/socketOperations.ts'
|
|
5
5
|
import { socketRegistry } from '../sockets/socketRegistry.ts'
|
|
@@ -18,12 +18,14 @@ export function buildInspectorSurface(): InspectorSurface {
|
|
|
18
18
|
url: entry.remote.url,
|
|
19
19
|
method: entry.remote.method,
|
|
20
20
|
clients: { ...entry.remote.clients },
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
// The declared inputSchema VALIDATOR wins; else the build-projected input-type schema
|
|
22
|
+
// (ADR-0030 input side); with neither the rpc advertises no input shape, as before.
|
|
23
|
+
inputSchema: resolveInputJsonSchema(entry),
|
|
24
|
+
outputSchema: resolveOutputJsonSchema(entry),
|
|
23
25
|
files: entry.filesSchema !== undefined,
|
|
24
26
|
timeout: entry.timeout,
|
|
25
27
|
maxBodySize: entry.maxBodySize,
|
|
26
|
-
crossOrigin: entry.crossOrigin,
|
|
28
|
+
crossOrigin: entry.remote.crossOrigin,
|
|
27
29
|
}))
|
|
28
30
|
const sockets = Array.from(socketRegistry.values()).map((entry) => ({
|
|
29
31
|
name: entry.socket.name,
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { carriesBodyArgs } from '../../shared/carriesBodyArgs.ts'
|
|
2
2
|
import { commandNameForUrl } from '../../shared/commandNameForUrl.ts'
|
|
3
3
|
import { jsonSchemaForSchema } from '../../shared/jsonSchemaForSchema.ts'
|
|
4
|
+
import type { ErrorJsonSchemas } from '../../shared/types/ErrorJsonSchemas.ts'
|
|
5
|
+
import { resolveInputJsonSchema, resolveOutputJsonSchema } from '../rpc/resolveRpcJsonSchema.ts'
|
|
4
6
|
import { rpcRegistry } from '../rpc/rpcRegistry.ts'
|
|
7
|
+
import { STATUS_TEXT } from './STATUS_TEXT.ts'
|
|
5
8
|
|
|
6
9
|
/*
|
|
7
10
|
Turns a rpc's resolved JSON Schema into OpenAPI query parameters — one
|
|
@@ -43,6 +46,34 @@ function multipartBodySchema(textSchema: Record<string, unknown>): Record<string
|
|
|
43
46
|
return schema
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
/*
|
|
50
|
+
Adds one `responses[status]` entry per typed-error branch (ADR-0030) — its description is the
|
|
51
|
+
status's standard reason phrase, and its `application/json` content is the error's projected `data`
|
|
52
|
+
schema (omitted for a bare `{}`, a nullary error with no payload). An already-present status (the
|
|
53
|
+
200, or any explicit response) is left untouched, so the success body and validation responses win a
|
|
54
|
+
collision. A no-op when the handler declared no typed errors (`errorJsonSchemas` undefined).
|
|
55
|
+
*/
|
|
56
|
+
function mergeErrorResponses(
|
|
57
|
+
responses: Record<string, unknown>,
|
|
58
|
+
errorJsonSchemas: ErrorJsonSchemas | undefined,
|
|
59
|
+
): void {
|
|
60
|
+
if (errorJsonSchemas === undefined) {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
for (const [status, schema] of Object.entries(errorJsonSchemas)) {
|
|
64
|
+
if (responses[status] !== undefined) {
|
|
65
|
+
continue
|
|
66
|
+
}
|
|
67
|
+
const response: Record<string, unknown> = {
|
|
68
|
+
description: STATUS_TEXT[Number(status)] ?? 'Error',
|
|
69
|
+
}
|
|
70
|
+
if (Object.keys(schema).length > 0) {
|
|
71
|
+
response.content = { 'application/json': { schema } }
|
|
72
|
+
}
|
|
73
|
+
responses[status] = response
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
46
77
|
/*
|
|
47
78
|
Builds an OpenAPI 3.1 document from the rpc registry — the HTTP surface
|
|
48
79
|
every rpc exposes regardless of which non-browser clients it advertises.
|
|
@@ -58,25 +89,38 @@ export function buildOpenApiSpec(info: {
|
|
|
58
89
|
for (const entry of rpcRegistry.values()) {
|
|
59
90
|
const url = entry.remote.url
|
|
60
91
|
const method = entry.remote.method
|
|
61
|
-
|
|
92
|
+
/*
|
|
93
|
+
Describe the parameters/request body from the `inputSchema` VALIDATOR when declared, else from
|
|
94
|
+
the handler's input parameter type projected to JSON Schema at build time (ADR-0030 input side —
|
|
95
|
+
`entry.inputJsonSchema`); otherwise the opaque fallback. The validator overrides the projection
|
|
96
|
+
(a runtime narrowing the type can't express), mirroring the 200-body output path below.
|
|
97
|
+
*/
|
|
98
|
+
const jsonSchema = resolveInputJsonSchema(entry) ?? jsonSchemaForSchema(undefined)
|
|
62
99
|
const description = jsonSchema.description as string | undefined
|
|
63
100
|
/*
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
fall back to a bare OK.
|
|
101
|
+
Describe the 200 body from the `outputSchema` VALIDATOR when declared, else from the handler
|
|
102
|
+
return type projected to JSON Schema at build time (ADR-0030 D2 — `entry.outputJsonSchema`);
|
|
103
|
+
otherwise fall back to a bare OK. The validator overrides the projection (a runtime narrowing
|
|
104
|
+
the type can't express).
|
|
67
105
|
*/
|
|
68
106
|
const okResponse: Record<string, unknown> = { description: 'OK' }
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
schema: jsonSchemaForSchema(entry.outputSchema),
|
|
73
|
-
},
|
|
74
|
-
}
|
|
107
|
+
const outputSchema = resolveOutputJsonSchema(entry)
|
|
108
|
+
if (outputSchema) {
|
|
109
|
+
okResponse.content = { 'application/json': { schema: outputSchema } }
|
|
75
110
|
}
|
|
111
|
+
/*
|
|
112
|
+
Merge the handler's typed-error branches (ADR-0030 — `entry.errorJsonSchemas`, a status-keyed
|
|
113
|
+
data-schema map baked from the `error.typed(...)` return branches) into the responses. Each
|
|
114
|
+
status gets a `responses[status]` entry describing the error's data payload; an existing
|
|
115
|
+
response (the 200, or a future explicit entry) is never clobbered. So the 200 success body and
|
|
116
|
+
every typed error's status + payload are documented from the one handler return type.
|
|
117
|
+
*/
|
|
118
|
+
const responses: Record<string, unknown> = { '200': okResponse }
|
|
119
|
+
mergeErrorResponses(responses, entry.errorJsonSchemas)
|
|
76
120
|
const operation: Record<string, unknown> = {
|
|
77
121
|
operationId: commandNameForUrl(url),
|
|
78
122
|
...(description ? { description } : {}),
|
|
79
|
-
responses
|
|
123
|
+
responses,
|
|
80
124
|
}
|
|
81
125
|
if (carriesBodyArgs(method)) {
|
|
82
126
|
operation.requestBody = entry.filesSchema
|
|
@@ -18,7 +18,7 @@ const ROUTE_CHUNK = /"(\/[^"]*)"\s*:\s*\(\)\s*=>\s*import\("\.\/((?:page|layout)
|
|
|
18
18
|
/* Reads a `_app` chunk's source by filename — gunzipped from the embedded asset map
|
|
19
19
|
(standalone compile) or off disk (dev + `abide start`). Undefined on a miss. */
|
|
20
20
|
function chunkReader(
|
|
21
|
-
|
|
21
|
+
appDir: string,
|
|
22
22
|
assets?: Assets,
|
|
23
23
|
): (name: string) => Promise<string | undefined> {
|
|
24
24
|
if (assets) {
|
|
@@ -28,23 +28,25 @@ function chunkReader(
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
return async (name) => {
|
|
31
|
-
const file = Bun.file(`${
|
|
31
|
+
const file = Bun.file(`${appDir}/${name}`)
|
|
32
32
|
return (await file.exists()) ? file.text() : undefined
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
/* The hashed client entry filename (`client-<hash>.js`), from the asset map or disk.
|
|
37
|
-
|
|
36
|
+
/* The hashed client entry filename (`client-<hash>.js`), from the asset map or disk.
|
|
37
|
+
Each build's `appDir` holds exactly one entry (production `_app`, or a dev
|
|
38
|
+
generation dir), so `.find` is unambiguous. */
|
|
39
|
+
async function findEntry(appDir: string, assets?: Assets): Promise<string | undefined> {
|
|
38
40
|
const isEntry = (name: string): boolean => /^client-[a-z0-9]+\.js$/i.test(name)
|
|
39
41
|
if (assets) {
|
|
40
42
|
const key = Object.keys(assets).find((path) => isEntry(path.replace('/_app/', '')))
|
|
41
43
|
return key?.replace('/_app/', '')
|
|
42
44
|
}
|
|
43
|
-
if (!existsSync(
|
|
45
|
+
if (!existsSync(appDir)) {
|
|
44
46
|
return undefined
|
|
45
47
|
}
|
|
46
48
|
const names = await Array.fromAsync(
|
|
47
|
-
new Glob('client-*.js').scan({ cwd:
|
|
49
|
+
new Glob('client-*.js').scan({ cwd: appDir, onlyFiles: true }),
|
|
48
50
|
)
|
|
49
51
|
return names.find(isEntry)
|
|
50
52
|
}
|
|
@@ -64,14 +66,14 @@ stream-close instead of waterfalling after it. Returns an empty map when the bun
|
|
|
64
66
|
absent or unparseable, so rendering degrades to the entry-only preload.
|
|
65
67
|
*/
|
|
66
68
|
export async function buildPreloadManifest({
|
|
67
|
-
|
|
69
|
+
appDir,
|
|
68
70
|
assets,
|
|
69
71
|
}: {
|
|
70
|
-
|
|
72
|
+
appDir: string
|
|
71
73
|
assets?: Assets
|
|
72
74
|
}): Promise<Record<string, string[]>> {
|
|
73
|
-
const read = chunkReader(
|
|
74
|
-
const entryName = await findEntry(
|
|
75
|
+
const read = chunkReader(appDir, assets)
|
|
76
|
+
const entryName = await findEntry(appDir, assets)
|
|
75
77
|
const entrySource = entryName ? await read(entryName) : undefined
|
|
76
78
|
if (entryName === undefined || entrySource === undefined) {
|
|
77
79
|
return {}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { NO_STORE } from '../../shared/CACHE_CONTROL_VALUES.ts'
|
|
2
|
-
import { TEXT_PLAIN } from '../../shared/TEXT_PLAIN.ts'
|
|
3
1
|
import { acceptsGzip } from './acceptsGzip.ts'
|
|
4
2
|
import { cacheControlForAsset } from './cacheControlForAsset.ts'
|
|
5
3
|
import { containsTraversal } from './containsTraversal.ts'
|
|
6
4
|
import { createAssetHeaderCache } from './createAssetHeaderCache.ts'
|
|
7
5
|
import { globToPathSet } from './globToPathSet.ts'
|
|
8
6
|
import { respondWithEmbeddedAsset } from './respondWithEmbeddedAsset.ts'
|
|
7
|
+
import { textResponse } from './textResponse.ts'
|
|
9
8
|
import type { Assets } from './types/Assets.ts'
|
|
10
9
|
|
|
11
10
|
/*
|
|
@@ -24,23 +23,29 @@ Unlike the public server this answers every `/_app/` request itself (404 on
|
|
|
24
23
|
a miss — nothing falls through past the build tree). The path-traversal
|
|
25
24
|
guard inspects the raw request URL because the WHATWG parser normalizes
|
|
26
25
|
encoded `..` segments away before `url.pathname` is visible.
|
|
26
|
+
|
|
27
|
+
`appDir` is the physical directory the `/_app/*` URLs map onto: `dist/_app` for
|
|
28
|
+
a production build, or — under `abide dev` — the generation dir this worker was
|
|
29
|
+
spawned on (`ABIDE_APP_DIR`). The URL space stays `/_app/*` either way; only the
|
|
30
|
+
disk location this worker reads from moves, so a rebuild's new worker reads a new
|
|
31
|
+
dir while this one keeps serving its own, immutable for its lifetime.
|
|
27
32
|
*/
|
|
28
33
|
export async function createAppAssetServer({
|
|
29
|
-
|
|
34
|
+
appDir,
|
|
30
35
|
assets,
|
|
31
36
|
}: {
|
|
32
|
-
|
|
37
|
+
appDir: string
|
|
33
38
|
assets?: Assets
|
|
34
39
|
}): Promise<(req: Request, url: URL) => Promise<Response>> {
|
|
35
40
|
// Per-pathname asset header bundles, hashed-chunk-aware Cache-Control.
|
|
36
41
|
const headersForAsset = createAssetHeaderCache(cacheControlForAsset)
|
|
37
|
-
/* Boot snapshot of every disk
|
|
38
|
-
header cache keys on (request-controlled) pathnames,
|
|
39
|
-
`/_app/*` probes would grow it without bound. A
|
|
40
|
-
|
|
42
|
+
/* Boot snapshot of every disk chunk path (keyed by its `/_app/*` URL), mirroring
|
|
43
|
+
createPublicAssetServer: the header cache keys on (request-controlled) pathnames,
|
|
44
|
+
so building bundles for junk `/_app/*` probes would grow it without bound. A
|
|
45
|
+
rebuild spawns a fresh worker (against a fresh generation dir), re-snapshotting. */
|
|
41
46
|
const diskPaths = assets
|
|
42
47
|
? new Set<string>()
|
|
43
|
-
: await globToPathSet(
|
|
48
|
+
: await globToPathSet(appDir, '**/*', (file) => `/_app/${file}`)
|
|
44
49
|
/* Derive the precompressed `.gz` sibling set from the single tree scan (it already
|
|
45
50
|
includes the `.gz` files) — a gzip-capable client gets those bytes without
|
|
46
51
|
on-the-fly compression. Keyed by the base path (the `.gz` suffix stripped). */
|
|
@@ -49,11 +54,7 @@ export async function createAppAssetServer({
|
|
|
49
54
|
)
|
|
50
55
|
|
|
51
56
|
// Fresh 404 per call — a Response body is single-use, so it can't be hoisted to a const.
|
|
52
|
-
const notFound = (): Response =>
|
|
53
|
-
new Response('Not Found', {
|
|
54
|
-
status: 404,
|
|
55
|
-
headers: { 'Content-Type': TEXT_PLAIN, 'Cache-Control': NO_STORE },
|
|
56
|
-
})
|
|
57
|
+
const notFound = (): Response => textResponse(404)
|
|
57
58
|
|
|
58
59
|
return async function serveAppAsset(req, url) {
|
|
59
60
|
if (containsTraversal(req.url)) {
|
|
@@ -88,7 +89,9 @@ export async function createAppAssetServer({
|
|
|
88
89
|
return notFound()
|
|
89
90
|
}
|
|
90
91
|
const { base: baseHeaders, gzip: gzipHeaders } = headersForAsset(assetPath)
|
|
91
|
-
|
|
92
|
+
// Map the `/_app/<file>` URL onto the physical dir: strip the `/_app` prefix
|
|
93
|
+
// (5 chars) and join under appDir, which already IS the `_app` (or generation) dir.
|
|
94
|
+
const diskPath = appDir + assetPath.slice('/_app'.length)
|
|
92
95
|
if (acceptsGzip(req) && diskGzipPaths.has(assetPath)) {
|
|
93
96
|
return new Response(Bun.file(`${diskPath}.gz`), { headers: gzipHeaders })
|
|
94
97
|
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { NO_STORE } from '../../shared/CACHE_CONTROL_VALUES.ts'
|
|
2
|
+
import { matchRoute } from '../../shared/matchRoute.ts'
|
|
3
|
+
import { normalizePathname } from '../../shared/normalizePathname.ts'
|
|
4
|
+
import { parseRouteSegments } from '../../shared/parseRouteSegments.ts'
|
|
5
|
+
import type { Pages } from '../../ui/types/Pages.ts'
|
|
6
|
+
import type { RemoteRoutes } from '../rpc/types/RemoteRoutes.ts'
|
|
7
|
+
import type { createRouteDispatcher } from './createRouteDispatcher.ts'
|
|
8
|
+
|
|
9
|
+
/* A route handler pre-bound to one registered URL — the dispatcher's output. */
|
|
10
|
+
type RouteHandler = ReturnType<ReturnType<typeof createRouteDispatcher>>
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
The URL-shape decision for one request, once the framework plumbing has passed
|
|
14
|
+
on it. A `handler` case (rpc, page, or the openapi doc) is dispatched through
|
|
15
|
+
the request scope; a `redirect` is returned verbatim; the asset cases name
|
|
16
|
+
which asset server serves the path, with `publicAsset` as the catch-all bucket
|
|
17
|
+
whose miss falls through to the 404 path. Keeping the decision data-only lets a
|
|
18
|
+
test assert it without booting Bun.serve — the effects (ALS dispatch, asset
|
|
19
|
+
serving, 404 render) stay in the fetch closure.
|
|
20
|
+
*/
|
|
21
|
+
export type RouteResolution =
|
|
22
|
+
| { kind: 'redirect'; response: Response }
|
|
23
|
+
| { kind: 'handler'; handler: RouteHandler; params: Record<string, string> }
|
|
24
|
+
| { kind: 'appAsset' }
|
|
25
|
+
| { kind: 'publicAsset' }
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
Owns the URL-shape decisions that used to sit inline in the Bun.serve fetch
|
|
29
|
+
closure: canonical-slash redirects (308), the openapi document's build +
|
|
30
|
+
memo, and the asset-precedence ordering. Route dispatch (rpc-vs-page-vs-404,
|
|
31
|
+
method matching) stays behind createRouteDispatcher; this resolver composes its
|
|
32
|
+
per-URL handlers and adds the request-URL → decision layer on top, so both the
|
|
33
|
+
redirect logic and the memo semantics become unit-testable without a listener.
|
|
34
|
+
|
|
35
|
+
App routes are matched AFTER the `/__abide/*` plumbing (a reserved namespace no
|
|
36
|
+
app route occupies) and BEFORE the root-level framework surfaces (the openapi
|
|
37
|
+
doc, `/_app/`, public/ files), so a page route shadows a same-path public file
|
|
38
|
+
— the precedence the Bun routes table used to impose implicitly, now pinned
|
|
39
|
+
here. rpc URLs are flat literals (always `/rpc/...`) resolved by direct lookup;
|
|
40
|
+
page URLs carry `[name]` / `[[name]]` / `[...rest]` segments resolved through
|
|
41
|
+
the shared matchRoute — the same matcher the client router runs — so params
|
|
42
|
+
decode and precedence agree across the sides by construction.
|
|
43
|
+
*/
|
|
44
|
+
export function createAppRouteResolver({
|
|
45
|
+
pages,
|
|
46
|
+
rpc,
|
|
47
|
+
buildRouteHandler,
|
|
48
|
+
openApiPath,
|
|
49
|
+
buildOpenApiDocument,
|
|
50
|
+
}: {
|
|
51
|
+
pages: Pages
|
|
52
|
+
rpc: RemoteRoutes
|
|
53
|
+
buildRouteHandler: ReturnType<typeof createRouteDispatcher>
|
|
54
|
+
openApiPath: string
|
|
55
|
+
buildOpenApiDocument: () => Promise<Record<string, unknown>>
|
|
56
|
+
}): (req: Request, url: URL) => RouteResolution {
|
|
57
|
+
/* Handlers pre-bound per registered URL. rpc URLs dispatch by direct lookup;
|
|
58
|
+
page URLs resolve through matchRoute against pageRouteUrls below. */
|
|
59
|
+
const rpcHandlers = new Map<string, RouteHandler>()
|
|
60
|
+
for (const routeUrl of Object.keys(rpc)) {
|
|
61
|
+
rpcHandlers.set(routeUrl, buildRouteHandler(routeUrl))
|
|
62
|
+
}
|
|
63
|
+
const pageHandlers = new Map<string, RouteHandler>()
|
|
64
|
+
for (const routeUrl of Object.keys(pages)) {
|
|
65
|
+
/* A `[...rest]` consumes every remaining segment, so segments after it
|
|
66
|
+
can never constrain matching — the route would silently serve paths
|
|
67
|
+
it shouldn't. Fail at boot instead. */
|
|
68
|
+
const segments = parseRouteSegments(routeUrl)
|
|
69
|
+
const catchAllIndex = segments.findIndex(
|
|
70
|
+
(segment) => segment.kind === 'param' && segment.catchAll,
|
|
71
|
+
)
|
|
72
|
+
if (catchAllIndex !== -1 && catchAllIndex !== segments.length - 1) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`[abide] invalid page route ${routeUrl}: a [...name] catch-all must be the last segment`,
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
pageHandlers.set(routeUrl, buildRouteHandler(routeUrl))
|
|
78
|
+
}
|
|
79
|
+
const pageRouteUrls = Object.keys(pages)
|
|
80
|
+
|
|
81
|
+
/* Built on first request, then reused — the rpc registry is frozen after load.
|
|
82
|
+
Memoised as a promise so two concurrent cold requests share one build instead
|
|
83
|
+
of both building (the second otherwise clobbering the first). A rejected build
|
|
84
|
+
clears the memo so the next request retries rather than caching the failure. */
|
|
85
|
+
let openApiSpec: Promise<Record<string, unknown>> | undefined
|
|
86
|
+
const openApiHandler: RouteHandler = async () => {
|
|
87
|
+
openApiSpec ??= buildOpenApiDocument().catch((error) => {
|
|
88
|
+
// Don't cache a failed build — clear the memo so a later request
|
|
89
|
+
// retries instead of 500-ing forever.
|
|
90
|
+
openApiSpec = undefined
|
|
91
|
+
throw error
|
|
92
|
+
})
|
|
93
|
+
return Response.json(await openApiSpec, { headers: { 'Cache-Control': NO_STORE } })
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return function resolveAppRoute(_req, url): RouteResolution {
|
|
97
|
+
const rpcHandler = rpcHandlers.get(url.pathname)
|
|
98
|
+
if (rpcHandler) {
|
|
99
|
+
return { kind: 'handler', handler: rpcHandler, params: {} }
|
|
100
|
+
}
|
|
101
|
+
/*
|
|
102
|
+
Pages match only in canonical slash form; a non-canonical request
|
|
103
|
+
(`/admin/`, `//admin`) that would match is 308'd to the canonical URL
|
|
104
|
+
instead of served. Serving it directly would hand app.handle — the auth
|
|
105
|
+
seam — a pathname the matcher silently normalized away, so an exact-match
|
|
106
|
+
guard on `/admin` never sees the request it's guarding (the old Bun routes
|
|
107
|
+
table 404'd these forms; the redirect keeps the guard sound AND the URL
|
|
108
|
+
friendly). rpc URLs stay exact-match strict, as they always were.
|
|
109
|
+
*/
|
|
110
|
+
const canonicalPathname = normalizePathname(url.pathname)
|
|
111
|
+
if (canonicalPathname !== url.pathname) {
|
|
112
|
+
if (matchRoute(pageRouteUrls, canonicalPathname)) {
|
|
113
|
+
return {
|
|
114
|
+
kind: 'redirect',
|
|
115
|
+
response: new Response(null, {
|
|
116
|
+
status: 308,
|
|
117
|
+
headers: {
|
|
118
|
+
Location: `${canonicalPathname}${url.search}`,
|
|
119
|
+
'Cache-Control': NO_STORE,
|
|
120
|
+
},
|
|
121
|
+
}),
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
const matchedPage = matchRoute(pageRouteUrls, url.pathname)
|
|
126
|
+
if (matchedPage) {
|
|
127
|
+
const pageHandler = pageHandlers.get(matchedPage.route)
|
|
128
|
+
if (pageHandler) {
|
|
129
|
+
return { kind: 'handler', handler: pageHandler, params: matchedPage.params }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (url.pathname === openApiPath) {
|
|
134
|
+
return { kind: 'handler', handler: openApiHandler, params: {} }
|
|
135
|
+
}
|
|
136
|
+
/* Static assets sidestep ALS + the per-request CacheStore + app.handle
|
|
137
|
+
(see the fetch closure); `/_app/` build output takes precedence over
|
|
138
|
+
public/ files, and anything else falls to the public bucket whose miss
|
|
139
|
+
becomes the 404. */
|
|
140
|
+
if (url.pathname.startsWith('/_app/')) {
|
|
141
|
+
return { kind: 'appAsset' }
|
|
142
|
+
}
|
|
143
|
+
return { kind: 'publicAsset' }
|
|
144
|
+
}
|
|
145
|
+
}
|