// The TYPED hook surface for the `app` api — bound ONCE, here, and imported by // every page instead of `@voltro/client`. // // `AppProcedures` is emitted by codegen into the api's `rpcGroup.generated.ts`: // a type-level map of every rpc tag (the app's own AND the plugins', e.g. // `billing.*`) to the descriptor behind it. Feeding it to `createHooks` makes // the tag a literal union and the input/output types INFER: // // const { data } = useSubscription('projects.list') // rows, typed // const create = useMutation('projects.create') // input + output typed // // So the row shapes this file used to declare by hand are gone. They were a // MIRROR of the api's descriptor outputs, kept in sync by nobody: the old // `useSubscription>('app', 'projects.list')` asserted a // type nothing checked against the server, and a typo'd tag only showed up at // runtime. Both are compile errors now. // // `'app'` is the key this web app gave the api in `app.config.ts` → `apis`. // `import type` is erased, so the binding costs the browser bundle nothing. import { createHooks } from '@voltro/client' import type { AppProcedures } from '@{{projectName}}/api/rpcGroup' // Destructured on purpose — `react-hooks/rules-of-hooks` only treats a member // call as a hook when the object is PascalCase, so top-level `useX` identifiers // are what keep the React lint rules working at every call site. export const { useSubscription, useMutation, useAction } = createHooks('app') // The caller's resolved identity, from `session.me`. The SSR gate reads it, and // a LOADER is not a hook — it calls `ctx.query` — so this shape stays hand-written. export interface Identity { readonly type: string readonly id: string | null readonly tenantId: string | null } // The billing plugin merges a typed `EntitlementExceeded` into the wire-error // union of any entitlement-gated mutation (projects.create / invites.create). // This is how the paywall is detected — NOT by string-matching a message. export const isEntitlementExceeded = (error: unknown): boolean => typeof error === 'object' && error !== null && (error as { _tag?: unknown })._tag === 'EntitlementExceeded'