@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
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
import { REACTIVE_CONTEXT } from './REACTIVE_CONTEXT.ts'
|
|
2
|
+
import type { ReactiveNode } from './types/ReactiveNode.ts'
|
|
2
3
|
import { updateIfNecessary } from './updateIfNecessary.ts'
|
|
3
4
|
|
|
4
5
|
/*
|
|
5
6
|
Drains queued effects synchronously. Each is queued when it first leaves CLEAN, but
|
|
6
7
|
runs only if `updateIfNecessary` finds a dependency that truly changed — a CHECK
|
|
7
8
|
effect whose deps all memoised back to equal values settles to CLEAN without
|
|
8
|
-
running its body.
|
|
9
|
-
|
|
10
|
-
mid-iteration; loops until the
|
|
9
|
+
running its body. Double-buffers the queue each pass — swaps in a fresh array and
|
|
10
|
+
drains the captured one — so an effect that dirties further effects re-queues them
|
|
11
|
+
for the next pass rather than mutating the array mid-iteration; loops until the
|
|
12
|
+
graph settles. The swap reuses the drained array as the next spare, so a steady
|
|
13
|
+
flush allocates nothing.
|
|
11
14
|
*/
|
|
12
15
|
export function flushEffects(): void {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
/* Empty-queue fast path allocates nothing — only the length check, matching the
|
|
17
|
+
old `Set.size` guard. Hot: a write with no subscribed effect (e.g. a reconcile's
|
|
18
|
+
index-cell write nobody reads) still calls here at batch-exit, thousands of times
|
|
19
|
+
per list op; the spare array below must not be born on that path. */
|
|
20
|
+
if (REACTIVE_CONTEXT.pendingEffects.length === 0) {
|
|
21
|
+
return
|
|
19
22
|
}
|
|
23
|
+
let spare: ReactiveNode[] = []
|
|
24
|
+
do {
|
|
25
|
+
const batch = REACTIVE_CONTEXT.pendingEffects
|
|
26
|
+
REACTIVE_CONTEXT.pendingEffects = spare
|
|
27
|
+
for (let index = 0; index < batch.length; index += 1) {
|
|
28
|
+
updateIfNecessary(batch[index] as ReactiveNode)
|
|
29
|
+
}
|
|
30
|
+
batch.length = 0
|
|
31
|
+
spare = batch
|
|
32
|
+
} while (REACTIVE_CONTEXT.pendingEffects.length > 0)
|
|
20
33
|
}
|
|
@@ -2,7 +2,7 @@ import type { AbideHistoryState } from './types/AbideHistoryState.ts'
|
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
4
|
Per-history-entry scroll buckets for manual scroll restoration. The browser's own
|
|
5
|
-
restoration is disabled (`history.scrollRestoration = 'manual'`
|
|
5
|
+
restoration is disabled (`history.scrollRestoration = 'manual'` after the first build/hydration)
|
|
6
6
|
because the router tears the page down and rebuilds it AFTER the browser would have
|
|
7
7
|
restored scroll — so the offset is lost against a node that no longer exists. Instead
|
|
8
8
|
each history entry carries a monotonic id (stamped into `history.state.abideEntry` by
|
|
@@ -15,8 +15,10 @@ observer is reading the same source in the same order as before — advance the
|
|
|
15
15
|
cursor and reuse the edge, allocating nothing. Otherwise splice a fresh edge in at
|
|
16
16
|
the cursor; `runNode` trims whatever stale edges trail the cursor when compute ends.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
On re-runs, an edge whose `dep` matches the next slot in the established list is
|
|
19
|
+
reused in-place (cursor advances, no allocation). New edges are still appended for
|
|
20
|
+
reads that extend the list or find a mismatched slot — including a second consecutive
|
|
21
|
+
read of the same source when no matching edge yet follows the cursor.
|
|
20
22
|
*/
|
|
21
23
|
export function track(dep: ReactiveNode): void {
|
|
22
24
|
const sub = REACTIVE_CONTEXT.observer
|
|
@@ -15,27 +15,41 @@ reaches). `nextSub` is read before recursing so the walk holds no edge a deeper
|
|
|
15
15
|
pass could detach.
|
|
16
16
|
*/
|
|
17
17
|
function mark(node: ReactiveNode, status: number): void {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (
|
|
25
|
-
|
|
18
|
+
/* Tail-iterative down the subscriber cone: a node with a single subscriber (a
|
|
19
|
+
linear computed chain) advances by reassigning `node` instead of recursing, so a
|
|
20
|
+
deep chain costs a loop, not a 500-frame call stack. Only a true fan-out (>1
|
|
21
|
+
subscriber) recurses, and only for the extra branches. */
|
|
22
|
+
let nextStatus = status
|
|
23
|
+
for (;;) {
|
|
24
|
+
if (node.status >= nextStatus) {
|
|
25
|
+
return
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
const wasClean = node.status === NODE_STATE.CLEAN
|
|
28
|
+
node.status = nextStatus
|
|
29
|
+
if (node.isEffect) {
|
|
30
|
+
if (wasClean) {
|
|
31
|
+
REACTIVE_CONTEXT.pendingEffects.push(node)
|
|
32
|
+
}
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
/* A computed propagates CHECK to its subscribers only on its first move out of
|
|
36
|
+
CLEAN — they are already CHECK on any later upgrade, so re-walking is wasted. */
|
|
37
|
+
if (!wasClean) {
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
const head = node.subsHead
|
|
41
|
+
if (head === undefined) {
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
/* Recurse into every branch but the last; continue the loop on the last, so the
|
|
45
|
+
common single-subscriber chain never recurses. */
|
|
46
|
+
let link = head
|
|
47
|
+
while (link.nextSub !== undefined) {
|
|
48
|
+
mark(link.sub, NODE_STATE.CHECK)
|
|
49
|
+
link = link.nextSub
|
|
50
|
+
}
|
|
51
|
+
node = link.sub
|
|
52
|
+
nextStatus = NODE_STATE.CHECK
|
|
39
53
|
}
|
|
40
54
|
}
|
|
41
55
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/* A routable page/layout. Callable to mount directly into a host (the direct-mount
|
|
2
2
|
API), but the router instead uses `build` — the bare client build — to fill the
|
|
3
3
|
layer into its outlet boundary as a marker range (no `<abide-outlet>` wrapper; see
|
|
4
|
-
`fillBoundary`/`outlet`). `hydratable` (
|
|
5
|
-
tells the router whether the first paint adopts the SSR DOM in place. */
|
|
4
|
+
`fillBoundary`/`outlet`). `hydratable` (always true for compiled components; undefined on manually-constructed
|
|
5
|
+
Route objects) tells the router whether the first paint adopts the SSR DOM in place. */
|
|
6
6
|
export type Route = ((host: Element, props?: unknown) => (() => void) | undefined) & {
|
|
7
7
|
build: (host: Node, props?: unknown) => void
|
|
8
8
|
hydratable?: boolean
|
|
@@ -10,8 +10,9 @@ page/route registries carry — abide-ui's compiled-component shape.
|
|
|
10
10
|
export type UiComponent = ((host: Element, props?: UiProps) => () => void) & {
|
|
11
11
|
/* `ctx` is the request-local block-id counter, threaded so a child's ids share the
|
|
12
12
|
page's depth-first numbering; omitted at the top level (a fresh counter defaults
|
|
13
|
-
in). Returns a Promise when the component
|
|
14
|
-
… then}` block
|
|
13
|
+
in). Returns a Promise when the component contains an inline `await` — a blocking
|
|
14
|
+
`{#await … then}` block, a child-component render, a `<slot>` read, or a
|
|
15
|
+
top-level `await` in the author script; otherwise renders synchronously. Callers `await`
|
|
15
16
|
it either way (awaiting a sync value just returns it). */
|
|
16
17
|
render: (props?: UiProps, ctx?: RenderContext) => SsrRender | Promise<SsrRender>
|
|
17
18
|
hydrate?: (host: Element, props?: UiProps) => () => void
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
/*
|
|
2
|
-
What a component is invoked with.
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
it is handed.
|
|
2
|
+
What a component is invoked with. A top-level page/layout is called by the router
|
|
3
|
+
(client) and `renderChain` (SSR) with its route params as reactive thunks, shaped
|
|
4
|
+
identically to the thunk map `mountChild` passes a nested child — so `props()` reads
|
|
5
|
+
(`$props[name]?.()`) work uniformly on both. `$children` is the slot builder a parent
|
|
6
|
+
component passes (carrying the component's `{children()}` content), or `CHILD_PRESENT`
|
|
7
|
+
the router/SSR set on a layout that has a child layer below it.
|
|
9
8
|
*/
|
|
10
|
-
export type UiProps =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
$children?: (host: Element) => void
|
|
14
|
-
})
|
|
9
|
+
export type UiProps = Record<string, (() => unknown) | ((host: Element) => void)> & {
|
|
10
|
+
$children?: (host: Element) => void
|
|
11
|
+
}
|
|
@@ -17,13 +17,25 @@ export function walkPath(tree: unknown, path: string): PathWalk {
|
|
|
17
17
|
if (path === '') {
|
|
18
18
|
return { exists: tree !== undefined, value: tree }
|
|
19
19
|
}
|
|
20
|
+
/* A path with no `~` carries no escape in any segment, so the per-segment
|
|
21
|
+
unescape is skipped wholesale — the case for every plain-identifier path. */
|
|
22
|
+
const escaped = path.includes('~')
|
|
20
23
|
let current: unknown = tree
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
/* Scan `/`-separated segments by index, no `split` array allocated — this runs
|
|
25
|
+
once per candidate on the structural descend scan, the hot path's hot loop. */
|
|
26
|
+
let start = 0
|
|
27
|
+
for (;;) {
|
|
28
|
+
const slash = path.indexOf('/', start)
|
|
29
|
+
const end = slash === -1 ? path.length : slash
|
|
30
|
+
const raw = path.slice(start, end)
|
|
31
|
+
const segment = escaped ? unescapeKey(raw) : raw
|
|
23
32
|
if (current === null || typeof current !== 'object' || !(segment in current)) {
|
|
24
33
|
return { exists: false, value: undefined }
|
|
25
34
|
}
|
|
26
35
|
current = (current as Record<string, unknown>)[segment]
|
|
36
|
+
if (slash === -1) {
|
|
37
|
+
return { exists: true, value: current }
|
|
38
|
+
}
|
|
39
|
+
start = slash + 1
|
|
27
40
|
}
|
|
28
|
-
return { exists: true, value: current }
|
|
29
41
|
}
|
|
@@ -1,26 +1,41 @@
|
|
|
1
1
|
import { createCacheStore } from '../shared/createCacheStore.ts'
|
|
2
|
+
import { healthSeedSlot } from '../shared/healthSeedSlot.ts'
|
|
3
|
+
import { rpcTimeoutSlot } from '../shared/rpcTimeoutSlot.ts'
|
|
4
|
+
import { setAppName } from '../shared/setAppName.ts'
|
|
2
5
|
import { setBaseResolver } from '../shared/setBaseResolver.ts'
|
|
3
6
|
import { setCacheStoreResolver } from '../shared/setCacheStoreResolver.ts'
|
|
4
7
|
import { setGlobalCacheStoreResolver } from '../shared/setGlobalCacheStoreResolver.ts'
|
|
5
8
|
import { setPageResolver } from '../shared/setPageResolver.ts'
|
|
6
9
|
import type { CacheSnapshotEntry } from '../shared/types/CacheSnapshotEntry.ts'
|
|
7
10
|
import type { StreamedResolution } from '../shared/types/StreamedResolution.ts'
|
|
8
|
-
import { installHotBridge } from './installHotBridge.ts'
|
|
9
|
-
import { installInspectorBridge } from './installInspectorBridge.ts'
|
|
10
11
|
import { probeNavigation } from './probeNavigation.ts'
|
|
12
|
+
|
|
13
|
+
/* Build-time flag the production client defines false (see build.ts `define`) so the dev-only
|
|
14
|
+
hot bridge — and the entire DOM runtime it statically pulls in for re-builds — is dead-code-
|
|
15
|
+
eliminated, not even emitted as a chunk. Dev defines it true; the test preload sets it on
|
|
16
|
+
globalThis so the bare reference resolves there. */
|
|
17
|
+
declare const __ABIDE_DEV__: boolean
|
|
18
|
+
|
|
11
19
|
import { router } from './router.ts'
|
|
12
20
|
import { clientPage } from './runtime/clientPage.ts'
|
|
13
21
|
import type { RouteLoader } from './runtime/types/RouteLoader.ts'
|
|
14
22
|
import { seedResolved } from './seedResolved.ts'
|
|
15
23
|
|
|
16
24
|
/* The server's __SSR__ payload this entry consumes. */
|
|
17
|
-
type SsrPayload = {
|
|
25
|
+
type SsrPayload = {
|
|
26
|
+
cache?: CacheSnapshotEntry[]
|
|
27
|
+
base?: string
|
|
28
|
+
app?: string
|
|
29
|
+
health?: Record<string, unknown>
|
|
30
|
+
clientTimeout?: number
|
|
31
|
+
}
|
|
18
32
|
|
|
19
33
|
/*
|
|
20
34
|
The official abide-ui client entry. Reads the server's `window.__SSR__` payload,
|
|
21
35
|
seeds a tab-scoped cache store from the inline snapshot (so a warm `cache()` read
|
|
22
|
-
resolves synchronously and the matching
|
|
23
|
-
no re-fetch), installs the mount base,
|
|
36
|
+
resolves synchronously and the matching `{#await}` adopts the SSR DOM with
|
|
37
|
+
no re-fetch), installs the mount base, seeds the per-page __SSR__ stamps (app
|
|
38
|
+
name, health, RPC timeout), and starts the router — which imports the
|
|
24
39
|
current route's chunk, adopts the server-rendered `#app`, then drives SPA
|
|
25
40
|
navigation — importing each further page's chunk on first visit and probing the
|
|
26
41
|
destination through the server's app.handle so auth/redirect gating still applies.
|
|
@@ -37,19 +52,31 @@ export function startClient(
|
|
|
37
52
|
if (target === null) {
|
|
38
53
|
throw new Error('[abide] startClient: missing #app target')
|
|
39
54
|
}
|
|
40
|
-
/* Dev only: the live-reload script sets `__abideDev` before this module runs,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
55
|
+
/* Dev only: the live-reload script sets `__abideDev` before this module runs, so the
|
|
56
|
+
runtime bridge is in place before any component mounts and records its instances
|
|
57
|
+
(mountChild) for hot replacement. Lazy-imported and `__ABIDE_DEV__`-gated so the
|
|
58
|
+
bridge (and the DOM runtime it drags in) is fully dead-code-eliminated in production
|
|
59
|
+
rather than shipped behind a runtime flag the minifier can't prove false. */
|
|
60
|
+
if (__ABIDE_DEV__ && (globalThis as { __abideDev?: boolean }).__abideDev) {
|
|
61
|
+
import('./installHotBridge.ts').then((module) => module.installHotBridge())
|
|
45
62
|
}
|
|
46
|
-
/* Inspector only: the server injects `__abideInspect` when ABIDE_ENABLE_INSPECTOR
|
|
47
|
-
|
|
63
|
+
/* Inspector only: the server injects `__abideInspect` when ABIDE_ENABLE_INSPECTOR is on,
|
|
64
|
+
so the scope/router bridge arms before the router builds any scope. Inspector can be
|
|
65
|
+
enabled in production, so this stays a lazy chunk (not `__ABIDE_DEV__`-gated) — emitted
|
|
66
|
+
but fetched only when the flag is set, never weighing down the default client load. */
|
|
48
67
|
if ((globalThis as { __abideInspect?: boolean }).__abideInspect) {
|
|
49
|
-
installInspectorBridge()
|
|
68
|
+
import('./installInspectorBridge.ts').then((module) => module.installInspectorBridge())
|
|
50
69
|
}
|
|
51
70
|
const ssr = (globalThis as { __SSR__?: SsrPayload }).__SSR__ ?? {}
|
|
52
71
|
setBaseResolver(() => ssr.base ?? '')
|
|
72
|
+
/* Seed the per-page __SSR__ stamps into their shared slots before mount: the app
|
|
73
|
+
name (default log channel), the health payload (so health()'s first probe is warm),
|
|
74
|
+
and the env-configured RPC timeout (ABIDE_CLIENT_TIMEOUT, shipped per request).
|
|
75
|
+
Without this the browser falls back to channel 'app', a cold first health probe,
|
|
76
|
+
and unbounded RPC fetches. */
|
|
77
|
+
setAppName(ssr.app)
|
|
78
|
+
healthSeedSlot.payload = ssr.health
|
|
79
|
+
rpcTimeoutSlot.ms = ssr.clientTimeout
|
|
53
80
|
/* The `page` proxy reads route/params/url off the router-updated snapshot. */
|
|
54
81
|
setPageResolver(() => clientPage.value)
|
|
55
82
|
|
|
@@ -61,7 +88,7 @@ export function startClient(
|
|
|
61
88
|
(inline — reads settled at render-return, in __SSR__) and `__abideResumeCache` (pending
|
|
62
89
|
{#await} reads whose `__abideResolve(...)` chunks the stream pushed during parse, before
|
|
63
90
|
this deferred bundle ran). A warm entry lets a `cache()` read resolve synchronously so
|
|
64
|
-
|
|
91
|
+
`{#await}` adopts without a refetch; a miss marker re-fetches live. */
|
|
65
92
|
const streamed =
|
|
66
93
|
(globalThis as { __abideResumeCache?: StreamedResolution[] }).__abideResumeCache ?? []
|
|
67
94
|
for (const resolution of [...(ssr.cache ?? []), ...streamed]) {
|
package/src/lib/ui/state.ts
CHANGED
|
@@ -7,9 +7,9 @@ import { writeNode } from './runtime/writeNode.ts'
|
|
|
7
7
|
A writable reactive cell — abide's from-scratch reactive primitive, with no
|
|
8
8
|
compiler sigil and no external reactivity-library import. `.value` is a
|
|
9
9
|
plain getter/setter over a signal node, so a read/write shows up as exactly that
|
|
10
|
-
in a stack trace. The compiler
|
|
11
|
-
|
|
12
|
-
needs no magic.
|
|
10
|
+
in a stack trace. The compiler enforces scope attachment (bare `state()` is a compile
|
|
11
|
+
error — use `scope().state()`), desugars plain `state(initial)` to a serializable `model`
|
|
12
|
+
doc slot, and keeps `state(initial, transform)` as a `.value` cell; the runtime needs no magic.
|
|
13
13
|
|
|
14
14
|
`transform` is an optional coercion gate on the write path: every `.value =`
|
|
15
15
|
runs it and stores what it returns, with `previous` for clamp-relative writes or
|
|
@@ -17,9 +17,9 @@ methods so a scope is a passable value: `<Child parentScope={scope} />`.
|
|
|
17
17
|
Capabilities route where the scope's changes go: `record()` to an undo journal,
|
|
18
18
|
`persist()` to durable storage, `broadcast()` to peers — declared once, then
|
|
19
19
|
`undo`/`redo` act on a recorded scope. `id` is the scope's identity for the
|
|
20
|
-
boundary-crossing capabilities — `persist()` defaults its key to it. `scope()` is
|
|
21
|
-
|
|
22
|
-
`history`/`persist`/`sync` helpers it composes are internal
|
|
20
|
+
boundary-crossing capabilities — `persist()` defaults its key to it. `scope()` is the only user-facing public entry; `effect` is also exported
|
|
21
|
+
but only for generated binding code and the SSR strip (tagged plumbing). The
|
|
22
|
+
`history`/`persist`/`sync` helpers it composes are internal.
|
|
23
23
|
*/
|
|
24
24
|
export type Scope = {
|
|
25
25
|
readonly id: string
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { BunPlugin } from 'bun'
|
|
2
2
|
import { abideResolverPlugin } from './abideResolverPlugin.ts'
|
|
3
3
|
import { abideUiPlugin } from './lib/ui/compile/abideUiPlugin.ts'
|
|
4
|
+
import { zodCjsPlugin } from './zodCjsPlugin.ts'
|
|
4
5
|
|
|
5
6
|
/*
|
|
6
|
-
The server-target Bun.build plugin
|
|
7
|
-
the abide-ui `.abide` loader (so SSR `render()` resolves)
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
The server-target Bun.build plugin set shared by compile / buildCli / bundleApp:
|
|
8
|
+
the abide-ui `.abide` loader (so SSR `render()` resolves), abide's virtual-module
|
|
9
|
+
resolver, and the zod→CommonJS shim that works around bun's broken ESM-cycle
|
|
10
|
+
bundling (see zodCjsPlugin). `embedAssets` flips on the gzip asset embed used by
|
|
11
|
+
the standalone server binary; the CLI + launcher builds leave it off.
|
|
10
12
|
*/
|
|
11
13
|
export function serverBuildPlugins({
|
|
12
14
|
cwd,
|
|
@@ -15,5 +17,9 @@ export function serverBuildPlugins({
|
|
|
15
17
|
cwd: string
|
|
16
18
|
embedAssets?: boolean
|
|
17
19
|
}): BunPlugin[] {
|
|
18
|
-
return [
|
|
20
|
+
return [
|
|
21
|
+
zodCjsPlugin(cwd),
|
|
22
|
+
abideUiPlugin,
|
|
23
|
+
abideResolverPlugin({ cwd, embedAssets, target: 'server' }),
|
|
24
|
+
]
|
|
19
25
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { dirname } from 'node:path'
|
|
3
|
+
import type { BunPlugin } from 'bun'
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
Routes every `zod` import in a server bundle to zod's CommonJS build.
|
|
7
|
+
|
|
8
|
+
zod v4's ESM is reached through two subpaths (`zod` and `zod/v4/core`) whose
|
|
9
|
+
modules form an initialization cycle. Bun's bundler emits a broken init order
|
|
10
|
+
for that cycle (oven-sh/bun#31586, closed as not-planned), so a bundled binary
|
|
11
|
+
crashes (`<helper> is not defined`) or — for a lazily imported module — silently
|
|
12
|
+
fails to evaluate, which drops every rpc whose module imports zod from the
|
|
13
|
+
registry. It only bites bundled output; `abide dev` runs unbundled and is fine.
|
|
14
|
+
|
|
15
|
+
zod ships a parallel CommonJS build (`index.cjs`, `v4/core/index.cjs`, …), and
|
|
16
|
+
CommonJS resolves circular requires through a mutating `module.exports` with no
|
|
17
|
+
temporal-dead-zone, so the cycle initializes correctly. Resolving each zod
|
|
18
|
+
specifier to its `.cjs` sibling makes bun bundle zod as CommonJS and sidesteps
|
|
19
|
+
the bug while keeping zod v4. Server-only: the client bundle strips zod from rpc
|
|
20
|
+
modules (they become remote proxies), and dev never bundles.
|
|
21
|
+
*/
|
|
22
|
+
export function zodCjsPlugin(cwd: string): BunPlugin {
|
|
23
|
+
/*
|
|
24
|
+
The (specifier, importer-dir) → cjs path mapping is deterministic per build —
|
|
25
|
+
zod's location doesn't move mid-build, and the filter fires for every zod
|
|
26
|
+
import in every module. Memoise on a `${path}\0${from}` key (mirrors the
|
|
27
|
+
resolver's resolveExtension cache) so the fs round-trips (resolveSync +
|
|
28
|
+
existsSync) run once per unique pair instead of once per import.
|
|
29
|
+
*/
|
|
30
|
+
const cjsPathCache = new Map<string, string>()
|
|
31
|
+
return {
|
|
32
|
+
name: 'abide-zod-cjs',
|
|
33
|
+
setup(build) {
|
|
34
|
+
build.onResolve({ filter: /^zod($|\/)/ }, (args) => {
|
|
35
|
+
/* Resolve from the importer (or cwd for the entry) to zod's ESM
|
|
36
|
+
target, then swap to its `.cjs` sibling when one exists. */
|
|
37
|
+
const from = args.importer ? dirname(args.importer) : cwd
|
|
38
|
+
const key = `${args.path}\0${from}`
|
|
39
|
+
const cached = cjsPathCache.get(key)
|
|
40
|
+
if (cached !== undefined) {
|
|
41
|
+
return { path: cached }
|
|
42
|
+
}
|
|
43
|
+
const resolved = Bun.resolveSync(args.path, from)
|
|
44
|
+
const cjs = resolved.replace(/\.js$/, '.cjs')
|
|
45
|
+
const path = existsSync(cjs) ? cjs : resolved
|
|
46
|
+
cjsPathCache.set(key, path)
|
|
47
|
+
return { path }
|
|
48
|
+
})
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"auto_install_extensions": { "abide": true },
|
|
3
|
-
"semantic_tokens": "combined"
|
|
3
|
+
"semantic_tokens": "combined",
|
|
4
|
+
"global_lsp_settings": {
|
|
5
|
+
"semantic_token_rules": [
|
|
6
|
+
{ "token_type": "tag", "style": ["tag"] },
|
|
7
|
+
{ "token_type": "attribute", "style": ["attribute"] }
|
|
8
|
+
]
|
|
9
|
+
}
|
|
4
10
|
}
|
package/template/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
/*
|
|
3
3
|
Root layout. A layout.abide wraps every page at or below its folder — here
|
|
4
|
-
src/ui/pages/, so it wraps the whole app. The page
|
|
5
|
-
the resolver finds it by filename (no per-page import), and the client router
|
|
4
|
+
src/ui/pages/, so it wraps the whole app. The page renders where this layout
|
|
5
|
+
calls {children()}; the resolver finds it by filename (no per-page import), and the client router
|
|
6
6
|
keeps it mounted across navigation, so this chrome never re-mounts. A nested
|
|
7
7
|
layout.abide (in a subfolder) wraps inside this one. Runs on the server during
|
|
8
8
|
SSR and on the client after hydration.
|
|
@@ -16,6 +16,4 @@ import '../app.css'
|
|
|
16
16
|
<a href="/about">About</a>
|
|
17
17
|
</nav>
|
|
18
18
|
</header>
|
|
19
|
-
<main>
|
|
20
|
-
<slot></slot>
|
|
21
|
-
</main>
|
|
19
|
+
<main>{children()}</main>
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
One DEBUG pattern against one channel name, npm-debug conventions:
|
|
3
|
-
`*` matches everything, `abide:*` matches 'abide' and every 'abide:…'
|
|
4
|
-
sub-channel, anything else matches exactly. Negation (`-` prefix) is the
|
|
5
|
-
caller's concern — patterns arrive here already stripped.
|
|
6
|
-
*/
|
|
7
|
-
export function matchesDebugPattern(name: string, pattern: string): boolean {
|
|
8
|
-
if (pattern === '*') {
|
|
9
|
-
return true
|
|
10
|
-
}
|
|
11
|
-
if (pattern.endsWith(':*')) {
|
|
12
|
-
const prefix = pattern.slice(0, -2)
|
|
13
|
-
return name === prefix || name.startsWith(`${prefix}:`)
|
|
14
|
-
}
|
|
15
|
-
return pattern === name
|
|
16
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
DEBUG's comma list, parsed once per distinct string — the gate runs on every
|
|
3
|
-
log emission, and the env never changes server-side. A single-slot memo
|
|
4
|
-
suffices: the browser's abide-debug localStorage toggle produces a new string
|
|
5
|
-
that misses the memo and reparses.
|
|
6
|
-
*/
|
|
7
|
-
let lastEnv: string | undefined
|
|
8
|
-
let lastPatterns: string[] = []
|
|
9
|
-
|
|
10
|
-
export function parseDebugPatterns(env: string | undefined): string[] {
|
|
11
|
-
if (env !== lastEnv) {
|
|
12
|
-
lastEnv = env
|
|
13
|
-
lastPatterns = env
|
|
14
|
-
? env
|
|
15
|
-
.split(',')
|
|
16
|
-
.map((raw) => raw.trim())
|
|
17
|
-
.filter((pattern) => pattern !== '')
|
|
18
|
-
: []
|
|
19
|
-
}
|
|
20
|
-
return lastPatterns
|
|
21
|
-
}
|