Skip to content

[RFC] @alchemy/data-apis v1 — publish-ready surface#2540

Closed
blakecduncan wants to merge 8 commits into
blake/data-sdk-mvpfrom
blake/data-sdk-v1
Closed

[RFC] @alchemy/data-apis v1 — publish-ready surface#2540
blakecduncan wants to merge 8 commits into
blake/data-sdk-mvpfrom
blake/data-sdk-v1

Conversation

@blakecduncan

@blakecduncan blakecduncan commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

What

Takes @alchemy/data-apis from the RFC vertical slice (#2539) to publish-ready: the full v1 method surface, hardened REST infrastructure, pagination, normalized errors, docs, CI gates, and alpha-release packaging. Stacked on blake/data-sdk-mvp.

31 methods across 5 namespaces (+ 9 auto-paginating *Pages companions):

Namespace Methods Channel
portfolio getTokensByAddress, getTokenBalancesByAddress, getNftsByAddress, getNftContractsByAddress REST, global, multi-network bodies
prices getTokenPricesBySymbol (chain-agnostic), getTokenPricesByAddress, getHistoricalTokenPrices REST, global
nft full v3 read surface — 21 methods (mutations + v2 legacy excluded per scope plan) REST, network-scoped
token getTokenBalances, getTokenMetadata, getTokenAllowance JSON-RPC
transfers getAssetTransfers JSON-RPC

Highlights

  • @alchemy/common REST hardening: RestRequestSchema gains a typed Query channel (legacy schemas unaffected — verified by type tests); AlchemyRestClient gets bounded retries with backoff (429/5xx/network only, honors Retry-After), per-attempt timeouts, abort support, and a per-request X-Alchemy-Client-Request-Id. New AlchemyApiError family (status/code/requestId/retryAfter); ServerError/FetchError re-parented additively.
  • Pagination: shared paginate() async-generator (stops on empty cursors, throws on repeated cursors, AbortSignal + maxPages) behind 9 hand-written *Pages actions; cursor metadata is declared in codegen.manifest.ts and validated against the spec snapshots at generate time.
  • Error normalization: both channels surface AlchemyApiError; JSON-RPC failures are wrapped at action boundaries with URL-credential redaction (keys never leak through error chains — tested).
  • Spec snapshots re-pinned to docs main (becfd714); prices + token specs added. The pagination validator caught a real docs-spec gap: alchemy_getTokenBalances omits pageKey from its result schema (flagged for the docs team; no Pages companion until fixed).
  • CI: codegen drift gate in the PR workflow (pnpm generate && git diff --exit-code); bump-api-specs workflow_dispatch that re-snapshots docs main and opens a PR.
  • Docs: typedoc entry point + nav registration; reference pages committed under docs/pages/reference/data-apis (covered by the existing docs drift check).

Release packaging (alpha-first)

5.0.3-alpha.0 with publishConfig.tag: "alpha", deliberately not in lerna.json's fixed-version set so the regular release can't ship it as latest. Alpha publish command + graduation checklist in the package README.

Deliberate public-type deltas (spec-accurate)

Fields the specs don't mark required are optional (transfers, ownedNfts, totalCount, ...); bracketed wire keys (contractAddresses[], excludeFilters[]) are exposed unbracketed; the transfers "Not Found (null)" string result branch is collapsed; portfolio/NFT network-slug enums are widened to support the registry-unknown-slug escape hatch.

Test plan

  • 303 tests, all passing: 253 unit (incl. 19 codegen, 53 data-apis wire-level, 11 hardened-rest-client), 7 type tests (TYPECHECK=true), 30 live smoke tests (every namespace against the real API, including two-page pagination walks), export-boundary lock
  • pnpm generate idempotent; snapshot deterministic; lint + build + npm pack --dry-run clean (no .env/scripts/manifest in the tarball)

Out of scope (cross-team, tracked in the scope plan)

ws-tools {slug, chainId, caip2} registry emission; docs-team spec fixes (token pageKey, nft.yaml inline schemas, required lists); wallet-apis three-format adoption; @alchemy/api-specs npm artifact.

🤖 Generated with Claude Code


PR-Codex overview

This PR primarily focuses on updating the VERSION of the data-apis package and enhancing its functionality by adding new types, endpoints, and documentation. It also includes changes to error handling and introduces new features related to token metadata and pricing.

Detailed summary

  • Updated VERSION in packages/data-apis/src/version.ts to "5.0.3-alpha.0".
  • Added PRICES_API_URL in packages/data-apis/src/internal/endpoints.ts.
  • Introduced PricesRestSchema in packages/data-apis/src/schema/rest.ts.
  • Enhanced FetchError and ServerError classes to include detailed API error information.
  • Updated TypeDoc configurations to include new types and endpoints.
  • Added various new type definitions and documentation for data APIs in docs/pages/reference/data-apis.
  • Updated package.json to reflect new version and description.
  • Introduced new utility functions in the common package for better error handling and signal management.

The following files were skipped due to too many changes: packages/data-apis/src/actions/token/getTokenAllowance.ts, packages/data-apis/src/actions/portfolio/getTokensByAddress.ts, packages/data-apis/src/schema/rpc.ts, docs/pages/reference/common/src/type-aliases/KnownAlchemyNetwork.mdx, packages/data-apis/src/actions/nft/getNftSalesPages.ts, packages/data-apis/src/actions/prices/getTokenPricesBySymbol.ts, docs/pages/reference/data-apis/src/type-aliases/DataRpcSchema.mdx, packages/data-apis/src/actions/transfers/getAssetTransfers.ts, packages/data-apis/src/actions/nft/getNftsForOwnerPages.ts, docs/pages/reference/data-apis/src/type-aliases/PortfolioRestSchema.mdx, packages/data-apis/src/actions/portfolio/getNftsByAddressPages.ts, packages/data-apis/src/actions/nft/getNftsForContractPages.ts, packages/data-apis/src/actions/nft/getSpamContracts.ts, packages/data-apis/src/actions/nft/isAirdropNft.ts, packages/data-apis/src/actions/nft/getNftSales.ts, packages/data-apis/src/actions/nft/getContractsForOwnerPages.ts, packages/data-apis/src/actions/nft/computeRarity.ts, packages/data-apis/src/actions/nft/isSpamContract.ts, packages/data-apis/src/actions/nft/getOwnersForNft.ts, packages/data-apis/src/actions/nft/getNftMetadata.ts, packages/data-apis/src/actions/nft/getNftsForCollectionPages.ts, packages/data-apis/src/actions/nft/getCollectionsForOwnerPages.ts, packages/data-apis/src/actions/nft/getFloorPrice.ts, packages/data-apis/src/actions/nft/getContractMetadata.ts, docs/pages/reference/data-apis/src/functions/getTokenAllowance.mdx, packages/data-apis/src/actions/nft/isHolderOfContract.ts, docs/pages/reference/data-apis/src/functions/dataActions.mdx, packages/data-apis/src/actions/nft/getNftsForContract.ts, packages/data-apis/src/actions/nft/getOwnersForContract.ts, packages/data-apis/src/actions/nft/searchContractMetadata.ts, packages/data-apis/src/actions/nft/getCollectionMetadata.ts, packages/data-apis/src/actions/transfers/getAssetTransfersPages.ts, packages/data-apis/src/actions/portfolio/getNftContractsByAddressPages.ts, docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesParams.mdx, packages/data-apis/src/actions/nft/getNftMetadataBatch.ts, packages/data-apis/src/actions/nft/summarizeNftAttributes.ts, docs/pages/reference/common/src/type-aliases/RestRequestParams.mdx, docs/pages/reference/data-apis/src/functions/getTokenMetadata.mdx, docs/pages/reference/data-apis/src/functions/getTokenBalances.mdx, docs/pages/reference/common/src/type-aliases/RestRequestOptions.mdx, packages/data-apis/src/actions/nft/getContractMetadataBatch.ts, packages/data-apis/src/actions/nft/getNftsForCollection.ts, docs/pages/reference/data-apis/src/functions/isAirdropNft.mdx, docs/pages/reference/common/src/type-aliases/AlchemyApiErrorDetails.mdx, docs/pages/reference/data-apis/src/functions/computeRarity.mdx, docs/pages/reference/data-apis/src/functions/isSpamContract.mdx, docs/pages/reference/data-apis/src/functions/searchContractMetadata.mdx, packages/data-apis/src/actions/prices/getTokenPricesByAddress.ts, docs/pages/reference/common/src/functions/resolveNetwork.mdx, docs/pages/reference/data-apis/src/functions/getNftMetadataBatch.mdx, docs/pages/reference/data-apis/src/functions/getOwnersForNft.mdx, docs/pages/reference/data-apis/src/functions/getSpamContracts.mdx, docs/pages/reference/data-apis/src/functions/getOwnersForContract.mdx, docs/pages/reference/data-apis/src/functions/getContractMetadataBatch.mdx, docs/pages/reference/data-apis/src/functions/isHolderOfContract.mdx, packages/data-apis/src/exports.test.ts, docs/pages/reference/data-apis/src/functions/getNftsForContract.mdx, packages/api-codegen/src/manifest.ts, docs/pages/reference/data-apis/src/functions/createDataClient.mdx, packages/data-apis/src/actions/token/getTokenBalances.ts, docs/pages/reference/data-apis/src/functions/getTokenPricesByAddress.mdx, docs/pages/reference/data-apis/src/functions/getTokenPricesBySymbol.mdx, docs/pages/reference/common/src/type-aliases/RestRequestFn.mdx, docs/pages/reference/data-apis/src/functions/getNftsForCollection.mdx, packages/api-codegen/src/rpc/openrpcWalker.ts, docs/pages/reference/data-apis/src/functions/getContractsForOwner.mdx, packages/data-apis/src/actions/nft/getContractsForOwner.ts, docs/pages/reference/data-apis/src/functions/getAssetTransfers.mdx, packages/data-apis/src/actions/prices/getHistoricalTokenPrices.ts, docs/pages/reference/data-apis/src/functions/getCollectionsForOwner.mdx, docs/pages/reference/data-apis/src/functions/getNftsByAddress.mdx, docs/pages/reference/data-apis/src/functions/summarizeNftAttributes.mdx, packages/data-apis/src/actions/nft/getCollectionsForOwner.ts, docs/pages/reference/data-apis/src/functions/getNftsForContractPages.mdx, packages/data-apis/src/internal/errors.ts, docs/pages/reference/data-apis/src/functions/getNftSales.mdx, docs/pages/reference/data-apis/src/functions/getNftsForCollectionPages.mdx, packages/common/src/utils/signals.ts, docs/pages/reference/data-apis/src/functions/getTokenBalancesByAddress.mdx, docs/pages/reference/data-apis/src/functions/getFloorPrice.mdx, docs/pages/reference/data-apis/src/functions/getNftsByAddressPages.mdx, docs/pages/reference/data-apis/src/functions/getNftContractsByAddress.mdx, docs/pages/reference/data-apis/src/functions/getContractsForOwnerPages.mdx, packages/data-apis/src/actions/portfolio/getNftsByAddress.ts, docs/pages/reference/data-apis/src/functions/getNftsForOwner.mdx, docs/pages/reference/data-apis/src/functions/getCollectionsForOwnerPages.mdx, docs/pages/reference/data-apis/src/functions/getNftSalesPages.mdx, packages/data-apis/src/actions/portfolio/portfolio.test.ts, packages/data-apis/src/actions/nft/getNftsForOwner.ts, docs/pages/reference/data-apis/src/functions/getAssetTransfersPages.mdx, docs/pages/reference/common/src/type-aliases/AlchemyRestClientParams.mdx, docs/pages/reference/data-apis/src/functions/getNftContractsByAddressPages.mdx, packages/data-apis/src/actions/portfolio/getNftContractsByAddress.ts, docs/pages/reference/data-apis/src/functions/getHistoricalTokenPrices.mdx, docs/pages/reference/data-apis/src/functions/getTokensByAddress.mdx, docs/pages/reference/data-apis/src/functions/getNftsForOwnerPages.mdx, packages/data-apis/src/actions/portfolio/getTokenBalancesByAddress.ts, packages/data-apis/src/internal/paginate.ts, docs/pages/reference/data-apis/src/functions/getCollectionMetadata.mdx, .github/workflows/bump-api-specs.yml, packages/api-codegen/src/schemaWalk.ts, packages/common/src/rest/types.ts, packages/data-apis/src/internal/clientHelpers.ts, packages/api-codegen/tests/schemaEmitter.test.ts, packages/data-apis/src/internal/errors.test.ts, packages/data-apis/src/actions/token/token.test.ts, packages/api-codegen/src/rpc/rpcEmitter.ts, packages/common/src/errors/AlchemyApiError.ts, packages/data-apis/src/generated/rest/portfolio.schema.ts, packages/data-apis/src/index.test-d.ts, docs/pages/reference/data-apis/src/functions/getContractMetadata.mdx, packages/data-apis/src/generated/rpc/token.ts, packages/data-apis/src/actions/prices/prices.test.ts, packages/data-apis/src/generated/rest/prices.schema.ts, docs/pages/reference/common/src/classes/AlchemyRestClient.mdx, packages/data-apis/src/internal/paginate.test.ts, docs/pages/reference/common/src/classes/FetchError.mdx, docs/pages/reference/common/src/classes/ServerError.mdx, docs/pages/reference/common/src/classes/AlchemyApiError.mdx, docs/pages/reference/data-apis/src/type-aliases/NftRestSchema.mdx, packages/data-apis/src/index.ts, docs/pages/reference/data-apis/src/functions/getNftMetadata.mdx, packages/api-codegen/specs/portfolio.json, packages/api-codegen/src/rest/schemaEmitter.ts, packages/data-apis/scripts/smoke-test.ts, packages/data-apis/src/generated/rest/portfolio.types.ts, packages/data-apis/src/actions/nft/nft.test.ts, packages/data-apis/codegen.manifest.ts, packages/common/tests/rest/restClient.test.ts, packages/data-apis/README.md, packages/common/src/rest/restClient.ts, docs/pages/reference/data-apis/src/type-aliases/DataActions.mdx, packages/data-apis/src/types.ts, packages/api-codegen/specs/token.json, packages/data-apis/src/generated/rest/nft.schema.ts, packages/data-apis/src/decorator.ts, packages/data-apis/src/generated/rest/prices.types.ts, docs/docs.yml, packages/api-codegen/specs/prices.json, docs/pages/reference/common/src/README.mdx, docs/pages/reference/data-apis/src/README.mdx

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

blakecduncan and others added 8 commits June 10, 2026 10:19
…n specs

Snapshots now pinned to alchemyplatform/docs main (becfd714) instead of a
feature branch. prices (REST) and token (OpenRPC) enter the manifest so
generation covers all five v1 spec sources; actions land in follow-ups.
RPC emitter now strips non-root schema titles before compilation —
title-named shared subschemas (e.g. token's Hex Encoded Address) otherwise
hoist duplicate identifiers when one spec has multiple methods.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…est-id)

- RestRequestSchema gains an optional Query channel; RestRequestParams maps
  it via a keyof guard so legacy entries without Query keep compiling and
  reject query payloads. Route-literal Response narrowing unchanged.
- Bounded retries with exponential backoff on 429/5xx/network failures only,
  honoring Retry-After; per-attempt AbortSignal.timeout merged with the
  caller's signal (caller aborts are never retried); per-request UUID sent as
  X-Alchemy-Client-Request-Id on every attempt.
- New AlchemyApiError base (status/code/requestId/retryAfter) under
  BaseError; ServerError/FetchError re-parented onto it with an additive
  trailing details param — existing instanceof checks and constructor calls
  are unaffected.
- composeSignals/sleep utils; 11 new rest client tests (query serialization,
  retry/backoff/Retry-After, abort, request-id propagation, error fields).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…recated ops

- REST tuples now carry the Query channel (required when any query param is
  required); standalone Query alias emitted whenever query params exist.
- Manifest gains optional pagination metadata per operation/method
  (pageParam / responseCursorField / itemsField, dotted paths for nested
  cursors); validated against snapshots at generate time — drift in cursor
  fields fails generation. Consumed by hand-written *Pages actions; emits no
  runtime code.
- Referencing a spec-deprecated operation hard-fails generation.
- NFT action drops the URLSearchParams route cast: typed query channel +
  abort signal via the hardened AlchemyRestClient. Actions accept an
  optional RequestOptions third arg.
- Snapshot lockfile labels detached-HEAD checkouts '(detached)'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- portfolio: + getTokenBalancesByAddress, getNftsByAddress,
  getNftContractsByAddress (global Data API, multi-network bodies;
  per-entry fields like address-level filters preserved, network-slug
  enum deliberately widened for the escape hatch)
- prices: getTokenPricesBySymbol (chain-agnostic GET),
  getTokenPricesByAddress (per-entry network resolution),
  getHistoricalTokenPrices (symbol or network+address forms)
- nft: full v3 read surface — 21 actions (ownership/contract/collection
  listings, metadata + batch, owners, sales, floor price, search, spam/
  airdrop/rarity checks). Mutations and deprecated/v2 ops excluded per
  scope plan. Bracketed wire keys (contractAddresses[], *Filters[])
  exposed unbracketed in params and restored by the actions.
- token: getTokenBalances (positional optionals trimmed), getTokenMetadata,
  getTokenAllowance over a shared getRpcRequest helper (also adopted by
  transfers.getAssetTransfers)
- DataRpcSchema now aggregates transfers + token entries; REST actions all
  accept an options arg with an abort signal
- 29 new wire-level tests (URL/method/body/query assertions per namespace)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- paginate(): shared async-generator driver yielding whole pages; stops on
  missing/empty cursors, throws on a repeated cursor (infinite-loop guard),
  supports AbortSignal + maxPages
- 9 *Pages companion actions for every paginated method (nft owner/contract/
  collection listings + sales, portfolio nfts/contracts by address,
  transfers), wired into the decorator namespaces; cursor wiring (pageKey vs
  startToken→pageKey/nextToken, body vs query vs rpc-param) follows the
  manifest's validated pagination metadata
- wrapRpcError(): JSON-RPC failures normalize into AlchemyApiError (code/
  status), with /v2/<key> and apiKey query credentials redacted from any
  URL-bearing text — keys never leak through error chains. REST is already
  normalized inside AlchemyRestClient.
- 10 new tests (cursor walking, repeat-cursor guard, maxPages, consumer
  break, abort, redaction, error mapping) + a decorator-level paging test

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- version 5.0.3-alpha.0 with publishConfig.tag 'alpha'; deliberately NOT in
  lerna.json's fixed-version publish set so the regular release can't ship
  it as latest — alpha publishing + graduation checklist documented in the
  README
- typedoc: data-apis entry point + tsconfig.typedoc include + nav
  registration in generate-typedoc-yaml.ts; generated reference docs
  committed under docs/pages/reference/data-apis (CI's docs:sdk drift check
  now covers the package)
- README rewritten: install, both entry points, per-namespace quickstart,
  three network formats, pagination, error semantics, release process

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- on-pull-request: 'pnpm generate && git diff --exit-code' over generated
  output and spec snapshots, mirroring the existing docs:sdk drift check —
  fails if generation is stale or snapshots were hand-edited (offline, no
  new secrets)
- bump-api-specs (workflow_dispatch): checks out the public docs repo at
  main, re-snapshots, regenerates, and opens a PR via create-pull-request;
  manifest validation hard-fails the run when an SDK-referenced operation
  was renamed/removed upstream

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- index.test-d.ts: 7 vitest typecheck tests (client/decorator surface
  equivalence, AlchemyTransport constraint enforcement, spec-accurate result
  shapes, three-format network params, Pages generator types); data-apis
  vitest config now enables typecheck under TYPECHECK=true like the shared
  config
- exports.test.ts: locks the package's runtime export surface; generated
  internals barrel stays internal
- smoke test grown 14 → 30 live tests: every namespace exercised against
  the real API (portfolio x3 new, prices x3, nft x5 new, token x3) plus
  two-page pagination walks for nft and transfers companions — all passing

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@blakecduncan

Copy link
Copy Markdown
Collaborator Author

Superseded by #2541, which contains this branch's full surface plus the dependency-free core inversion approved at eng tech review (decision doc). All 31 methods, the codegen pipeline, pagination, and the 30-test live smoke suite carry over — verified green on the inverted core.

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.

1 participant