feat(react): add createProgramHooks factory for Codama program plugins - #1900
feat(react): add createProgramHooks factory for Codama program plugins#1900amilz wants to merge 1 commit into
Conversation
A Codama-generated program plugin is already a fully typed runtime map — codecs with fetch helpers, instruction builders with sendTransaction, PDA finders — so one factory covers every program with no per-program codegen. Each hook maps one plugin function onto one existing primitive in this package and is named for the primitive it uses: useAccount, useMaybeAccount, useAllAccounts, useAllMaybeAccounts and usePda issue a single request via useRequest; useTrackedAccount additionally opens a subscription via useTrackedData, decoding both the initial getAccountInfo response and later accountNotifications with the plugin's own codec; useSendInstruction builds, signs and sends via useAction. Passing a null address, address list or seed object disables a hook rather than requiring a conditional call.
🦋 Changeset detectedLatest commit: 2b0af02 The changes in this PR will be included in the next version bump. This PR includes changesets to release 48 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
| @@ -0,0 +1,112 @@ | |||
| /** | |||
| * Type-level projections over a Codama-generated program plugin. | |||
There was a problem hiding this comment.
is this file automatically generated each build? Curious if we truly need it committed to the repo vs having it as part of the build artifacts and NPM will deploy it even then.
| const aKeys = Object.keys(aRecord); | ||
| if (aKeys.length !== Object.keys(bRecord).length) return false; | ||
| return aKeys.every(key => Object.hasOwn(bRecord, key) && isEqual(aRecord[key], bRecord[key])); | ||
| } |
There was a problem hiding this comment.
This is extremely expensive to run on every render. I clanked a few options and that version seems needed but it does raise a few alarm bells for me to see deep equality checks in a hook.
| export function getAccountEntry(client: object, capability: string, key: string): ProgramAccountEntry { | ||
| const entry = getNamespace(client, capability).accounts?.[key]; | ||
| if (!entry) { | ||
| throw new Error(`Program plugin "${capability}" does not declare an account named "${key}".`); | ||
| } | ||
| return entry; | ||
| } |
There was a problem hiding this comment.
I'd add tsdocs strings to each function with examples for documentation and sharing intent on usage
A Codama-generated program plugin is already a fully typed runtime map — codecs with
fetchhelpers, instruction builders withsendTransaction, PDA finders.createProgramHooksprojects that map into React hooks, so one factory covers every program with no per-program codegen.Point the type argument at the client type and an uninstalled plugin is a compile-time error. A
nulladdress, address list or seed object disables a hook rather than requiring a conditional call.Hooks
Each one maps a single plugin function onto an existing primitive in this package, and is named for the primitive it uses.
useAccount.fetchuseRequestuseMaybeAccount.fetchMaybeuseRequestuseAllAccounts.fetchAlluseRequestuseAllMaybeAccounts.fetchAllMaybeuseRequestusePdafind*PdauseRequestuseTrackedAccount.decodeuseTrackedDatauseSendInstruction.sendTransactionuseActionuseTrackedAccountis the only one that opens a subscription: initialgetAccountInfoplusaccountNotifications, slot-deduped, both decoded with the plugin's own codec. It's opt-in rather than the default foruseAccount, since holding a subscription per address isn't the right cost for every app.Notes
ProgramAccountKey,ProgramInstructionInput, …) derive everything from the plugin type — no Codama naming convention is reimplemented.useStableValueholds inline array/object arguments stable by value, sousePda('associatedToken', { mint, owner })doesn't rebuild its store every render.createProgramHooks<{ token: TokenPlugin }>('token')still works for a single-plugin slice; on a multi-plugin type it degrades to an instructive error literal rather than silently typing against the union. Happy to simplify to curried-only if you'd prefer one call shape.Test plan
createProgramHooks-test.browser.tsx) and a type test with a self-contained plugin fixture.packages/react: prettier, lint, typecheck, browser unit (313), node unit (77), treeshakability — all green.