@hasna/domains
Version:
Domain portfolio, registrar, marketplace, and DNS management for AI agents — CLI + MCP + HTTP API + SDK, shared account API and PostgreSQL
184 lines • 9.78 kB
TypeScript
/** An environment as the client resolver reads it. */
export type ClientEnv = Record<string, string | undefined>;
/** The env-key spec for one app, in precedence order. */
export interface ClientTransportEnvKeys {
/** API base-URL keys, in precedence order. */
apiUrlKeys: string[];
/** API-key keys, in precedence order. */
apiKeyKeys: string[];
}
/** Which tier of the credential chain supplied a key. */
export type CredentialTier = "argument" | "override" | "pointer" | "profile" | "keychain" | "disk" | "env";
/**
* A credential resolved from one tier of the chain.
*
* `apiKey` is non-enumerable and redacted by a custom-inspect hook in the
* resolver, so spreading or serializing a resolution drops it. That is a
* runtime property of the value, not something a type can carry.
*/
export interface ResolvedCredential {
readonly apiKey: string;
readonly tier: CredentialTier;
/** An env key NAME, an absolute file path, or `keychain:<service>@<account>`. Never a value. */
readonly source: string;
/** True for tiers an operator sets on purpose. These never fall through. */
readonly deliberate: boolean;
/** For `tier === "pointer"`, the vault ITEM KEY to resolve at request time. Never a value. */
readonly pointerVaultKey?: string;
/** The disk paths consulted before this credential was chosen. */
readonly diskCandidates: readonly string[];
/** Human-readable advisory. Never contains key material. */
readonly warning: string | null;
}
/**
* A per-request credential source.
*
* Prefer this over a bare string for a long-lived client: the transport calls
* it fresh for every request, so a key rotation heals without a rebuild.
*/
export type CredentialProvider = () => ResolvedCredential;
/** The captured outcome of one `security` invocation. `stdout` IS the secret. */
export interface KeychainCommandResult {
/** Exit status; null when the tool could not be started or was killed. */
status: number | null;
stdout: string;
stderr: string;
}
/** Runs `/usr/bin/security` with the given argv — no shell. Injected by tests. */
export type KeychainCommandRunner = (argv: readonly string[]) => KeychainCommandResult;
/** Keychain-tier controls. Every field is optional; production callers pass nothing. */
export interface KeychainTierOptions {
/**
* Whether the Keychain is consulted for a caller-built env object. The tier is
* AMBIENT: by default it runs only for the live `process.env`. Injecting `run`
* implies `true`.
*/
enabled?: boolean;
/** Defaults to `process.platform`; the tier exists only on `"darwin"`. */
platform?: string;
/** The machine's host name (label before the first dot), used when `HASNA_STATION` is unset. */
hostname?: () => string;
/** The `security` runner. Defaults to spawning `/usr/bin/security` by argv. */
run?: KeychainCommandRunner;
}
/** Tier-1 credential inputs (`--api-key` / `--profile`) plus the Keychain-tier seam. */
export interface CredentialChainOptions {
/** Tier 1: an explicit key, e.g. from `--api-key`. */
apiKey?: string;
/** Tier 1: an explicit profile name, e.g. from `--profile`. Beats `HASNA_PROFILE`. */
profile?: string;
/** Tier 3: Keychain controls — a fake `security` runner in tests, an opt-out on CI. */
keychain?: KeychainTierOptions;
}
/** Where a client reads and writes. There is one: the authenticated service API. */
export type ClientTransportKind = "http";
/** The transport decision, with every source named and no key value in it. */
export interface ClientTransportResolution {
/** Where the client should read/write from. */
transport: ClientTransportKind;
/** What selected the transport: an env key NAME, a Keychain item reference, a file PATH, or `"default"`. */
transportSource: string;
/** `<origin>/v1` base for the server API. */
baseUrl: string;
/** WHERE the API URL came from: an env key NAME, a Keychain item reference, a file PATH, `"default"`, or null. */
apiUrlSource: string | null;
/** Whether an API key is present (value never exposed). */
apiKeyPresent: boolean;
/** WHERE the API key came from: an env key NAME or an absolute file path. Never the value. */
apiKeySource: string | null;
/** Which tier of the credential chain supplied the key. */
apiKeyTier: CredentialTier;
/** Kept for diagnostic shape compatibility; a successful resolution is never misconfigured. */
misconfigured: boolean;
/** Human-readable warning, or null. Never contains secret values. */
warning: string | null;
}
/** Query string params appended to a request URL. */
export type QueryParams = URLSearchParams | Record<string, string | number | boolean | null | undefined | ReadonlyArray<string | number | boolean>>;
/** Retry policy for transient failures (network errors, timeouts, 5xx, 429). */
export interface HasnaRetryOptions {
/** Max RETRY attempts after the first try. Default 2 (=> up to 3 total tries). */
retries?: number;
/** Base backoff in ms for exponential backoff. Default 200. */
baseDelayMs?: number;
/** Backoff ceiling in ms. Default 2000. */
maxDelayMs?: number;
/** HTTP statuses that trigger a retry. Default 408, 425, 429, 500, 502, 503, 504. */
retryStatuses?: number[];
}
/** Per-call request options: query, idempotency, timeout, retry, extra headers. */
export interface HasnaRequestOptions {
/** Query string params appended to the URL. */
query?: QueryParams;
/** Idempotency key sent as `Idempotency-Key`, making an unsafe method safe to retry. */
idempotencyKey?: string;
/** Override the transport timeout for this call (ms). */
timeoutMs?: number;
/** Extra headers merged into this call (override transport headers). */
headers?: Record<string, string>;
/** Override or disable retry for this call. `false` disables retries. */
retry?: HasnaRetryOptions | false;
/** Caller abort signal, combined with the internal timeout. */
signal?: AbortSignal;
}
/** The authenticated HTTP transport. The API key lives inside it and is never returned. */
export interface HasnaHttpTransport {
readonly baseUrl: string;
request<T = unknown>(method: string, path: string, body?: unknown, opts?: HasnaRequestOptions): Promise<T>;
get<T = unknown>(path: string, opts?: HasnaRequestOptions): Promise<T>;
post<T = unknown>(path: string, body?: unknown, opts?: HasnaRequestOptions): Promise<T>;
put<T = unknown>(path: string, body?: unknown, opts?: HasnaRequestOptions): Promise<T>;
patch<T = unknown>(path: string, body?: unknown, opts?: HasnaRequestOptions): Promise<T>;
del<T = unknown>(path: string, body?: unknown, opts?: HasnaRequestOptions): Promise<T>;
}
/** Options for a `list()` call: filters/pagination as query params. */
export interface StorageListOptions extends Pick<HasnaRequestOptions, "timeoutMs" | "headers" | "retry" | "signal"> {
/** Query params (limit, offset, cursor, filters, ...). */
query?: QueryParams;
}
/** Options for a `get()` call. */
export type StorageGetOptions = Pick<HasnaRequestOptions, "timeoutMs" | "headers" | "retry" | "signal" | "query">;
/** Options for a `create()` call. */
export interface StorageCreateOptions extends Pick<HasnaRequestOptions, "timeoutMs" | "headers" | "retry" | "signal" | "query"> {
/** Defaults to a fresh UUID so a retried POST is deduped rather than duplicated. */
idempotencyKey?: string;
}
/** Options for an `update()` call. */
export interface StorageUpdateOptions extends Pick<HasnaRequestOptions, "timeoutMs" | "headers" | "retry" | "signal" | "query"> {
/** HTTP verb for the update. Default `PATCH` (partial); use `PUT` for replace. */
method?: "PATCH" | "PUT";
/** Idempotency key. Set this to make a PATCH retry-safe too. */
idempotencyKey?: string;
}
/** Options for a `delete()` call. */
export type StorageDeleteOptions = Pick<HasnaRequestOptions, "timeoutMs" | "headers" | "retry" | "signal" | "query">;
/** Result of a `list()` call. `items` is the extracted array; `raw` is the full envelope. */
export interface StorageListResult<T> {
items: T[];
/** Total count when the server reports one (`total`/`count`), else null. */
total: number | null;
/** Opaque pagination cursor when the server reports one, else null. */
cursor: string | null;
/** The full parsed response body (envelope preserved). */
raw: unknown;
}
/** The app storage interface exposed by the authenticated service API. */
export interface HasnaStorageClient {
/** App slug this client targets. */
readonly name: string;
/** `<origin>/v1` base URL. */
readonly baseUrl: string;
/** The underlying HTTP transport (escape hatch for non-CRUD routes). */
readonly transport: HasnaHttpTransport;
/** List a collection. Returns extracted `items` plus the raw envelope. */
list<T = unknown>(resource: string, options?: StorageListOptions): Promise<StorageListResult<T>>;
/** Fetch one entity by id. Returns `null` on 404. */
get<T = unknown>(resource: string, id: string, options?: StorageGetOptions): Promise<T | null>;
/** Create one entity. Retry-safe via an auto `Idempotency-Key`. */
create<T = unknown>(resource: string, body: unknown, options?: StorageCreateOptions): Promise<T>;
/** Update one entity by id (PATCH by default). */
update<T = unknown>(resource: string, id: string, patch: unknown, options?: StorageUpdateOptions): Promise<T>;
/** Delete one entity by id. Resolves for 2xx and 404 (already gone). */
delete(resource: string, id: string, options?: StorageDeleteOptions): Promise<void>;
}
//# sourceMappingURL=client-types.d.ts.map