feat(strawberry-shake): persist Razor query components across prerender#1
feat(strawberry-shake): persist Razor query components across prerender#1cliedeman wants to merge 1 commit into
Conversation
Generated Blazor query components can now survive the server prerender to interactive boundary using PersistentComponentState, so the query is not re-executed on the client after a prerender (no double fetch, no loading flash). - OperationResultBuilder captures the raw transport "data" payload into ContextData (gated by the CapturePersistedData virtual) and can rebuild a result from a persisted payload via BuildFromPersistedData. - OperationExecutor.Watch gains a persisted-state overload that seeds the store from the payload and serves the result from the cache (CacheFirst), skipping the network. StorelessOperationExecutor throws (store required). - New UsePersistentQuery<TResult> Razor base awaits the first result on the cold path, persists it during prerender, rehydrates it on interactive init, and re-subscribes only when the operation arguments change. - RazorQueryGenerator and OperationServiceGenerator emit the wiring behind a new opt-in RazorPersistedState setting (queries and store-backed clients only). Output is unchanged when the setting is off. Tests: Core.Tests cover capture, gating, BuildFromPersistedData round-trip, rehydrate-without-network, and the storeless guard; a new Razor codegen snapshot covers the generated component and operation members. Relates to ChilliCream#7209. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Closing as the wrong direction, superseded by #2. The declarative The only capability this branch had that #2 doesn't is reactive store-subscription updates after hydration (the |
Summary
Generated Blazor query components can now survive the server prerender → interactive boundary using
PersistentComponentState, so the query is not re-executed on the client after a prerender. This removes the double fetch and the loading-state flash forInteractiveServer/InteractiveAuto/ prerendered WASM.Opt-in; queries and store-backed clients only. Output is unchanged when the feature is off.
Relates to ChilliCream#7209.
How it works
OperationResultBuilder.Buildclones the raw transportdataelement intoIOperationResult.ContextData[WellKnownContextData.PersistedData], gated by a newCapturePersistedDatavirtual. The generatedJsonResultBuilderoverrides it totrueonly when the feature is on (generated output is byte-identical when off).UsePersistentQuery<TResult>Razor base awaits the first result on the cold path (so prerendered HTML contains data), thenRegisterOnPersistingsaves the payload underOperation.GetPersistenceKey(args)(a stableOperationRequest.GetHash()).TryTakeFromJson→Operation.Watch(args, persistedState)→OperationExecutor.Watch(request, persistedState, …)rebuilds the typed result viaBuildFromPersistedData(seedingIEntityStore), seeds the operation store, and forcesCacheFirst→ served from cache, no network call.Opt-in
.graphqlrc.json:{ "extensions": { "strawberryShake": { "razorComponents": true, "razorPersistedState": true } } }Notable decisions
CacheFirston the persisted path (the default strategy isNetworkOnly, which would otherwise refetch).OnParametersSetre-subscribes only when the operation arguments change — supports dynamic parameters and avoids re-subscribing on every re-render.BuildFromPersistedDatais a throwing default-interface-method onIOperationResultBuilder, so non-JSON transport builders are unaffected.NoStore(the storeless executor throws; generators skip the wiring). Subscriptions and mutations are untouched.Tests
Core.Tests: capture intoContextData, gating-off,BuildFromPersistedDataround-trip fidelity, rehydrate-without-network (asserts the connection is never called), and the storeless guard.RazorGeneratorTests.Query_With_Persisted_State(the snapshot test compiles the generated code against the runtime).StrawberryShake.ClientandStrawberryShake.CodeGenerationsolutions build clean across net8.0/net9.0/net10.0/net11.0.Known limitations
RegisterOnRestoring/AllowUpdates) are not used.CapturePersistedDataoverride (harmless; nothing reads it for mutations). Gating to query result builders only is not cleanly available fromResultBuilderDescriptor.🤖 Generated with Claude Code