@ariestools/sdk
v8.2.0
Published
All-in-one umbrella for the Aries Tools TypeScript/JavaScript utility libraries
Readme
@ariestools/sdk
All-in-one umbrella for the Aries Tools TypeScript/JavaScript utility libraries.
Import the whole SDK:
import { fetchJson } from '@ariestools/sdk'…or a single slice via a subpath export (tree-shaking-friendly — you only pay for what you import):
import { fetchJson } from '@ariestools/sdk/fetch'
import { assertEx } from '@ariestools/sdk/assert'
import type { ApiConfig } from '@ariestools/sdk/api/model'Fetch transport and HTTP caching
The fetch helpers are runtime-neutral and do not install an HTTP client or cache. By default,
fetchCompress, fetchJson, FetchClient, and fetchJsonClient resolve globalThis.fetch when
they make a request. Consumers can override that behavior with a FetchFunction through the
fetcher option.
Transport resolution is, from highest to lowest precedence:
- A per-request
fetcher. - The
fetcherconfigured on aFetchClientinstance. globalThis.fetchand whatever behavior the host runtime has configured for it.
Browsers normally provide their own HTTP cache. Node's built-in fetch does not enable a response
cache, but a Node application can install a compatible Undici release and configure either a
module-local cached fetcher or the global Undici dispatcher. Undici remains a consumer-owned
dependency; @ariestools/sdk does not depend on it.
Module-local Node cache
Inject an Undici fetcher when one module or client should own the dispatcher and cache:
import { FetchClient, type FetchFunction } from '@ariestools/sdk/fetch'
import {
Agent,
cacheStores,
fetch as undiciFetch,
interceptors,
} from 'undici'
const dispatcher = new Agent().compose(
interceptors.cache({
store: new cacheStores.MemoryCacheStore({
maxSize: 20 * 1024 * 1024,
maxCount: 250,
maxEntrySize: 2 * 1024 * 1024,
}),
type: 'shared',
}),
)
const fetcher: FetchFunction = (input, init) =>
undiciFetch(input, { ...init, dispatcher })
const client = new FetchClient({
baseURL: 'https://api.example.com',
fetcher,
})
await client.get('/data')
// The module that creates a dispatcher owns its lifecycle.
await dispatcher.close()Passing fetcher directly to fetchJson or another convenience function scopes it to that one
call. Supplying a per-request fetcher to FetchClient overrides the client default.
Shared Node default
An application may instead install a cached dispatcher during process bootstrap. Every SDK call
without an injected fetcher then inherits it through Node's built-in globalThis.fetch:
import { fetchJson, fetchJsonClient } from '@ariestools/sdk/fetch'
import {
Agent,
cacheStores,
interceptors,
setGlobalDispatcher,
} from 'undici'
const dispatcher = new Agent().compose(
interceptors.cache({
store: new cacheStores.MemoryCacheStore({
maxSize: 100 * 1024 * 1024,
maxCount: 1_000,
maxEntrySize: 5 * 1024 * 1024,
}),
type: 'shared',
}),
)
setGlobalDispatcher(dispatcher)
await fetchJson('https://api.example.com/data')
await fetchJsonClient.get('https://api.example.com/data')Only a terminal application, such as a service, CLI, or worker entrypoint, should replace the global dispatcher. Imported libraries should inject a local fetcher instead. Install the global dispatcher once, before normal requests, and include any required proxy, TLS, retry, or tracing behavior in the dispatcher being installed.
An in-memory global store is shared only within the current Node isolate. Worker threads and
separate processes must each configure their own dispatcher. Tests that replace the global
dispatcher should retain getGlobalDispatcher(), restore it afterward, and close only the
dispatcher they created.
Cache scope versus HTTP cache type
Cache ownership and Undici's HTTP cache type are independent:
- A local dispatcher limits the store to the module or client that owns its fetcher.
- A global dispatcher shares the store among all uninjected fetch calls in that Node isolate.
type: 'shared'applies shared-cache HTTP rules and is the safe default for a process-wide cache.type: 'private'may cache personalized responses and should be used only when the dispatcher and store are isolated to one identity or credential context.
Do not use a global private cache for unrelated users or tenants. Cache headers and Vary do not
replace an explicit application identity boundary.
Bounded response reads and cancellation
JSON helpers and FetchClient / FetchJsonClient accept an optional
maxResponseBytes, a positive safe integer. It is validated before starting the
request. Omit it for the existing unlimited behavior; a per-request value overrides
the client default, including undefined to remove that default.
import { FetchJsonClient } from '@ariestools/sdk/fetch'
const controller = new AbortController()
const client = new FetchJsonClient({
maxResponseBytes: 1024 * 1024,
timeout: 5000,
redirect: 'error',
credentials: 'omit',
})
const result = await client.get('https://api.example.com/data', {
signal: controller.signal,
})The cap counts the bytes exposed by Response.body, incrementally, before text
decoding or JSON parsing. It applies to successful and non-2xx bodies.
Content-Length is not used as evidence that a body fits. Native Fetch normally
decompresses content encodings before exposing these bytes, so a small compressed
response can still exceed the cap. This does not bound raw network bytes,
headers, buffering inside the transport, decompressor memory, or allocations
already made for an individual chunk. JSON parsing is synchronous and cannot be
interrupted mid-parse; choose an appropriate byte cap for the application's budget.
A caller signal remains active through body consumption. A client timeout
composes with that signal instead of replacing it; the first abort reason wins.
An oversized body rejects with FetchError.type === 'response-too-large';
body cancellation uses 'aborted' or 'timeout' and retains the signal reason as
cause. The reader is cancelled and unlocked on early exit without waiting
indefinitely for the transport's cancellation hook. Injected fetchers must honor
the provided signal while acquiring the response.
Non-2xx malformed JSON still produces data: null. Without a byte cap or signal,
non-2xx read errors retain their historical null behavior. With either policy
option, read errors propagate so cancellation and limits cannot be silently
ignored; validateStatus: null does not disable these checks.
For callers that need text rather than JSON, use the same neutral primitive:
import { readResponseText } from '@ariestools/sdk/fetch'
import type { ReadResponseTextOptions } from '@ariestools/sdk/fetch/model'
const options: ReadResponseTextOptions = {
maxResponseBytes: 1024 * 1024,
signal: controller.signal,
}
const response = await fetch('https://api.example.com/data', {
signal: options.signal,
redirect: 'error',
credentials: 'omit',
})
const text = await readResponseText(response, options)readResponseText consumes the response once and never returns a truncated
success. It requires native Fetch/Web Streams and TextDecoder; client timeout
support also requires AbortSignal.timeout. These APIs are available in the
SDK's supported Node baseline (18.17.1+) and modern browsers; no HTTP dependency
or global dispatcher is installed by the SDK.
Redirects, credentials, destination validation, and fetcher behavior remain
application choices. Set native redirect: 'error' and credentials: 'omit'
explicitly when required; validate allowed destinations before making requests.
The SDK does not add an SSRF policy, strip explicit authorization headers, or
change the existing fetcher precedence.
Monolithic layout
All barreled library code lives in a single source tree under src/modules/. Cross-module
imports use package.json imports aliases (for example #zod, #api, #fetch). Top-level
src/*.ts files are compile entry shims that produce the published subpath exports in dist/.
Tests live under src/spec/ only (not inside src/modules/).
Module membership, import aliases, build entries, and shims are declared in xy.config.ts as
sdkModules (intended to become first-class @ariestools/toolchain support). Generated files are
synced by scripts/sync-sdk-layout.mjs (runs automatically before compile). After editing
sdkModules, build or sync explicitly:
pnpm xy build @ariestools/sdk
# or: pnpm sync-sdk-layoutRuntime notes:
async-mutexis a direct dependency.zodis an optional peer (only if you use zod helpers).@opentelemetry/apiis a required peer while telemetry is still re-exported from this package. Prefer@ariestools/telemetryfor new code; the@ariestools/sdk/@ariestools/sdk/telemetryre-exports are deprecated and will be removed from the main barrel in a future major release.
Specialist packages (@ariestools/express, @ariestools/telemetry,
@ariestools/threads, etc.) remain separate installs.
