Brandapp sdk review
Review consumer project code for @reopt-ai/brandapp-sdk usage anti-patterns and suggest improvements. Triggers on "brandapp-sdk review", "SDK review", "improve SDK usage", "EAV optimization", "brandapp-sdk audit".From its SKILL.md
npx -y skills add reopt-ai/reopt-skills --skill brandapp-sdk-reviewAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
3 things to look at
- reads credentialsReads from 4 credential sources: `.env` and 3 more.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
- runs commandsInstructs the agent to run 1 command, including `grep '"@reopt-ai/brandapp-sdk"' package.json`.
SKILL.md
9.7 KB, ~2.7k tokens by cl100k_base, as published. Nobody here has run it
Brandapp SDK Review
This is NOT the SDK you know. Read
node_modules/@reopt-ai/brandapp-sdk/docs/before judging any usage (the package ships docs at top-leveldocs/, notdist/docs/). Anti-pattern remedies live there; this skill is grep keys + categories only.
When to apply
A consumer project already uses @reopt-ai/brandapp-sdk and wants an audit. Triggers: "review", "audit", "improve SDK usage", "EAV optimization".
Step 1 — Pin agent rules into AGENTS.md / CLAUDE.md
Source: the module's own agent-rules file once it ships one (@reopt-ai/brandapp-sdk does not, as of 3.1.0). Fallback: agent-rules.md bundled with this skill. Wrap content between:
<!-- BEGIN:reopt/brandapp-sdk-agent-rules -->
…content from source…
<!-- END:reopt/brandapp-sdk-agent-rules -->
Markers are shared with brandapp-sdk-install — same module, one block. If the block already exists from install, leave it alone (replace only when stale).
Step 2 — Version gate
grep '"@reopt-ai/brandapp-sdk"' package.json
< 3.1.0— noplanshosted checkout (createCheckout/getCheckout/cancel,RequiredTermsError). If the app builds subscription / checkout UI, recommend 3.1 and run Err5 below.< 3.0.0— webhook contract differs from the live platform sender: 2.xverifySignature(body, sig, secret)+record.*event handlers silently 401-reject / never fire in prod. BrowserclientSecretwas allowed (now throwsCONFIG_BROWSER_SECRET).ReoptAdapterConfig/ReoptEavConfig/ReoptAdapterErroraliases still exist (removed in 3.0). Bump to 3.0 and run the W / Cfg patterns below.< 2.3.0— AI errors not unified (streaming 402 wasSTREAM_ERROR, notCreditLimitError);ModelAccessError/ModelNotFoundError/ContentFilterErrorabsent;sdk.ai.models()lacksmodality/isDefault. Recommend 2.3 for AI work.< 2.2.0— mutations retried by default (dup-write / dup-credit risk), AItimeoutis wall-clock not idle,backfilldoes per-record PATCH, noQUERY_TOO_LARGEguard. Recommend 2.2+.< 2.0.0— env-var rename ships in 2.0 without aliases. Migrate.env+ bump first; patterns below assume 2.0+ surface.< 1.12.0(pre-2.0 lineage) — service token (1.12), schema drift (1.11, Schema Pattern 5), host split (1.10, hardcodedwww.reopt.ai), narrowed EAV errors (1.9, Error Pattern 3/4), 4xx classes +toleranceMs(1.6) land incrementally; bump toward 2.x.
Step 3 — Detect anti-patterns by category
For each match, name the pattern, point at the file/line, then route the consumer to the relevant docs/ section (paths relative to node_modules/@reopt-ai/brandapp-sdk/docs/; api-reference.md is the combined surface). Do not paste before/after code into the review report — read the doc and quote the canonical fix.
SDK init / lifecycle → docs/api-reference.md
| Pattern | Grep signal |
|---|---|
P1 Hand-rolled singleton (new Proxy<>) | new Proxy({} as + createReoptEavClient |
| P2 List-then-find on the client | .find(( / .filter(( chained on listAllRecords / records.list |
| P3 Manual upsert (find + create/update) | if (existing) + records.create + records.update in same fn |
| P4 Manual pagination loop | while + page + totalPages + records.list |
| P5 Hand-rolled enum normalization | new Set< + .has(.* as |
P6 Locally redefined asString / asNumber / asBoolean / asDate / asJson | function names match |
P7 Per-item records.delete loop | Promise.all( + .map( + records.delete |
P8 Per-item records.update loop | Promise.all( + .map( + records.update |
P9 .length on listAllRecords for counting | .length on a listAllRecords result |
Auth wiring → docs/api-reference.md
| Pattern | Grep signal |
|---|---|
Auth1 No error boundary on useSession | authClient.useSession() without try/catch or ErrorBoundary nearby |
Auth2 No middleware.ts route protection | middleware.ts absent; auth checked inside page components |
| Auth3 Module-level mutable auth state | let session / let user at module scope |
Auth4 signOut without error handling | authClient.signOut( not awaited or unguarded |
Auth5 signInWithReopt swallows errors | signIn.oauth2( returns void or catches without surfacing |
| Auth6 No session cache strategy | repeated getSession() calls per request |
| Auth7 Re-implementing cross-subdomain session verification | manual cookie parsing for *.reopt.ai; use verifySession / getSessionFromCookies |
Error handling → docs/errors.md
| Pattern | Grep signal |
|---|---|
Err1 Generic catch instead of SDK error classes | catch (e) without isReoptSDKError / class check |
Err2 Unhandled API errors (no handleApiError wrapper) | route handlers without a centralized error handler |
Err3 EAV mutation on linkedTo='brandappAuthUser' without 1.9 narrowed catches | records.create / bulk on linked entity, no AuthUserRecordExistsError / DuplicateAuthUserError / AuthUserNotFoundError branch |
Err4 Legacy e.code === 'REQUEST_ERROR' string check | literal string match — pre-1.9 only |
Err5 Plans checkout without RequiredTermsError / 409 LIVE_MODE_UNSUPPORTED handling (3.1) | plans.createCheckout( with no isRequiredTermsError / live-mode branch |
Config / security → docs/environment.md (env / hosts), docs/api-reference.md (service token)
| Pattern | Grep signal |
|---|---|
Cfg1 Hardcoded URL / stale www.reopt.ai | baseUrl: literal containing www.reopt.ai |
Cfg2 Missing import "server-only" | createReopt* / createLazySDK called in a file without server-only |
Cfg3 ! non-null env assertions w/o validation | process.env.BRANDAPP_*! without zod / t3-env nearby |
Cfg4 Redundant clientSecret alongside a token (token wins, 3.0) | token: and clientSecret: on the same ReoptSDKConfig — drop clientSecret |
Cfg5 clientSecret reachable in the browser (3.0 throws CONFIG_BROWSER_SECRET) | NEXT_PUBLIC_BRANDAPP_CLIENT_SECRET, or clientSecret: in a "use client" file / createBrandappProvider — mint a server token, pass { token } |
| Cfg6 Removed type/error aliases (3.0) | ReoptAdapterConfig / ReoptEavConfig / ReoptAdapterError — rename to ReoptSDKConfig / ReoptSDKError |
Schema / types → docs/api-reference.md
| Pattern | Grep signal |
|---|---|
| Sch1 Type-safe entity client unused | sdk.eav.entity( w/o schema passed at SDK init |
| Sch2 Hand-rolled schema resolution cache | manual Map of entityName → entityId |
Sch3 Hardcoded attributeId literals | UUID-shaped strings in attribute lookups |
Sch4 defineEntity missing linkedTo for 1:1 user metadata | per-user entity without linkedTo: 'brandappAuthUser' |
| Sch5 Schema drift unchecked (1.11+) | no computeEavSchemaHash in build / no verifyEavSchema probe |
Performance → docs/api-reference.md
| Pattern | Grep signal |
|---|---|
| Perf1 Duplicate SDK clients per file | multiple createLazySDK( / createReoptSDK( in lib/ |
| Perf2 Over-fetching attributes | records.list without attributes: projection on wide entities |
React → docs/api-reference.md
| Pattern | Grep signal |
|---|---|
R1 Manual useEffect + useState for EAV fetching | replace with useRecords / useRecord |
| R2 Manual invalidation after mutation | replace with useUpsertRecord etc. (auto-invalidate) |
| R3 Manual infinite-scroll | replace with useInfiniteRecords |
Webhook → docs/api-reference.md
| Pattern | Grep signal |
|---|---|
| W1 Hand-rolled HMAC verification | manual crypto.createHmac against the webhook secret — use createWebhookHandler |
| W2 Stale 2.x webhook contract (3.0 breaking) | record.created/record.updated/entity./subscription.changed/customer.created in handlers:, or verifySignature( called with 3 args — move to contactCreated/contactUpdated/contactDeleted/workflowRun* + timestamp-first verifySignature(timestamp, body, sig, secret) |
Debug → docs/environment.md (BRANDAPP_SDK_DEBUG / BRANDAPP_SDK_LOG_FORMAT)
| Pattern | Grep signal |
|---|---|
| D1 Custom SDK request logging | bespoke fetch wrapper instead of BRANDAPP_SDK_DEBUG / BRANDAPP_SDK_LOG_FORMAT |
CMS / external site (1.8+) → docs/cms.md
| Pattern | Grep signal |
|---|---|
| CMS1 Calling removed write surface | cms.posts.create / .update / .delete / cms.tags.create — gone in 1.8 |
| CMS2 Hand-rolled blog metadata | manual <head> tags — use toMetadata(post) |
| CMS3 Hand-rolled sitemap / RSS | manual XML — use toSitemapItems / toRssFeed |
Step 4 — Report
For each finding emit:
[<pattern-id>] <pattern-name>
file:line
why: <one line — pulled from docs/...md>
fix: <one line + link to docs/<file>.md#anchor>
Group by category; lead with version-gate failures (Step 2). Do not paste full before/after code in the report — keep it scannable.
Step 5 — Offer auto-fix
Patterns P5/P6/P7/P8/P9/Sch3/R1/R2/W1/W2/Cfg6/CMS2/CMS3 are mechanical rewrites — offer to apply directly. P1/P3/Auth*/Err3/Err5/Cfg1–Cfg5/Sch1/Sch4/Sch5 require human judgment — propose, don't apply.
Safety
- Never apply fixes that change
.envkeys without explicit user approval (2.0 rename is wholesale). - Never edit
package.jsonversion pins without confirming the rest of the matrix passes Step 2. - Read
docs/for the canonical fix; do not invent code that the docs do not endorse.
What ships with it: 3 files
6.8 KB alongside SKILL.md
- agent-rules.md6.1 KB
- metadata.json270 B
- README.md456 B