- creator-studio-plan.md: scope, decisions, architecture (buzz already a service; in-process monetization module; ClickHouse analytics), main-app add/remove, phasing, and the consolidated "Questions for Justin" review-pass list. - creator-studio-overview.md + creator-studio/ per-page specs (models, dashboard, analytics, earnings, licensing, settings, join) + index with a cross-cutting decisions table. - buzz-client-handoff.md: state of the @civitai/buzz work for a fresh session. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8.8 KiB
@civitai/buzz client — handoff
Status + context for continuing the buzz-client work in a fresh session. Part of the Creator Studio effort (see creator-studio-plan.md §5.1 / §7.2) — the buzz service is already external, so this package is the shared, server-side client both the main app and the future SvelteKit spokes call.
What exists now (done + verified)
A new package packages/civitai-buzz/ (@civitai/buzz), and the main app refactored to use it.
pnpm typecheck is green (it exercises the widely-imported buzz.constants.ts facade across the whole app).
Package layout
| File | Purpose |
|---|---|
src/env.ts |
Lazy/memoized loadBuzzEnv() → { endpoint } from BUZZ_ENDPOINT (required prod, optional dev). |
src/account-types.ts |
Browser-safe, no deps: buzz account-type model + the pure friendly↔API name map. |
src/client.ts |
createBuzzClient(options) → typed per-endpoint client; BuzzApiError; request<T> transport. |
src/index.ts |
export * from client / account-types / env. |
README.md |
Usage reference. |
Client API — createBuzzClient(options)
options: { endpoint?, retries=3, log?, mapError? }. Returns:
- reads:
getAccount,getUserBuzzByAccountType,getUserAccounts,getAccountTransactions,getUserTransactionsReport,getAccountBalances,getAccountSummary,getContributors,getCounterparties,previewMultiTransaction,listMultiTransactions - writes:
createTransaction,createTransactions,refundTransaction,createMultiTransaction,refundMultiTransaction - escape hatch:
request<T>(urlPart, init?), plusendpoint()
Account-aware methods take the app's friendly account types ('yellow'/'blue'/…) and map to the buzz API
internally. On a non-2xx the client throws BuzzApiError (has .status), or the result of mapError.
account-types.ts also exports (browser-safe): toApiType, toClientType, toApiTransaction,
clientToApiAccountType, buzzApiAccountTypes, and the BuzzAccountType / BuzzApiAccountType / BuzzSpendType
/ … types.
Key design decisions (don't re-litigate)
- Package discipline (avoid sprawl):
@civitai/buzzis the one thing that earns package status now (used by the main app broadly + creator-studio). Creator monetization ops (fee/access, kysely + this client) and analytics reads (ClickHouse) live as creator-studio modules, extracted to a package only at the full-cutover consolidation (when the main app adopts them). A package needs ≥2 app importers and to be a coherent capability — not a code-slice. - Server-only client, browser-safe types: the fetch client must never run in the browser (buzz service is internal). The account-type helpers are pure and browser-safe by design.
- Friendly types (chosen by the user): the pure name-map moved into the package; the app's
BuzzTypes(src/shared/constants/buzz.constants.ts) is now a facade delegatingtoApiType/toClientType/getApiTransactionto the package, andbuzzTypeConfig.valueis sourced from the package'sclientToApiAccountType(single source, no drift). All existing exports/types are re-exported, so no consumer import sites changed. mapErrorhook: keeps buzz-status → tRPC-error mapping centralized (the main app configures it once).- Behavior preserved: same retry count (3), same error mapping. This was a lift, not a behavior change.
Main-app changes
package.json— added"@civitai/buzz": "workspace:*".next.config.mjs— added@civitai/buzztotranspilePackages.src/shared/constants/buzz.constants.ts—BuzzTypesis now a facade (see above); name-map/types removed and re-exported from the package.TransactionType,buzzTypeConfig(UX flags),buzzSpendTypes/buzzBankTypes/buzzPurchaseTypes,toSpendType,toOrchestratorType,buzzConstantsstayed in the app.src/server/services/buzz.service.ts—buzzService = createBuzzClient({ endpoint, log, mapError }); every wrapper calls a typed method (nobuzzApiFetchalias, no localbaseEndpoint()— both removed).
Buzz service reference
The buzz service is a .NET / ASP.NET Core Minimal API on PostgreSQL at C:\work\civitai-buzz (ClickHouse
is only a post-commit tracking sink). Its routes are defined in src/Civitai.Buzz.Api/Program.cs (e.g.
GET /account/{type}/{id}, GET /user/{id}/transactions/report, POST /transaction, GET /account-balances).
The AccountTransaction view (src/Civitai.Buzz.Infrastructure/Views/) backs the earnings reads.
What's left (follow-ups, not done)
Migrate remaining call sites inDone — every call site now uses a named method (buzz.service.tsfrom thebuzzApiFetchalias to the named methods.getAccountTransactions,createTransaction,createTransactions,refundTransaction,createMultiTransaction,refundMultiTransaction,previewMultiTransaction,listMultiTransactions,getAccountSummary,getContributors,getCounterparties,getUserTransactionsReport,getAccountBalances).buzzApiFetchis deleted.getAccountSummary/getContributors/getCounterpartiesgained anopts.query(andopts.accountId) so the app's URL-param reads fit the typed methods. Note:createBuzzTransactionMany's validity filter dropped a provably-dead|| fromAccountType === 'cashPending'branch (it ran after the friendly→API map, so it was always false); behavior is unchanged, but if the intent was to keep cashPending self-transactions, that's a pre-existing bug to fix separately.AddDone —ping()andgetTransactionByExternalId()methods, then retire the localbaseEndpoint().ping()(root liveness,falseon error) andgetTransactionByExternalId()(viarequest's newallow404option →nullon 404) are on the client; the two direct-fetchsites andbaseEndpoint()are gone.Tighten response types — several read methods returnDone —<T = unknown>; define concrete response types.src/responses.tsdefines the buzz-service wire contract types (account-type fields are the PascalCaseBuzzApiAccountType; dates are ISO strings — the app maps/coerces to friendly types +Datevia its zod schemas). Each read method now defaults its genericTto the matching concrete type (GetAccountTransactionsResponse,GetTransactionsReportResponse,GetAccountBalancesResponse,GetAccountSummaryResponse,GetContributorsResponse,GetCounterpartiesResponse,ListMultiTransactionsResponse,PreviewMultiTransactionResponse,BuzzTransactionResponse), and the write methods returnCreate*/Refund*types. The<T = …>generic is kept as an escape hatch (e.g. the app still overridesgetCounterpartieswith its friendly-typed domain view), but no call site needs to pass one for the default wire shape — the app's inline response types were removed in favour of these.- Typed query params (done later) — the read methods no longer take a raw
query: string.src/queries.tsdefines a typed params object per method (GetAccountTransactionsQuery,GetTransactionsReportQuery,GetAccountBalancesQuery,GetAccountSummaryQuery,GetContributorsQuery,GetCounterpartiesQuery,PreviewMultiTransactionQuery,ListMultiTransactionsQuery); the client serializes them internally (toQueryString): friendly account types → API values,Dates → the format each endpoint wants (full ISO for transactions/contributors, date-onlyYYYY-MM-DDUTC for report/summary), arrays → repeated keys, nullish/empty dropped. The app call sites now pass structured objects (no moreQS.stringify/URLSearchParamshand-building). Behavior note: the report'sstart/endwere previously formatted with dayjs in local time; they're now date-only in UTC, which is identical on UTC-run servers (prod) and matches how the summary endpoint already formatted its dates — only a non-UTC dev box could see a one-day shift.
- Typed query params (done later) — the read methods no longer take a raw
- Consume from creator-studio — when that app exists, import
@civitai/buzzin its server code (+page.server.ts/+server.ts) for account/transaction operations. Server-only; never in a.sveltecomponent. Note (Justin): analytics + earnings dashboards read from ClickHouse (daily aggregates / materialized views), not the buzz client's report endpoints — the buzz service is too slow for that path. - Docs — mark
@civitai/buzzas built (a full client) in creator-studio-overview.md / creator-studio-plan.md.
Verify
pnpm run typecheck # green
# lint: use `pnpm run lint` (full repo) — invoking eslint on individual files hits a repo config quirk