Skip to content

feat(sdk): expose strict Order and Product types with a typed client - #257

Merged
mergekeeper[bot] merged 2 commits into
accensa:mainfrom
meem08:feat/126-sdk-order-product-types
Aug 27, 2026
Merged

feat(sdk): expose strict Order and Product types with a typed client#257
mergekeeper[bot] merged 2 commits into
accensa:mainfrom
meem08:feat/126-sdk-order-product-types

Conversation

@meem08

@meem08 meem08 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

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 any or Record<string, unknown>, which defeats TypeScript in the consuming apps. This PR ports the Order/Product shapes to strict TS types in packages/sdk/src/types, adds strict mappers from the indexer's wire rows (themselves decoded from Soroban transfer XDR) into those types, and ships a small AccensaClient whose fetchOrder / listOrders / fetchProduct / listProducts methods all return the strict types.

Closes #126


What changed

1. Strict types — packages/sdk/src/types/

  • order.tsOrder interface: id (tx hash), productId? (route), amount (decimal string, never a float), asset?, payer?, method?, ledger?, createdAt, and metadata?.
  • product.tsProduct interface: id (route path), method?, calls, totalRevenue (decimal string), and metadata?.
  • Every column that can be absent on the wire is declared optional (?) with strict null checks: metadata is undefined unless a deployment actually publishes it — never null, never any.
  • Available from the package root and via the new @accensa/sdk/types subpath export.

2. Strict mappers — packages/sdk/src/mapping.ts

  • orderFromWire / ordersFromResponse and productFromWire / productsFromResponse map the snake-cased JSON the indexer publishes (/api/payments, /api/routes) into Order / Product.
  • SQL NULL optional columns are normalised to undefined, and amounts stay strings throughout.
  • Single-row mappers return null for unreadable input (matching the defensive style of parseSettlementHeader); the response mappers throw on a malformed row rather than silently dropping it from a page.

3. Typed client — packages/sdk/src/client.ts

  • AccensaClient with:
    • listOrders({ limit, cursor })OrdersPage (orders, nextCursor) — mirrors GET /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) — mirrors GET /api/routes.
    • fetchProduct(route)Product | null — searches the top 200 products by revenue, documented.
  • AccensaError with the HTTP status is thrown for non-2xx responses; headers lets callers attach whatever credential the deployment requires (the read endpoints are scoped to the signed-in merchant).

4. Wiring

  • index.ts re-exports the client, mappers, and types.
  • package.json adds the ./types export; tsconfig.json includes src/**/*.ts.

5. Tests & docs

  • src/mapping.test.ts — full-row mapping, null → undefined normalisation, required-field validation, malformed-row rejection (15 tests).
  • src/client.test.ts — endpoints, query params, header passing, trailing-slash handling, AccensaError on 401, fetchOrder/fetchProduct lookups (14 tests).
  • README.md — short "Reading Orders and Products" section with usage.

Design notes

  • What an "order" is here: one indexed payment — a settled Stellar Asset Contract transfer the indexer decoded from Soroban XDR and recorded in the merchant's ledger. The dashboard's "Recent Orders" widget reads exactly this data (/api/payments).
  • What a "product" is here: one of the merchant's sellable x402 endpoints, as the indexer sees it — identity plus aggregated calls / totalRevenue within the reporting window. Price configuration lives in the seller's own routesConfig, which the indexer does not hold, so the SDK types what the API can actually return rather than inventing fields.
  • Why optional fields are undefined, not null: strict null checks (metadata?: …) are the acceptance criterion. Mappers convert SQL NULL to undefined, so a consumer never has to handle both.
  • Why amounts are strings: the indexer writes money as NUMERIC and serves it as text (amount::text); the SDK preserves that invariant so money never crosses this boundary as a float.

Acceptance criteria

Criterion Status
Consuming the SDK provides full autocomplete for Order and Product fields Order/Product exported from the root and @accensa/sdk/types, used as the return types of every client method
Strict null checks are enforced on optional fields (e.g. metadata) ✅ optional fields declared ?, mappers normalise null → undefined
SDK builds without TS errors pnpm typecheck passes for web and SDK

Testing

  • 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.

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>
@drips-wave

drips-wave Bot commented Aug 26, 2026

Copy link
Copy Markdown

@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! 🚀

Learn more about application limits

@vercel

vercel Bot commented Aug 26, 2026

Copy link
Copy Markdown

@meem08 is attempting to deploy a commit to the ACCENSA Team on Vercel.

A member of the Team first needs to authorize it.

@mergekeeper

mergekeeper Bot commented Aug 27, 2026

Copy link
Copy Markdown

MergeKeeper review

Scope: in scope for linked issue #126.
Verdict: clean

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: 2f231b27a2c24b1e04e0457eed9c911e76626446.
CI and merge eligibility are checked separately.

@mergekeeper mergekeeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved

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.

@mergekeeper
mergekeeper Bot merged commit e8d2cce into accensa:main Aug 27, 2026
0 of 8 checks passed
@mergekeeper

mergekeeper Bot commented Aug 27, 2026

Copy link
Copy Markdown

Merged

Merged with squash.

Ajibose added a commit to Ajibose/accensa-app that referenced this pull request Aug 27, 2026
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.
mergekeeper Bot pushed a commit that referenced this pull request Aug 27, 2026
… 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SDK: Expose strict TypeScript types for Order and Product

1 participant