@abide/abide 0.43.1 → 0.44.1
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 +293 -219
- package/CHANGELOG.md +63 -0
- package/README.md +191 -67
- package/package.json +2 -2
- package/src/abideLsp.ts +8 -4
- package/src/abideResolverPlugin.ts +17 -2
- package/src/build.ts +9 -0
- package/src/buildDisconnected.ts +2 -0
- package/src/devEntry.ts +44 -9
- package/src/lib/bundle/probeAbideServer.ts +1 -1
- package/src/lib/cli/parseArgvForRpc.ts +25 -26
- package/src/lib/cli/runCli.ts +9 -24
- package/src/lib/cli/tokenizeArgvFlags.ts +84 -0
- package/src/lib/cli/types/CliManifest.ts +6 -4
- package/src/lib/mcp/annotationsForMethod.ts +1 -1
- package/src/lib/mcp/toolResultFromResponse.ts +1 -1
- package/src/lib/mcp/types/JsonRpcRequest.ts +3 -3
- package/src/lib/server/AppModule.ts +1 -1
- package/src/lib/server/cli/handleCliDownload.ts +7 -5
- package/src/lib/server/cli/installScript.ts +3 -3
- package/src/lib/server/prompts/promptRegistry.ts +1 -1
- package/src/lib/server/prompts/types/Prompt.ts +2 -1
- package/src/lib/server/rpc/defineRpc.ts +9 -1
- package/src/lib/server/rpc/dispatchRpcInProcess.ts +2 -2
- package/src/lib/server/rpc/types/RpcHelper.ts +1 -1
- package/src/lib/server/runtime/createPublicAssetServer.ts +2 -2
- package/src/lib/server/runtime/createRouteDispatcher.ts +1 -1
- package/src/lib/server/runtime/createServer.ts +50 -12
- package/src/lib/server/runtime/createUiPageRenderer.ts +7 -11
- package/src/lib/server/runtime/crossOriginForbidden.ts +2 -2
- package/src/lib/server/runtime/crossOriginGate.ts +1 -1
- package/src/lib/server/runtime/devHotModuleResponse.ts +3 -3
- package/src/lib/server/runtime/disableIdleTimeoutForStream.ts +1 -1
- package/src/lib/server/runtime/gzipResponse.ts +14 -16
- package/src/lib/server/runtime/logExposedSurfaces.ts +3 -4
- package/src/lib/server/runtime/runWithRequestScope.ts +4 -1
- package/src/lib/server/runtime/serializeCacheSnapshot.ts +3 -8
- package/src/lib/server/runtime/snapshotEntryFromCache.ts +10 -7
- package/src/lib/server/runtime/streamFromIterator.ts +2 -2
- package/src/lib/server/runtime/types/InspectorRpc.ts +2 -2
- package/src/lib/server/runtime/types/RequestStore.ts +16 -0
- package/src/lib/server/runtime/withResponseDefaults.ts +8 -3
- package/src/lib/server/socket.ts +1 -1
- package/src/lib/server/sockets/createSocketDispatcher.ts +2 -2
- package/src/lib/server/sockets/defineSocket.ts +5 -1
- package/src/lib/server/sockets/socketRegistry.ts +2 -2
- package/src/lib/server/sockets/types/SocketClientFrame.ts +3 -3
- package/src/lib/shared/CACHE_WRAPPED.ts +3 -2
- package/src/lib/shared/REMOTE_FUNCTION.ts +1 -1
- package/src/lib/shared/RPC_ARGS_TYPE.ts +7 -0
- package/src/lib/shared/STREAMING_CONTENT_TYPES.ts +3 -2
- package/src/lib/shared/activeCacheStore.ts +5 -14
- package/src/lib/shared/activePage.ts +5 -19
- package/src/lib/shared/augmentModule.ts +18 -0
- package/src/lib/shared/basePath.ts +6 -5
- package/src/lib/shared/baseResolver.ts +10 -0
- package/src/lib/shared/baseSlot.ts +6 -12
- package/src/lib/shared/bodyValueForKind.ts +35 -0
- package/src/lib/shared/cache.ts +22 -2
- package/src/lib/shared/cacheEntryFromSnapshot.ts +23 -18
- package/src/lib/shared/cacheStoreResolver.ts +12 -0
- package/src/lib/shared/cacheStoreSlot.ts +5 -13
- package/src/lib/shared/changeAffectsClient.ts +35 -0
- package/src/lib/shared/createPushIterator.ts +1 -1
- package/src/lib/shared/createResolverSlot.ts +37 -0
- package/src/lib/shared/debugGate.ts +90 -0
- package/src/lib/shared/decodeResponse.ts +11 -8
- package/src/lib/shared/emitLogRecord.ts +18 -4
- package/src/lib/shared/globalCacheStoreResolver.ts +12 -0
- package/src/lib/shared/globalCacheStoreSlot.ts +6 -11
- package/src/lib/shared/hasReplayableRequest.ts +17 -0
- package/src/lib/shared/html.ts +7 -26
- package/src/lib/shared/isDebugEnabled.ts +3 -10
- package/src/lib/shared/isDebugNegated.ts +4 -6
- package/src/lib/shared/manifestModule.ts +2 -2
- package/src/lib/shared/online.ts +1 -1
- package/src/lib/shared/outboxProbeSlot.ts +1 -1
- package/src/lib/shared/pageResolver.ts +16 -0
- package/src/lib/shared/pageSlot.ts +5 -14
- package/src/lib/shared/pageUrlForFile.ts +1 -1
- package/src/lib/shared/requestScopeResolver.ts +12 -0
- package/src/lib/shared/requestScopeSlot.ts +7 -12
- package/src/lib/shared/responseBodyKind.ts +35 -0
- package/src/lib/shared/routeParamsShape.ts +20 -0
- package/src/lib/shared/setBaseResolver.ts +2 -4
- package/src/lib/shared/setCacheStoreResolver.ts +3 -5
- package/src/lib/shared/setGlobalCacheStoreResolver.ts +3 -5
- package/src/lib/shared/setPageResolver.ts +2 -5
- package/src/lib/shared/setRequestScopeResolver.ts +3 -5
- package/src/lib/shared/snapshotShippable.ts +22 -0
- package/src/lib/shared/types/CacheEntry.ts +1 -1
- package/src/lib/shared/types/CacheInvalidation.ts +3 -3
- package/src/lib/shared/types/ClientFlags.ts +5 -3
- package/src/lib/shared/types/DebugGate.ts +10 -0
- package/src/lib/shared/types/LastConnection.ts +1 -1
- package/src/lib/shared/types/ResolverSlot.ts +10 -0
- package/src/lib/shared/url.ts +4 -1
- package/src/lib/shared/withBase.ts +1 -2
- package/src/lib/shared/writeDts.ts +10 -3
- package/src/lib/shared/writePublicAssetsDts.ts +5 -9
- package/src/lib/shared/writeRoutesDts.ts +14 -46
- package/src/lib/shared/writeRpcDts.ts +13 -12
- package/src/lib/shared/writeTestRpcDts.ts +11 -11
- package/src/lib/shared/writeTestSocketsDts.ts +9 -9
- package/src/lib/test/createTestSocketChannel.ts +3 -3
- package/src/lib/ui/COMPONENT_WRAPPER_PREFIX.ts +1 -1
- package/src/lib/ui/compile/ABIDE_SEMANTIC_TOKENS_LEGEND.ts +40 -6
- package/src/lib/ui/compile/REACTIVE_CALLEES.ts +1 -1
- package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +49 -37
- package/src/lib/ui/compile/abideUiPlugin.ts +10 -8
- package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +27 -13
- package/src/lib/ui/compile/awaitPlan.ts +14 -2
- package/src/lib/ui/compile/catchBinding.ts +8 -0
- package/src/lib/ui/compile/classStyleMergePlan.ts +108 -0
- package/src/lib/ui/compile/collectAbideDiagnostics.ts +11 -0
- package/src/lib/ui/compile/compileComponent.ts +4 -4
- package/src/lib/ui/compile/compileModule.ts +56 -21
- package/src/lib/ui/compile/compileSSR.ts +12 -10
- package/src/lib/ui/compile/compileShadow.ts +163 -12
- package/src/lib/ui/compile/composeProps.ts +3 -3
- package/src/lib/ui/compile/createShadowLanguageService.ts +58 -4
- package/src/lib/ui/compile/createShadowProgram.ts +2 -1
- package/src/lib/ui/compile/createShadowScope.ts +42 -0
- package/src/lib/ui/compile/decodeHtmlEntities.ts +4 -0
- package/src/lib/ui/compile/desugarSignals.ts +12 -7
- package/src/lib/ui/compile/eachPlan.ts +61 -0
- package/src/lib/ui/compile/elementPlan.ts +55 -0
- package/src/lib/ui/compile/encodeSemanticTokens.ts +24 -1
- package/src/lib/ui/compile/generateBuild.ts +156 -127
- package/src/lib/ui/compile/generateSSR.ts +112 -73
- package/src/lib/ui/compile/ifPlan.ts +7 -1
- package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +25 -0
- package/src/lib/ui/compile/isPlainIdentifier.ts +7 -0
- package/src/lib/ui/compile/isTextLeaf.ts +8 -7
- package/src/lib/ui/compile/lowerContext.ts +36 -46
- package/src/lib/ui/compile/lowerDocAccess.ts +2 -2
- package/src/lib/ui/compile/lowerScript.ts +1 -1
- package/src/lib/ui/compile/markupTokens.ts +313 -0
- package/src/lib/ui/compile/pagePropsType.ts +23 -0
- package/src/lib/ui/compile/parseTemplate.ts +135 -62
- package/src/lib/ui/compile/reactiveBinding.ts +47 -0
- package/src/lib/ui/compile/renameSignalRefs.ts +44 -10
- package/src/lib/ui/compile/skeletonable.ts +5 -5
- package/src/lib/ui/compile/snippetPlan.ts +28 -0
- package/src/lib/ui/compile/spreadExcludedNames.ts +4 -4
- package/src/lib/ui/compile/structuralBlockTokens.ts +3 -4
- package/src/lib/ui/compile/switchPlan.ts +7 -1
- package/src/lib/ui/compile/tryPlan.ts +8 -1
- package/src/lib/ui/compile/types/AnalyzedComponent.ts +2 -2
- package/src/lib/ui/compile/types/Binding.ts +24 -0
- package/src/lib/ui/compile/types/CompiledShadow.ts +13 -1
- package/src/lib/ui/compile/types/ElementPlan.ts +43 -0
- package/src/lib/ui/compile/types/ShadowKind.ts +9 -0
- package/src/lib/ui/compile/types/ShadowScope.ts +19 -0
- package/src/lib/ui/compile/types/TemplateAttr.ts +7 -0
- package/src/lib/ui/compile/types/TemplateNode.ts +9 -9
- package/src/lib/ui/compile/withBindings.ts +45 -0
- package/src/lib/ui/createScope.ts +2 -2
- package/src/lib/ui/dom/MATHML_NAMESPACE.ts +2 -2
- package/src/lib/ui/dom/SVG_NAMESPACE.ts +3 -2
- package/src/lib/ui/dom/appendText.ts +1 -1
- package/src/lib/ui/dom/applyResolved.ts +1 -1
- package/src/lib/ui/dom/buildDetachedRange.ts +4 -3
- package/src/lib/ui/dom/cloneStatic.ts +3 -2
- package/src/lib/ui/dom/each.ts +26 -7
- package/src/lib/ui/dom/foreignWrapperTag.ts +2 -2
- package/src/lib/ui/dom/mountChild.ts +8 -1
- package/src/lib/ui/dom/mountSwappableRange.ts +79 -0
- package/src/lib/ui/dom/readCall.ts +6 -4
- package/src/lib/ui/dom/scopeLabel.ts +3 -3
- package/src/lib/ui/dom/skeleton.ts +10 -1
- package/src/lib/ui/dom/switchBlock.ts +17 -63
- package/src/lib/ui/dom/types/EachRow.ts +4 -0
- package/src/lib/ui/dom/types/SkeletonHoles.ts +4 -2
- package/src/lib/ui/dom/when.ts +11 -64
- package/src/lib/ui/history.ts +1 -2
- package/src/lib/ui/html.ts +28 -0
- package/src/lib/ui/installHotBridge.ts +0 -2
- package/src/lib/ui/renderChain.ts +16 -3
- package/src/lib/ui/router.ts +24 -3
- package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +1 -1
- package/src/lib/ui/runtime/CHILD_PRESENT.ts +8 -0
- package/src/lib/ui/runtime/OUTLET_MARKER.ts +2 -2
- package/src/lib/ui/runtime/RANGE_MARKER.ts +1 -1
- package/src/lib/ui/runtime/REACTIVE_CONTEXT.ts +9 -3
- package/src/lib/ui/runtime/captureModelDoc.ts +1 -1
- package/src/lib/ui/runtime/createDoc.ts +107 -13
- package/src/lib/ui/runtime/createEffectNode.ts +5 -5
- package/src/lib/ui/runtime/flushEffects.ts +22 -9
- package/src/lib/ui/runtime/historyEntries.ts +1 -1
- package/src/lib/ui/runtime/track.ts +4 -2
- package/src/lib/ui/runtime/trigger.ts +34 -20
- package/src/lib/ui/runtime/types/Route.ts +2 -2
- package/src/lib/ui/runtime/types/UiComponent.ts +3 -2
- package/src/lib/ui/runtime/types/UiProps.ts +9 -12
- package/src/lib/ui/runtime/walkPath.ts +15 -3
- package/src/lib/ui/startClient.ts +41 -14
- package/src/lib/ui/state.ts +3 -3
- package/src/lib/ui/types/Scope.ts +3 -3
- package/src/serverBuildPlugins.ts +11 -5
- package/src/zodCjsPlugin.ts +51 -0
- package/template/.zed/settings.json +7 -1
- package/template/package.json +3 -0
- package/template/src/ui/pages/layout.abide +3 -5
- package/src/lib/shared/matchesDebugPattern.ts +0 -16
- package/src/lib/shared/parseDebugPatterns.ts +0 -21
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createCacheStore } from './createCacheStore.ts'
|
|
2
|
+
import { createResolverSlot } from './createResolverSlot.ts'
|
|
3
|
+
import type { CacheStore } from './types/CacheStore.ts'
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
The active-CacheStore slot/resolver/reader bundle. The server entry installs an
|
|
7
|
+
ALS-backed resolver (request-scoped); the client entry a module-singleton one.
|
|
8
|
+
With no resolver registered, a single fallback store is created lazily so
|
|
9
|
+
isolated tests work without booting the runtime. cacheStoreSlot / activeCacheStore
|
|
10
|
+
re-export the slot and reader; setCacheStoreResolver the setter.
|
|
11
|
+
*/
|
|
12
|
+
export const cacheStoreResolver = createResolverSlot<CacheStore>(createCacheStore)
|
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { cacheStoreResolver } from './cacheStoreResolver.ts'
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
|
-
Internal slot the runtime entries register their resolver into
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
single lazy store used only when no resolver is registered — keeps
|
|
8
|
-
isolated tests working without forcing them to spin up the runtime.
|
|
4
|
+
Internal slot the runtime entries register their resolver into (see
|
|
5
|
+
cacheStoreResolver). Exposed so test helpers snapshot/poke `.resolver` and
|
|
6
|
+
`.fallback` directly.
|
|
9
7
|
*/
|
|
10
|
-
export const cacheStoreSlot
|
|
11
|
-
resolver: (() => CacheStore | undefined) | undefined
|
|
12
|
-
fallback: CacheStore | undefined
|
|
13
|
-
} = {
|
|
14
|
-
resolver: undefined,
|
|
15
|
-
fallback: undefined,
|
|
16
|
-
}
|
|
8
|
+
export const cacheStoreSlot = cacheStoreResolver.slot
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Classifies a watcher change (a path relative to the project's `src/`, with
|
|
3
|
+
either OS separator) by whether it can alter the client bundle. The dev
|
|
4
|
+
orchestrator uses this to skip the full client `Bun.build` for changes that
|
|
5
|
+
only reach the server or MCP runtimes, while still restarting the SSR worker.
|
|
6
|
+
|
|
7
|
+
Server/MCP-only — client unaffected, skip the client rebuild:
|
|
8
|
+
- `server/rpc/**` — the client only ships proxy stubs derived from the file
|
|
9
|
+
path (rpc url) and the `<METHOD>(...)` wrapper, not the
|
|
10
|
+
handler body; the rpc manifest is server-only. A body
|
|
11
|
+
edit produces an identical stub. (Risk: changing the
|
|
12
|
+
method, the export name, or `outbox: true` does change
|
|
13
|
+
the stub — those need a manual full rebuild.)
|
|
14
|
+
- `server/sockets/**` — name-only `socketProxy` stubs derived from the file
|
|
15
|
+
path; socket opts are server-side. Body edits don't
|
|
16
|
+
change the stub.
|
|
17
|
+
- `mcp/**` — prompts/resources are MCP-only; the client bundle never
|
|
18
|
+
imports them (it emits empty stubs defensively).
|
|
19
|
+
- `server/config.ts` — server boot-time env validation (`abide:config`); never
|
|
20
|
+
reaches the client.
|
|
21
|
+
|
|
22
|
+
Everything else (`.abide` templates, `ui/**`, `shared/**`, `app.ts`, state
|
|
23
|
+
files, the rest of `server/`) is treated as client-affecting. Conservative by
|
|
24
|
+
default: an unrecognised path returns true so an ambiguous change still pays the
|
|
25
|
+
full rebuild rather than risking a stale client.
|
|
26
|
+
*/
|
|
27
|
+
export function changeAffectsClient(relativePath: string): boolean {
|
|
28
|
+
const path = relativePath.split('\\').join('/')
|
|
29
|
+
const serverOnly =
|
|
30
|
+
path.startsWith('server/rpc/') ||
|
|
31
|
+
path.startsWith('server/sockets/') ||
|
|
32
|
+
path.startsWith('mcp/') ||
|
|
33
|
+
path === 'server/config.ts'
|
|
34
|
+
return !serverOnly
|
|
35
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
|
|
2
|
+
Bounded-queue AsyncIterator factory shared by the in-process
|
|
3
3
|
socket fan-out (defineSocket) and the client-side ws proxy
|
|
4
4
|
(socketProxy). Callers push values, signal end, signal an error, or
|
|
5
5
|
signal a transport disconnect; the iterator drains a queue then awaits
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ResolverSlot } from './types/ResolverSlot.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
The slot/resolver/reader triple the runtime entries share for cache store,
|
|
5
|
+
page, and mount-base state. A runtime entry registers a side-specific resolver
|
|
6
|
+
via `set` (ALS-backed on the server, a module singleton on the client); `get`
|
|
7
|
+
returns the resolved value, falling back to a single lazily-created fallback
|
|
8
|
+
when no resolver is registered so isolated tests work without booting the
|
|
9
|
+
runtime. `slot` stays exposed so test helpers can snapshot/poke `.resolver` and
|
|
10
|
+
`.fallback` directly. Pass `createFallback` for a lazily-built, cached fallback
|
|
11
|
+
(cache store, page); omit it for a slot whose fallback is a plain value set
|
|
12
|
+
directly (mount base) — `get` then returns `T | undefined`.
|
|
13
|
+
*/
|
|
14
|
+
export function createResolverSlot<T>(createFallback?: () => T): {
|
|
15
|
+
slot: ResolverSlot<T>
|
|
16
|
+
set: (fn: () => T | undefined) => void
|
|
17
|
+
get: () => T | undefined
|
|
18
|
+
} {
|
|
19
|
+
const slot: ResolverSlot<T> = { resolver: undefined, fallback: undefined }
|
|
20
|
+
|
|
21
|
+
function set(fn: () => T | undefined): void {
|
|
22
|
+
slot.resolver = fn
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function get(): T | undefined {
|
|
26
|
+
const fromResolver = slot.resolver?.()
|
|
27
|
+
if (fromResolver !== undefined) {
|
|
28
|
+
return fromResolver
|
|
29
|
+
}
|
|
30
|
+
if (createFallback && slot.fallback === undefined) {
|
|
31
|
+
slot.fallback = createFallback()
|
|
32
|
+
}
|
|
33
|
+
return slot.fallback
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { slot, set, get }
|
|
37
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { DebugGate } from './types/DebugGate.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
The DEBUG-gate core: one memoized decision-maker per distinct DEBUG string,
|
|
5
|
+
npm-debug conventions. `enabled(name)` answers "is gated channel X on?",
|
|
6
|
+
`negated(name)` answers "did a `-X` pattern explicitly shut it off?" — the two
|
|
7
|
+
questions isDebugEnabled / isDebugNegated expose. Both were re-deriving the same
|
|
8
|
+
parse + filter work on every emission; here the include/exclude partition is
|
|
9
|
+
computed once per env value (a single pass, not two throwaway-array filters) and
|
|
10
|
+
each per-name boolean is cached, since the server's DEBUG is stable and channel
|
|
11
|
+
names are a tiny fixed set.
|
|
12
|
+
|
|
13
|
+
A new env string (the browser's abide-debug localStorage toggle, or tests
|
|
14
|
+
mutating process.env.DEBUG) misses the single-slot memo and rebuilds — so a
|
|
15
|
+
runtime DEBUG change takes effect on its next read.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// One DEBUG pattern (already `-`-stripped) against one channel name: `*` matches
|
|
19
|
+
// everything, `abide:*` matches 'abide' and every 'abide:…' sub-channel, else exact.
|
|
20
|
+
function matches(name: string, pattern: string): boolean {
|
|
21
|
+
if (pattern === '*') {
|
|
22
|
+
return true
|
|
23
|
+
}
|
|
24
|
+
if (pattern.endsWith(':*')) {
|
|
25
|
+
const prefix = pattern.slice(0, -2)
|
|
26
|
+
return name === prefix || name.startsWith(`${prefix}:`)
|
|
27
|
+
}
|
|
28
|
+
return pattern === name
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Builds a gate for one env value: partitions the comma list into include/exclude
|
|
32
|
+
// patterns in a single pass, then memoizes each per-name decision in a map.
|
|
33
|
+
function buildGate(env: string | undefined): DebugGate {
|
|
34
|
+
const includes: string[] = []
|
|
35
|
+
const excludes: string[] = []
|
|
36
|
+
if (env) {
|
|
37
|
+
for (const raw of env.split(',')) {
|
|
38
|
+
const pattern = raw.trim()
|
|
39
|
+
if (pattern === '') {
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
if (pattern.startsWith('-')) {
|
|
43
|
+
excludes.push(pattern.slice(1))
|
|
44
|
+
} else {
|
|
45
|
+
includes.push(pattern)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/* Cache-hit fast path: a Map keyed on channel name, since names are a tiny
|
|
50
|
+
fixed set and the gate runs on every log emission. Negated and enabled
|
|
51
|
+
cache separately — a channel can be queried through either question. */
|
|
52
|
+
const negatedCache = new Map<string, boolean>()
|
|
53
|
+
const enabledCache = new Map<string, boolean>()
|
|
54
|
+
return {
|
|
55
|
+
negated(name: string): boolean {
|
|
56
|
+
const cached = negatedCache.get(name)
|
|
57
|
+
if (cached !== undefined) {
|
|
58
|
+
return cached
|
|
59
|
+
}
|
|
60
|
+
const result = excludes.some((pattern) => matches(name, pattern))
|
|
61
|
+
negatedCache.set(name, result)
|
|
62
|
+
return result
|
|
63
|
+
},
|
|
64
|
+
enabled(name: string): boolean {
|
|
65
|
+
const cached = enabledCache.get(name)
|
|
66
|
+
if (cached !== undefined) {
|
|
67
|
+
return cached
|
|
68
|
+
}
|
|
69
|
+
// Exclusions win over inclusions (npm-debug negation precedence).
|
|
70
|
+
const result =
|
|
71
|
+
!excludes.some((pattern) => matches(name, pattern)) &&
|
|
72
|
+
includes.some((pattern) => matches(name, pattern))
|
|
73
|
+
enabledCache.set(name, result)
|
|
74
|
+
return result
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Single-slot memo on the env string: the gate (partition + decision caches) is
|
|
80
|
+
rebuilt only when DEBUG changes. */
|
|
81
|
+
let lastEnv: string | undefined
|
|
82
|
+
let lastGate: DebugGate = buildGate(undefined)
|
|
83
|
+
|
|
84
|
+
export function debugGate(env: string | undefined): DebugGate {
|
|
85
|
+
if (env !== lastEnv) {
|
|
86
|
+
lastEnv = env
|
|
87
|
+
lastGate = buildGate(env)
|
|
88
|
+
}
|
|
89
|
+
return lastGate
|
|
90
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bodyValueForKind, DEFER } from './bodyValueForKind.ts'
|
|
1
2
|
import { contentBodyKind } from './contentBodyKind.ts'
|
|
2
3
|
import { contentTypeOf } from './contentTypeOf.ts'
|
|
3
4
|
import { httpErrorFor } from './httpErrorFor.ts'
|
|
@@ -6,7 +7,7 @@ import { httpErrorFor } from './httpErrorFor.ts'
|
|
|
6
7
|
Decodes a Response into the natural body value based on Content-Type:
|
|
7
8
|
application/json (or `*\/+json`) → parsed JSON
|
|
8
9
|
text/* → string
|
|
9
|
-
204 No Content
|
|
10
|
+
204 No Content → undefined
|
|
10
11
|
everything else → Blob
|
|
11
12
|
|
|
12
13
|
Non-2xx responses throw HttpError so the happy path never has to check
|
|
@@ -39,11 +40,13 @@ export async function decodeResponse(response: Response): Promise<unknown> {
|
|
|
39
40
|
`[abide] response at ${response.url} is a stream (${contentType}) — use tail(fn.stream(args)) for a reactive view, or fn.stream(args) for per-call iteration, instead of awaiting the bare call or cache()`,
|
|
40
41
|
)
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
/* json/text go through the shared mapping warmValueFromSnapshot also uses, so a warm
|
|
44
|
+
seed reads byte-identically to this live read; binary (the only DEFER kind left,
|
|
45
|
+
streaming already threw above) blobs. */
|
|
46
|
+
const value = bodyValueForKind(
|
|
47
|
+
kind,
|
|
48
|
+
() => response.json(),
|
|
49
|
+
() => response.text(),
|
|
50
|
+
)
|
|
51
|
+
return value === DEFER ? response.blob() : value
|
|
49
52
|
}
|
|
@@ -8,12 +8,21 @@ const useColor = hasBun && Bun.enableANSIColors
|
|
|
8
8
|
const RESET = '\x1b[0m'
|
|
9
9
|
const DIM = '\x1b[2m'
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
/* The finite color set the log fragments paint with. Resolve each ANSI-256
|
|
12
|
+
escape once at module load — `Bun.color` was otherwise called per colored
|
|
13
|
+
fragment per line. Empty when colors are off so `paint` short-circuits. */
|
|
14
|
+
const COLOR_NAMES = ['red', 'yellow', 'cyan', 'green', 'blue', 'white'] as const
|
|
15
|
+
const COLOR_ESCAPES: Record<string, string> = useColor
|
|
16
|
+
? Object.fromEntries(COLOR_NAMES.map((name) => [name, Bun.color(name, 'ansi-256') ?? '']))
|
|
17
|
+
: {}
|
|
18
|
+
|
|
19
|
+
// Wraps `text` in a precomputed ANSI color escape; no-op when colors are disabled or unavailable (browser).
|
|
12
20
|
function paint(color: string, text: string): string {
|
|
13
|
-
|
|
21
|
+
const ansiEscape = COLOR_ESCAPES[color]
|
|
22
|
+
if (ansiEscape === undefined) {
|
|
14
23
|
return text
|
|
15
24
|
}
|
|
16
|
-
return `${
|
|
25
|
+
return `${ansiEscape}${text}${RESET}`
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
// Applies the ANSI dim attribute; no-op when colors are disabled.
|
|
@@ -66,12 +75,17 @@ function formatElapsed(ms: number): string {
|
|
|
66
75
|
return `+${ms.toFixed(2)}ms`
|
|
67
76
|
}
|
|
68
77
|
|
|
78
|
+
/* Left-pads a clock component to a fixed width. Module-scoped so it isn't
|
|
79
|
+
re-allocated per log line. */
|
|
80
|
+
function pad(value: number, width = 2): string {
|
|
81
|
+
return String(value).padStart(width, '0')
|
|
82
|
+
}
|
|
83
|
+
|
|
69
84
|
/* `14:23:01.072` — local wall-clock to the ms. Every record carries `ts`, so the
|
|
70
85
|
tsv line always leads with one; `+elapsedMs` still trails as the request-relative
|
|
71
86
|
timing — the two are different axes (when it happened vs how long it took). */
|
|
72
87
|
function formatClock(ts: number): string {
|
|
73
88
|
const date = new Date(ts)
|
|
74
|
-
const pad = (value: number, width = 2): string => String(value).padStart(width, '0')
|
|
75
89
|
return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}.${pad(date.getMilliseconds(), 3)}`
|
|
76
90
|
}
|
|
77
91
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createResolverSlot } from './createResolverSlot.ts'
|
|
2
|
+
import type { CacheStore } from './types/CacheStore.ts'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
Slot + setter for the process-level cache store resolver used by cache() entries
|
|
6
|
+
opting into `global: true`. The server entry registers a module-singleton store
|
|
7
|
+
outliving any one request; the client entry points it at its single tab store so
|
|
8
|
+
`global` is a no-op there. No fallback creator — when unset, globalCacheStore()
|
|
9
|
+
falls through to the active store rather than minting an isolated one.
|
|
10
|
+
globalCacheStoreSlot / setGlobalCacheStoreResolver re-export the slot and setter.
|
|
11
|
+
*/
|
|
12
|
+
export const globalCacheStoreResolver = createResolverSlot<CacheStore>()
|
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { globalCacheStoreResolver } from './globalCacheStoreResolver.ts'
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
|
-
Slot for the process-level cache store resolver
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
case globalCacheStore() falls back to the active (request/tab) store.
|
|
4
|
+
Slot for the process-level cache store resolver (see globalCacheStoreResolver).
|
|
5
|
+
Exposed so test helpers snapshot/poke `.resolver` directly. Unset means no global
|
|
6
|
+
store is registered, in which case globalCacheStore() falls back to the active
|
|
7
|
+
(request/tab) store.
|
|
9
8
|
*/
|
|
10
|
-
export const globalCacheStoreSlot
|
|
11
|
-
resolver: (() => CacheStore | undefined) | undefined
|
|
12
|
-
} = {
|
|
13
|
-
resolver: undefined,
|
|
14
|
-
}
|
|
9
|
+
export const globalCacheStoreSlot = globalCacheStoreResolver.slot
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { isReplayableMethod } from './isReplayableMethod.ts'
|
|
2
|
+
import type { CacheEntry } from './types/CacheEntry.ts'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
Whether a cache entry carries a wire request that can be replayed from a warm
|
|
6
|
+
snapshot — a replayable (GET-only) method, so the seed never re-fires a write or
|
|
7
|
+
loses a body. Producer entries (no request) and write methods fail it.
|
|
8
|
+
|
|
9
|
+
The request half of snapshot shippability, factored out so the two gates that
|
|
10
|
+
need it cannot drift: `snapshotShippable` composes it with `settled` for the
|
|
11
|
+
store-filter sites, and `snapshotEntryFromCache` calls it alone — its
|
|
12
|
+
streaming-drain caller hands it still-pending entries it then awaits, so it can't
|
|
13
|
+
require `settled` up front.
|
|
14
|
+
*/
|
|
15
|
+
export function hasReplayableRequest(entry: CacheEntry): boolean {
|
|
16
|
+
return entry.request !== undefined && isReplayableMethod(entry.request.method.toUpperCase())
|
|
17
|
+
}
|
package/src/lib/shared/html.ts
CHANGED
|
@@ -1,36 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
/* The registered brand for trusted raw markup. `Symbol.for` resolves through the
|
|
2
|
+
global registry, so this binding is the same symbol across module/bundle copies;
|
|
3
|
+
`html` (`abide/ui/html`) imports it to brand, `rawHtmlString` reads it back. */
|
|
4
|
+
export const RAW_HTML = Symbol.for('abide.rawHtml')
|
|
2
5
|
|
|
3
6
|
/* A value marked as trusted raw markup — a `{expr}` interpolation inserts it
|
|
4
7
|
verbatim (not escaped) on both sides. The brand is a registered Symbol, so it
|
|
5
|
-
survives across module/bundle copies.
|
|
8
|
+
survives across module/bundle copies. Branded by `html` (`abide/ui/html`); read
|
|
9
|
+
back by `rawHtmlString` here — the isomorphic reader both runtimes consume. */
|
|
6
10
|
export type RawHtml = { readonly [RAW_HTML]: string }
|
|
7
11
|
|
|
8
|
-
/*
|
|
9
|
-
Marks a string as trusted raw HTML so a `{expr}` interpolation inserts its nodes
|
|
10
|
-
instead of escaped text — the abide idiom for raw markup (no `{@html}` mustache).
|
|
11
|
-
Works two ways:
|
|
12
|
-
|
|
13
|
-
html(trustedString) // plain call — insert the string verbatim
|
|
14
|
-
html`<b>${name}</b>` // tagged — concatenate parts verbatim
|
|
15
|
-
|
|
16
|
-
Calling `html` is the explicit opt-in to raw insertion; plain `{value}` always
|
|
17
|
-
escapes. The tag does NOT auto-escape interpolations (it's raw by intent), so only
|
|
18
|
-
build markup from values you trust, or escape them yourself.
|
|
19
|
-
*/
|
|
20
|
-
// @documentation templating
|
|
21
|
-
export function html(strings: TemplateStringsArray | string, ...values: unknown[]): RawHtml {
|
|
22
|
-
if (typeof strings === 'string') {
|
|
23
|
-
return { [RAW_HTML]: strings }
|
|
24
|
-
}
|
|
25
|
-
let markup = strings[0] ?? ''
|
|
26
|
-
for (let index = 0; index < values.length; index += 1) {
|
|
27
|
-
markup += String(values[index]) + (strings[index + 1] ?? '')
|
|
28
|
-
}
|
|
29
|
-
return { [RAW_HTML]: markup }
|
|
30
|
-
}
|
|
31
|
-
|
|
32
12
|
/* The raw markup of a `html`-branded value, or undefined for anything else
|
|
33
13
|
(so text bindings can fast-path plain values and only branded ones go raw). */
|
|
14
|
+
// @documentation plumbing
|
|
34
15
|
export function rawHtmlString(value: unknown): string | undefined {
|
|
35
16
|
return value !== null && typeof value === 'object' && RAW_HTML in value
|
|
36
17
|
? (value as RawHtml)[RAW_HTML]
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { matchesDebugPattern } from './matchesDebugPattern.ts'
|
|
3
|
-
import { parseDebugPatterns } from './parseDebugPatterns.ts'
|
|
1
|
+
import { debugGate } from './debugGate.ts'
|
|
4
2
|
|
|
5
3
|
/*
|
|
6
4
|
Whether a DEBUG-gated channel is enabled, npm-debug conventions:
|
|
@@ -11,16 +9,11 @@ DEBUG="a,abide" → comma-separated list
|
|
|
11
9
|
DEBUG="abide:*,-abide:cache" → negation: exclusions win over inclusions
|
|
12
10
|
Always-on channels don't consult this — they check isDebugNegated only.
|
|
13
11
|
The default is guarded: this runs in the browser bundle, where `process`
|
|
14
|
-
doesn't exist.
|
|
12
|
+
doesn't exist. Decision routed through debugGate, memoized per DEBUG value.
|
|
15
13
|
*/
|
|
16
14
|
export function isDebugEnabled(
|
|
17
15
|
name: string,
|
|
18
16
|
env: string | undefined = typeof process === 'undefined' ? undefined : process.env.DEBUG,
|
|
19
17
|
): boolean {
|
|
20
|
-
|
|
21
|
-
return false
|
|
22
|
-
}
|
|
23
|
-
return parseDebugPatterns(env)
|
|
24
|
-
.filter((pattern) => !pattern.startsWith('-'))
|
|
25
|
-
.some((pattern) => matchesDebugPattern(name, pattern))
|
|
18
|
+
return debugGate(env).enabled(name)
|
|
26
19
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { parseDebugPatterns } from './parseDebugPatterns.ts'
|
|
1
|
+
import { debugGate } from './debugGate.ts'
|
|
3
2
|
|
|
4
3
|
/*
|
|
5
4
|
Whether a `-` pattern in DEBUG explicitly shuts a channel off — the off
|
|
@@ -7,13 +6,12 @@ switch for the always-on channels (the app's name, 'abide'): DEBUG="-abide"
|
|
|
7
6
|
silences framework lines including the per-request closing records,
|
|
8
7
|
DEBUG="-myapp" the app's own. Silencing a channel silences all its levels —
|
|
9
8
|
levels never gate, in either direction. The default is guarded: this runs in
|
|
10
|
-
the browser bundle, where `process` doesn't exist.
|
|
9
|
+
the browser bundle, where `process` doesn't exist. Decision routed through
|
|
10
|
+
debugGate, memoized per DEBUG value.
|
|
11
11
|
*/
|
|
12
12
|
export function isDebugNegated(
|
|
13
13
|
name: string,
|
|
14
14
|
env: string | undefined = typeof process === 'undefined' ? undefined : process.env.DEBUG,
|
|
15
15
|
): boolean {
|
|
16
|
-
return
|
|
17
|
-
.filter((pattern) => pattern.startsWith('-'))
|
|
18
|
-
.some((pattern) => matchesDebugPattern(name, pattern.slice(1)))
|
|
16
|
+
return debugGate(env).negated(name)
|
|
19
17
|
}
|
|
@@ -16,8 +16,8 @@ export function manifestModule(options: {
|
|
|
16
16
|
manifests whose contents are enumerated once at boot as an aligned table (see
|
|
17
17
|
logExposedSurfaces) — rpc/sockets/pages/layouts — which both avoids the
|
|
18
18
|
redundant list and the double-log of manifests (pages/layouts) loaded by both
|
|
19
|
-
the server and client bundles. prompts
|
|
20
|
-
|
|
19
|
+
the server and client bundles. prompts has no table, so it passes a label to
|
|
20
|
+
keep its build-time line.
|
|
21
21
|
*/
|
|
22
22
|
label?: string
|
|
23
23
|
}): { contents: string; loader: 'js' } {
|
package/src/lib/shared/online.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { requestScopeSlot } from './requestScopeSlot.ts'
|
|
|
3
3
|
|
|
4
4
|
/*
|
|
5
5
|
Reactive network-connectivity probe in the pending()/refreshing() family:
|
|
6
|
-
reading it from a
|
|
6
|
+
reading it from a scope().computed() / scope().effect() re-runs that scope when the browser's
|
|
7
7
|
online/offline events fire. navigator.onLine's *offline* signal is reliable
|
|
8
8
|
(a true network loss always reports); its online value can false-positive
|
|
9
9
|
behind captive portals — verified backend reachability is a separate,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Internal slot the browser outbox registry registers its prober into, so the
|
|
3
3
|
shared pending() probe can count parked durable-rpc writes without shared/
|
|
4
4
|
importing browser/. Mirrors tailProbeSlot. The prober reads the doc-backed
|
|
5
|
-
queue entries (reactive inside
|
|
5
|
+
queue entries (reactive inside scope().computed() / scope().effect()) and reports whether any
|
|
6
6
|
undelivered entry matches the selector: a durable rpc selector (carries a
|
|
7
7
|
`url`) narrows to its own queue, the bare form spans every registered queue,
|
|
8
8
|
optional args narrow to one parked call by structural compare. Outbox state is
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createResolverSlot } from './createResolverSlot.ts'
|
|
2
|
+
import type { PageSnapshot } from './types/PageSnapshot.ts'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
The active-page slot/resolver/reader bundle. The server entry installs an
|
|
6
|
+
ALS-backed resolver (request-scoped, so concurrent and streaming renders never
|
|
7
|
+
share state); the client entry a module-singleton one. With no resolver
|
|
8
|
+
registered, a single empty snapshot is created lazily so isolated tests work.
|
|
9
|
+
pageSlot / activePage re-export the slot and reader; setPageResolver the setter.
|
|
10
|
+
*/
|
|
11
|
+
export const pageResolver = createResolverSlot<PageSnapshot>(() => ({
|
|
12
|
+
route: '',
|
|
13
|
+
params: {},
|
|
14
|
+
url: new URL('http://localhost/'),
|
|
15
|
+
navigating: false,
|
|
16
|
+
}))
|
|
@@ -1,17 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { pageResolver } from './pageResolver.ts'
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
|
-
Internal slot the runtime entries register their page resolver into
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
module-singleton resolver. `fallback` is a single lazy snapshot used only
|
|
8
|
-
when no resolver is registered — keeps isolated tests working without forcing
|
|
9
|
-
them to spin up the runtime. Mirrors cacheStoreSlot.
|
|
4
|
+
Internal slot the runtime entries register their page resolver into (see
|
|
5
|
+
pageResolver). Exposed so test helpers snapshot/poke `.resolver` and
|
|
6
|
+
`.fallback` directly.
|
|
10
7
|
*/
|
|
11
|
-
export const pageSlot
|
|
12
|
-
resolver: (() => PageSnapshot | undefined) | undefined
|
|
13
|
-
fallback: PageSnapshot | undefined
|
|
14
|
-
} = {
|
|
15
|
-
resolver: undefined,
|
|
16
|
-
fallback: undefined,
|
|
17
|
-
}
|
|
8
|
+
export const pageSlot = pageResolver.slot
|
|
@@ -4,7 +4,7 @@ folder-based: every leaf is `page.abide` or `layout.abide`, and the URL
|
|
|
4
4
|
is the directory path. Pages mount at the directory path; layouts mount at
|
|
5
5
|
the directory prefix. Dynamic segments keep their `[name]` / `[...rest]`
|
|
6
6
|
shape — translation to Bun's `:name` / `*` happens at server registration
|
|
7
|
-
via toBunRoutePattern; consumers see the readable form in `
|
|
7
|
+
via toBunRoutePattern; consumers see the readable form in `page.route`.
|
|
8
8
|
*/
|
|
9
9
|
export function pageUrlForFile(relPath: string): string {
|
|
10
10
|
const segments = relPath.split('/')
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createResolverSlot } from './createResolverSlot.ts'
|
|
2
|
+
import type { RequestScopeInfo } from './types/RequestScopeInfo.ts'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
Slot + setter for the request-scope resolver. The server installs an ALS-backed
|
|
6
|
+
resolver (createServer, reading the RequestStore); the client a module-singleton
|
|
7
|
+
seeded from __SSR__ (startClient). No fallback creator — an unset resolver (or one
|
|
8
|
+
returning undefined outside any request) means "no scope": callers read
|
|
9
|
+
`.resolver?.()` and treat undefined as absent. requestScopeSlot /
|
|
10
|
+
setRequestScopeResolver re-export the slot and setter.
|
|
11
|
+
*/
|
|
12
|
+
export const requestScopeResolver = createResolverSlot<RequestScopeInfo>()
|
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { requestScopeResolver } from './requestScopeResolver.ts'
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
|
-
Internal slot the runtime entries register their request-scope resolver into
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
lines print without the context prefix. Mirrors pageSlot / cacheStoreSlot.
|
|
4
|
+
Internal slot the runtime entries register their request-scope resolver into (see
|
|
5
|
+
requestScopeResolver). Exposed so callers read `.resolver?.()` and test helpers
|
|
6
|
+
snapshot/poke `.resolver` directly. Undefined resolver — or one returning
|
|
7
|
+
undefined outside any request — means "no scope": trace() returns undefined and
|
|
8
|
+
log lines print without the context prefix.
|
|
10
9
|
*/
|
|
11
|
-
export const requestScopeSlot
|
|
12
|
-
resolver: (() => RequestScopeInfo | undefined) | undefined
|
|
13
|
-
} = {
|
|
14
|
-
resolver: undefined,
|
|
15
|
-
}
|
|
10
|
+
export const requestScopeSlot = requestScopeResolver.slot
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { contentBodyKind } from './contentBodyKind.ts'
|
|
2
|
+
import { contentTypeOf } from './contentTypeOf.ts'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
Compressible Content-Types — text and structured-text payloads. Binary or
|
|
6
|
+
already-compressed bodies (images, fonts, archives, zstd/gzip blobs) gain
|
|
7
|
+
nothing from a second pass and only burn CPU.
|
|
8
|
+
*/
|
|
9
|
+
const COMPRESSIBLE_TYPE =
|
|
10
|
+
/^(?:text\/|application\/(?:json|javascript|xml|[\w.-]+\+(?:json|xml))|image\/svg)/
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
The post-handler body class that decides a dynamic response's wire handling:
|
|
14
|
+
`streaming` (SSE / JSONL — drained frame-by-frame, never gzip-buffered, opted
|
|
15
|
+
out of the idle timeout), `compressible` (text/structured-text — gzipped when
|
|
16
|
+
the client accepts it), or `opaque` (binary/already-encoded — passed through).
|
|
17
|
+
|
|
18
|
+
One header read + classify per response, threaded through the dispatch pipeline
|
|
19
|
+
so `gzipResponse` and the idle-timeout opt-out don't each re-derive it from the
|
|
20
|
+
Content-Type (the S2 finding: the same body was classified 3-4×). Mirrors the
|
|
21
|
+
shared `contentBodyKind` streaming bucket so the wire path and the decode path
|
|
22
|
+
can't disagree on what's a stream.
|
|
23
|
+
*/
|
|
24
|
+
export type ResponseBodyKind = 'streaming' | 'compressible' | 'opaque'
|
|
25
|
+
|
|
26
|
+
export function responseBodyKind(response: Response): ResponseBodyKind {
|
|
27
|
+
const contentType = contentTypeOf(response.headers)
|
|
28
|
+
if (contentBodyKind(contentType) === 'streaming') {
|
|
29
|
+
return 'streaming'
|
|
30
|
+
}
|
|
31
|
+
if (COMPRESSIBLE_TYPE.test(contentType)) {
|
|
32
|
+
return 'compressible'
|
|
33
|
+
}
|
|
34
|
+
return 'opaque'
|
|
35
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { parseRouteSegments } from './parseRouteSegments.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
The TypeScript type literal for a route's path params — `{ "id": string }` for
|
|
5
|
+
`/post/[id]`, `Record<string, never>` for a param-less route. Walks the `[name]` /
|
|
6
|
+
`[...rest]` segments; catch-all segments map to `string` under their declared name
|
|
7
|
+
(the server's toBunRoutePattern renames Bun's `*` key back to that name when
|
|
8
|
+
dispatching, so the page sees `params.rest`, not `params['*']`). The single source of
|
|
9
|
+
truth shared by `writeRoutesDts` (page.params typing) and the `.abide` check shadow
|
|
10
|
+
(page `props()` typing).
|
|
11
|
+
*/
|
|
12
|
+
export function routeParamsShape(routeUrl: string): string {
|
|
13
|
+
const keys = parseRouteSegments(routeUrl)
|
|
14
|
+
.filter((segment) => segment.kind === 'param')
|
|
15
|
+
.map((segment) => segment.name)
|
|
16
|
+
if (keys.length === 0) {
|
|
17
|
+
return 'Record<string, never>'
|
|
18
|
+
}
|
|
19
|
+
return `{ ${keys.map((key) => `${JSON.stringify(key)}: string`).join('; ')} }`
|
|
20
|
+
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { baseResolver } from './baseResolver.ts'
|
|
2
2
|
|
|
3
3
|
// Registers the runtime's mount-base resolver. Called once per side at boot.
|
|
4
|
-
export
|
|
5
|
-
baseSlot.resolver = fn
|
|
6
|
-
}
|
|
4
|
+
export const setBaseResolver = baseResolver.set
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { CacheStore } from './types/CacheStore.ts'
|
|
1
|
+
import { cacheStoreResolver } from './cacheStoreResolver.ts'
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
3
|
+
// Registers the runtime's active-CacheStore resolver. Called once per side at boot.
|
|
4
|
+
export const setCacheStoreResolver = cacheStoreResolver.set
|