npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aetherwealth/sdk

v0.1.36

Published

Official Aether Wealth SDK: a typed TypeScript client for the Aether Wealth public API — trades, accounts, analytics, alerts, market data, macro calendar, and diary.

Readme

@aetherwealth/sdk

Typed TypeScript client for the Aether Wealth public API. Programmatic access to your trading journal, accounts, analytics, alerts, market data, and diary over the public REST surface (/api/public/v1/…).

Keep your API key secret. Authentication uses a public API key (aw_live_…) sent as a bearer token. Treat it like a password — never commit it or ship it to an untrusted client.

Install

npm install @aetherwealth/sdk

Quickstart

import { AetherClient } from '@aetherwealth/sdk'

const client = new AetherClient({
  auth: { type: 'apiKey', apiKey: process.env.AETHER_API_KEY! },
  // baseUrl defaults to the production API (https://api.aetherwealth.ai).
  // Set it only to target staging or a local dev backend.
})

// Every call is authenticated with your API key.
const { data: openTrades } = await client.trades.list({ status: 'OPEN' })
const stats = await client.stats.summary({ pair: 'EURUSD' })

Authentication

Each request sends your public API key as Authorization: Bearer <apiKey>. The key scopes every operation to its owner — you never pass userId in a request body. auth is a discriminated union (one member today, apiKey) so future auth modes can be added without breaking existing callers.

new AetherClient({
  baseUrl,
  auth: { type: 'apiKey', apiKey },
  fetchImpl,             // optional — inject a custom fetch (tests, proxies)
  userAgent,             // optional
  timeoutMs: 30_000,     // optional — per-request timeout (default 30s; 0 disables)
  validateResponses: false, // optional — opt-in Zod validation of responses
  dangerouslyAllowBrowser: false, // optional — see "Browser use" below
  onRequest, onResponse, // optional diagnostics hooks
})

The diagnostics hooks receive only { method, url, status, ms } — your API key travels solely on the Authorization header and never appears in a hook payload, a thrown error, or the request URL.

Browser use is blocked by default

The API key is a server-side secret. Constructing the client in a browser-like environment (window.document present) throws — a key shipped to a browser is visible in the bundle and DevTools and must be treated as public. Call the API from your server. If you are certain you are in a trusted non-browser runtime that happens to define window, set dangerouslyAllowBrowser: true (mirrors the OpenAI SDK).

Resources

| Resource | Methods | | --- | --- | | client.trades | list · get · create · update · close · delete · pages · listAll | | client.accounts | list · create · update · delete · trades · tradesAll | | client.stats | summary | | client.alerts | list · createPrice · createTrendline · update · delete · listIndicator · createIndicator · updateIndicator · deleteIndicator | | client.market | config · calendar · macroSeries · macro | | client.diary | list · get · upsert · delete · pages · listAll |

List methods return { data, pagination }; single-item methods return the object directly. All response fields are camelCase.

Pagination

pages() and listAll() async-iterate a paginated resource, auto-advancing page until the last page (or an empty page), with an infinite-loop backstop:

// One page at a time (keeps pagination metadata):
for await (const page of client.trades.pages({ status: 'CLOSED', limit: 100 })) {
  console.log(page.pagination.page, page.data.length)
}

// Every item, flattened across all pages:
for await (const trade of client.trades.listAll({ pair: 'EURUSD' })) {
  handle(trade)
}

// Also: client.diary.pages/listAll and client.accounts.tradesAll(accountId)

Iteration starts at query.page if given, else page 1.

Timeouts & cancellation

Every request is bounded by timeoutMs (default 30s), overridable per call. A timeout aborts the request and rejects with AetherTimeoutError (a subclass of AetherNetworkError, so it's retryable and caught by existing network-error handlers). You can also pass your own AbortSignal — the request aborts when either the timeout or your signal fires:

// Client-wide default (30s) or a per-request override via the low-level request():
const slow = new AetherClient({ baseUrl, auth, timeoutMs: 60_000 })
await client.request('/api/public/v1/trades/list', { method: 'POST', body: {}, timeoutMs: 5_000 })

// Caller cancellation (e.g. a UI "cancel" button or request budget):
const controller = new AbortController()
setTimeout(() => controller.abort(new Error('cancelled')), 1_000)
await client.request('/api/public/v1/stats/stats', { method: 'POST', body: {}, signal: controller.signal })

A timeout throws AetherTimeoutError; a caller-initiated abort surfaces your signal's own reason (it is not treated as a transient error to retry).

Idempotency & safe retries

Each create — trades.create, accounts.create, alerts.createPrice, alerts.createTrendline, alerts.createIndicator — sends an Idempotency-Key header so a create that the server committed but whose response you never saw won't duplicate on a re-send. If you don't pass one, the SDK generates a fresh key per call:

await client.trades.create(input) // auto Idempotency-Key, protects this one call

An auto per-call key protects a single invocation. It does not make a create retry-safe: a new key each attempt means the backend can't dedup. To retry a create safely, pass a stable key so every attempt targets the same record:

import { withRetry } from '@aetherwealth/sdk'

const idempotencyKey = crypto.randomUUID() // generated ONCE, outside the retry
const trade = await withRetry(
  () => client.trades.create(input, { idempotencyKey }),
  { maxAttempts: 3 },
)

Do not generate the key inside the retried closure — each attempt would get a different key and dedup would be lost. withRetry only retries transient failures (AetherRateLimitError, AetherNetworkError, AetherTimeoutError); retrying a mutation is only safe with a stable key.

Errors

Non-2xx responses (and { success: false } envelopes) throw a typed AetherApiError subclass so you can branch on the failure mode:

import {
  AetherRateLimitError,
  AetherNotFoundError,
  AetherAuthError,
  AetherTimeoutError,
} from '@aetherwealth/sdk'

try {
  await client.trades.get(id)
} catch (err) {
  if (err instanceof AetherNotFoundError) { /* 404 */ }
  else if (err instanceof AetherRateLimitError) { /* 429 — err.retryAfterSeconds */ }
  else if (err instanceof AetherAuthError) { /* 401 — invalid or missing API key */ }
  else if (err instanceof AetherTimeoutError) { /* client timeout — err.timeoutMs / err.elapsedMs */ }
  else throw err
}

AetherTimeoutError and AetherNetworkError (its parent) never reached the server; they carry no HTTP status. All HTTP failures are AetherApiError subclasses.

Optional runtime validation

Compile-time types come from the SDK. Runtime validation is off by default for performance and additive-field tolerance (a backend that adds a field won't break older SDK consumers). Turn it on two ways:

Whole-client — every resource validates its response with a Zod parser and throws a ZodError on shape drift (unknown fields are still tolerated):

const client = new AetherClient({ baseUrl, auth, validateResponses: true })

Per-call — import a parser and validate a single response:

import { parseTrade } from '@aetherwealth/sdk'
const trade = parseTrade(await client.trades.get(id)) // throws on shape drift

Not covered

Live candles and AI chat conversations are streamed/tRPC-only on the backend and are intentionally not part of this REST SDK — reach them through @aetherwealth/client-core (used by the CLI and MCP server). API-key management is likewise out of scope: an API key can't provision other keys.