feat(sdk): expose strict Order and Product types with a typed client - #257
Conversation
The SDK returned no typed surface for reading a merchant's orders and products, leaving consumers to type API responses as any or Record<string, unknown>. This adds strict Order and Product types in packages/sdk/src/types, mappers that normalize the indexer's wire rows (null optional columns -> undefined) into those types, and an AccensaClient with fetchOrder/listOrders/fetchProduct/listProducts. Closes accensa#126 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
|
@meem08 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
|
@meem08 is attempting to deploy a commit to the ACCENSA Team on Vercel. A member of the Team first needs to authorize it. |
|
MergeKeeper review Scope: in scope for linked issue The pull request successfully implements strict TypeScript types, mappers, and a typed client for Order and Product in the SDK, fulfilling all issue requirements and acceptance criteria. Reviewed commit: |
|
Merged Merged with |
Rebasing onto main picked up accensa#257's own AccensaClient (an unrelated indexer HTTP read client re-exported from @accensa/sdk, added after this branch was created), which collided by name with the on-chain contract client added here. Renames the latter to ReceiptAnchorClient (file, class, options type, export subpath, and README) to remove the ambiguity, and fixes a missing comma in packages/sdk/package.json's exports map that accensa#257 merged into main with invalid JSON, which was failing pnpm install for the whole workspace.
… docs (#270) * feat(sdk): add AccensaClient with custom contract initialization, and docs @accensa/sdk had no way to read Accensa's on-chain ReceiptAnchor contract directly, and no documented way for a merchant who deployed their own ReceiptAnchor instance to point at it instead of Accensa's. Adds AccensaClient (packages/sdk/client.ts, exported from the new @accensa/sdk/client entry point so its @stellar/stellar-sdk dependency stays opt-in) with a contractId constructor option that defaults to Accensa's testnet ReceiptAnchor and can be overridden for a merchant-deployed instance, alongside rpcUrl/networkPassphrase to match. Documents the default-vs-custom tradeoff and RPC requirements in the SDK README. Closes #139 * fix(sdk): resolve rebase collision with the newly-merged AccensaClient Rebasing onto main picked up #257's own AccensaClient (an unrelated indexer HTTP read client re-exported from @accensa/sdk, added after this branch was created), which collided by name with the on-chain contract client added here. Renames the latter to ReceiptAnchorClient (file, class, options type, export subpath, and README) to remove the ambiguity, and fixes a missing comma in packages/sdk/package.json's exports map that #257 merged into main with invalid JSON, which was failing pnpm install for the whole workspace.
Summary
The SDK previously had no typed surface for reading a merchant's orders and products — consumers had to type indexer API responses themselves, falling back on
anyorRecord<string, unknown>, which defeats TypeScript in the consuming apps. This PR ports the Order/Product shapes to strict TS types inpackages/sdk/src/types, adds strict mappers from the indexer's wire rows (themselves decoded from SorobantransferXDR) into those types, and ships a smallAccensaClientwhosefetchOrder/listOrders/fetchProduct/listProductsmethods all return the strict types.Closes #126
What changed
1. Strict types —
packages/sdk/src/types/order.ts—Orderinterface:id(tx hash),productId?(route),amount(decimal string, never a float),asset?,payer?,method?,ledger?,createdAt, andmetadata?.product.ts—Productinterface:id(route path),method?,calls,totalRevenue(decimal string), andmetadata?.?) with strict null checks:metadataisundefinedunless a deployment actually publishes it — nevernull, neverany.@accensa/sdk/typessubpath export.2. Strict mappers —
packages/sdk/src/mapping.tsorderFromWire/ordersFromResponseandproductFromWire/productsFromResponsemap the snake-cased JSON the indexer publishes (/api/payments,/api/routes) intoOrder/Product.NULLoptional columns are normalised toundefined, and amounts stay strings throughout.nullfor unreadable input (matching the defensive style ofparseSettlementHeader); the response mappers throw on a malformed row rather than silently dropping it from a page.3. Typed client —
packages/sdk/src/client.tsAccensaClientwith:listOrders({ limit, cursor })→OrdersPage(orders,nextCursor) — mirrorsGET /api/payments.fetchOrder(txHash)→Order | null— searches the most recent 1000 indexed payments (the API max), documented, since no lookup-by-hash endpoint exists yet.listProducts({ limit, from, to })→ProductsPage(products,truncated) — mirrorsGET /api/routes.fetchProduct(route)→Product | null— searches the top 200 products by revenue, documented.AccensaErrorwith the HTTP status is thrown for non-2xx responses;headerslets callers attach whatever credential the deployment requires (the read endpoints are scoped to the signed-in merchant).4. Wiring
index.tsre-exports the client, mappers, and types.package.jsonadds the./typesexport;tsconfig.jsonincludessrc/**/*.ts.5. Tests & docs
src/mapping.test.ts— full-row mapping,null → undefinednormalisation, required-field validation, malformed-row rejection (15 tests).src/client.test.ts— endpoints, query params, header passing, trailing-slash handling,AccensaErroron 401,fetchOrder/fetchProductlookups (14 tests).README.md— short "Reading Orders and Products" section with usage.Design notes
/api/payments).calls/totalRevenuewithin the reporting window. Price configuration lives in the seller's ownroutesConfig, which the indexer does not hold, so the SDK types what the API can actually return rather than inventing fields.undefined, notnull: strict null checks (metadata?: …) are the acceptance criterion. Mappers convert SQLNULLtoundefined, so a consumer never has to handle both.NUMERICand serves it as text (amount::text); the SDK preserves that invariant so money never crosses this boundary as a float.Acceptance criteria
Order/Productexported from the root and@accensa/sdk/types, used as the return types of every client methodmetadata)?, mappers normalisenull → undefinedpnpm typecheckpasses for web and SDKTesting
pnpm --filter @accensa/sdk test— 6 files, 110 tests passed (29 new).pnpm typecheck— web and SDK both clean.pnpm exec prettier --check packages/sdk— clean.