This file orients Claude Code, Codex, Cursor, and any other AI coding agent working in @viz-cx/api.
@viz-cx/api is a type-safe TypeScript SDK for the VIZ site API (api.viz.cx). It provides a createApiClient() factory that exposes REST getters (blocks, profiles, richlist, chain info), a nonce-based signed-request auth helper, webhooks CRUD, and a reconnecting WebSocket op-stream client (streamOps) that doubles as an AsyncIterable. The package is dual ESM+CJS, has no runtime dependencies, and exposes an optional @viz-cx/core peer dependency via the ./core-signer subpath for batteries-included ECDSA signing.
| Goal | Command |
|---|---|
| Install | pnpm install --frozen-lockfile |
| Build (dual ESM+CJS via tsup) | pnpm build |
| Unit tests | pnpm test |
| Type-level tests (tsd) | pnpm build && pnpm test:types (build first — tsd reads dist/index.d.ts) |
| Lint | pnpm lint |
| Typecheck | pnpm lint:types |
| Exports sanity (arethetypeswrong) | pnpm lint:exports |
| OpenAPI drift guard | pnpm drift |
| Tarball size budget | pnpm size |
| Smoke test against live API | pnpm smoke |
| Pre-publish gate | pnpm prepublishOnly |
Use pnpm, not npm — lockfile is pnpm-lock.yaml. pnpm test:types requires dist/ — always run pnpm build first. CI matrix does this.
src/
index.ts public exports (the published API surface)
client.ts createApiClient(), ApiClient interface
rest.ts REST transport: getChainInfo, getBlock, getProfile, getRichlist, avatarUrl
auth.ts nonce fetch, signed-request helper (authedFetch), Signer resolution
webhooks.ts webhooks.create / list / delete — camel↔snake at wire boundary
ws.ts createStreamOps(), OpStream (reconnecting WebSocket + AsyncIterable)
config.ts ApiClientOptions, normalizeApiOptions, DEFAULT_API_BASE, DEFAULT_WS_URL
errors.ts VizApiError, VizApiHttpError, VizApiAuthError, VizApiTransportError
types.ts RawOp, OpRecord, BlockDoc, ChainInfo, RichlistRow, Richlist, Profile,
WebhookFilter, WebhookCreated, WebhookRow, OpStreamMessage, SignFn, Signer
core-signer.ts withCoreSigner() — wraps @viz-cx/core keys.sign (optional peer dep)
version.ts VERSION constant (re-exported) — GENERATED, gitignored (see scripts/gen-version.mjs)
scripts/
gen-version.mjs writes src/version.ts from package.json (runs on prepare + build)
check-openapi-drift.mjs fetches live OpenAPI spec, diffs against src/types.ts
check-tarball-size.mjs pnpm pack → asserts size < 50 KB
smoke-test.mjs live read against api.viz.cx (requires network)
test/
unit/ vitest unit tests — one file per src module
types/ tsd type-level assertions (require dist/)
integration/ live-API integration tests (gated, off by default)
- No runtime dependencies.
@viz-cx/coreis an optionalpeerDependencyaccessed only via the./core-signersubpath. Don't adddependencieswithout strong justification — the 50 KB tarball budget is enforced bypnpm size. - camelCase in source, snake_case at the wire boundary.
webhooks.tsandrest.tstranslate between camelCase TypeScript types and snake_case JSON. Do not leak snake_case field names into public types or method signatures. - Signing format.
SignFnreceives aUint8Array(the nonce bytes) and must return a hex-encoded 65-byte recoverable ECDSA signature oversha256(nonceBytes).withCoreSignerfrom./core-signerhandles this via@viz-cx/core'skeys.sign. - WebSocket is injectable.
createApiClient({ WebSocket })accepts any constructor compatible with the browserWebSocketinterface. Node.js users injectwsthis way; browser users rely on the global. streamOpsis anAsyncIterable. Breaking thefor awaitloop automatically callsstream.close()via the iterator'sreturn()method. Don't remove that hook.@viz-cx/coreis a peer dep, not a direct dep. The./core-signersubpath imports from@viz-cx/core. This import must never appear anywhere else in the source tree.src/version.tsis generated, never hand-edited.scripts/gen-version.mjswrites it frompackage.json'sversion; it is gitignored and regenerated onprepare(post-install) andbuild. To change the version, bumppackage.json(via Changesets) — never editversion.tsdirectly.
- Versioning via Changesets. To ship a change:
pnpm changeset(or hand-write.changeset/<slug>.md) → push. changesets/action@v1opens a "Version Packages" PR that bumpspackage.json+CHANGELOG.mdand deletes consumed changesets.- Merging that PR triggers
release.yml, which compares local version vs npm and publishes the delta via npm Trusted Publisher (OIDC) — noNPM_TOKENinvolved. - Required workflow perms:
id-token: write,contents: write,pull-requests: write.
- Running
npm install(instead ofpnpm install) writes apackage-lock.jsonand corrupts the dep graph. If it ever appears, delete it and re-runpnpm install --lockfile-only. pnpm smokerequires a live network connection toapi.viz.cx— it is not run in offline/isolated environments.pnpm driftfetches the live OpenAPI spec fromapi.viz.cx/openapi.json— same network requirement.- The pnpm lockfile is in pnpm 11 format locally (pnpm 11.2.2); CI pins pnpm 10.34.4. If CI lockfile compatibility breaks, regenerate with
pnpm install --lockfile-onlyunder pnpm 10. pnpm lint:exportsuses--ignore-rules no-resolutionbecausenode10TypeScript resolution predatesexportsmaps and cannot resolve subpath exports (like./core-signer) by definition. Thenode16andbundlerresolution modes are fully checked and green. Remove the flag only if you also remove all subpath exports.