docs(devframe): rpc/ folder + WeakMap context pattern#25
Merged
Conversation
Module-level RPC defs registered into multiple contexts in the same process (multi-server tests, hot-reload teardown/replay) previously shared a handler closed over the first context's state. Cache by context object so each context gets its own setup result.
Skill and rpc guide now describe the one-file-per-RPC layout under src/rpc/ and the WeakMap-based src/context.ts for sharing setup-time state across files. All three examples adopt the pattern, with streaming-chat demonstrating the WeakMap context for its shared channel + history.
✅ Deploy Preview for devfra ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Pull request overview
This PR documents and codifies a recommended RPC project layout (src/rpc/ + optional src/context.ts WeakMap pattern) for non-trivial devframes, updates examples to follow it, and fixes a framework-level caching bug so module-level RPC definitions can safely be registered into multiple node contexts within the same process.
Changes:
- Add/clarify documentation for the
src/rpc/per-function layout, plussrc/context.tsWeakMap context-sharing pattern. - Refactor the
streaming-chat,next-runtime-snapshot, andfiles-inspectorexamples to use the new layout and type-safe client registry (removingrpc.call(... as any)casts). - Change RPC setup-result caching from a single global slot to a per-context WeakMap cache (
getRpcResolvedSetupResult+ public type snapshots).
Reviewed changes
Copilot reviewed 27 out of 28 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/snapshots/tsnapi/devframe/recipes/open-helpers.snapshot.d.ts | Updates public API snapshot to reflect new per-context setup caching fields. |
| skills/devframe/SKILL.md | Documents recommended src/rpc/ layout and WeakMap context-sharing pattern with examples. |
| docs/guide/rpc.md | Adds a concise recommendation for per-RPC files + serverFunctions barrel + WeakMap context. |
| packages/devframe/src/rpc/types.ts | Replaces __resolved with __cache (WeakMap) + __promise fallback in RPC definition types. |
| packages/devframe/src/rpc/handler.ts | Implements per-context setup-result caching using WeakMap keyed by context object. |
| examples/streaming-chat/src/devframe.ts | Registers RPCs via serverFunctions and uses setStreamingChatContext for shared setup state. |
| examples/streaming-chat/src/context.ts | Introduces WeakMap-backed context store for sharing channel/history across RPC modules. |
| examples/streaming-chat/src/rpc/* | Splits RPCs into per-file modules and barrels them in src/rpc/index.ts. |
| examples/streaming-chat/src/types.ts | Extracts shared-state typing into a dedicated types module. |
| examples/streaming-chat/src/constants.ts | Extracts constants used across server/client/types. |
| examples/streaming-chat/src/client/app.tsx | Removes unsafe as any casts by relying on the typed registry + shared constants/types. |
| examples/next-runtime-snapshot/src/devframe.ts | Registers RPCs via serverFunctions and re-exports types from per-RPC files. |
| examples/next-runtime-snapshot/src/rpc/* | Introduces per-file RPCs and a serverFunctions barrel with module augmentation. |
| examples/next-runtime-snapshot/src/client/app/components/* | Removes as any casts from RPC calls now that names are typed. |
| examples/files-inspector/src/devframe.ts | Registers RPCs via serverFunctions barrel instead of inline definitions. |
| examples/files-inspector/src/rpc/* | Introduces per-file RPCs and a serverFunctions barrel with module augmentation. |
| examples/files-inspector/src/client/routes/* | Removes as any casts from RPC calls now that names are typed. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1,22 @@ | |||
| import type { HISTORY_KEY } from './constants' | |||
Comment on lines
+96
to
+104
| for (const token of fakeTokens(prompt)) { | ||
| if (stream.signal.aborted) { | ||
| cancelled = true | ||
| break | ||
| } | ||
| stream.write(token) | ||
| acc += token | ||
| await new Promise(r => setTimeout(r, intervalMs)) | ||
| } |
Mirrors @vitejs/devtools-kit conventions and leaves room for sibling files like rpc/utils.ts next to rpc/index.ts as the rpc surface grows.
Node native ESM (bin.mjs -> src/devframe.ts) doesn't auto-resolve directory or extension-less imports — only the bundler-backed test harness did. Add explicit .ts extensions so the playwright e2e webserver can boot the examples.
Comment on lines
+18
to
+21
| declare module 'devframe/types' { | ||
| interface DevToolsRpcSharedStates { | ||
| [HISTORY_KEY]: ChatHistory | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Establishes a recommended project layout for non-trivial devframes — one file per RPC under
src/rpc/, with a siblingsrc/context.tsexposing aWeakMap<DevToolsNodeContext, T>for sharing setup-time state (channels, shared state handles, loaders) across per-file RPCs. The skill and the RPC guide previously implied this layout in code samples but never stated the convention; the three examples now follow it (streaming-chatdemonstrates the WeakMap context for its shared channel + history).Includes a framework fix:
definition.__resolvedis now keyed per-context viaWeakMap, so module-level RPC defs registered into multiple contexts in the same process (multi-server tests, hot-reload teardown/replay) no longer share a handler closed over the first context's state. This unlocks the new pattern in tests without requiring user-side workarounds.As a side effect of the type-safe client registry barrels (
const serverFunctions = [...] as const+RpcDefinitionsToFunctions), everyrpc.call(... as any)cast in the example clients is gone.