src/transport/envelope.ts
v0.2.0 · 2 KB
// Envelope parsing: `{status:'success', data, request_type}` | `{status:'failure', error, error_code}`.
import { NadoEnvelopeError, NadoRejection } from './errors.js';
import type { Envelope } from './types.js';
/** Result of {@link parseEnvelope}. */
export type ParsedEnvelope<T = unknown> =
| { readonly ok: true; readonly data: T; readonly requestType: string | undefined }
| { readonly ok: false; readonly code: number; readonly error: string; readonly requestType: string | undefined };
/**
* Classifies a parsed JSON body. Any other shape (no `status`, not an object) is a transport failure
* with an unknown outcome, never "the venue said no".
*
* @throws NadoEnvelopeError when the body is not an envelope.
*/
export function parseEnvelope<T = unknown>(body: unknown, requestType?: string): ParsedEnvelope<T> {
if (body === null || typeof body !== 'object' || Array.isArray(body))
throw new NadoEnvelopeError({ body, requestType });
const env = body as Partial<Envelope<T>> & { request_type?: unknown; error?: unknown; error_code?: unknown };
const rt = typeof env.request_type === 'string' ? env.request_type : requestType;
if (env.status === 'success') return { ok: true, data: env.data as T, requestType: rt };
if (env.status === 'failure') {
const raw = Number(env.error_code);
return {
ok: false,
code: Number.isFinite(raw) ? raw : -1,
error: typeof env.error === 'string' ? env.error : String(env.error ?? 'unknown error'),
requestType: rt,
};
}
throw new NadoEnvelopeError({ body, requestType });
}
/**
* Unwraps an envelope: returns `data` on success, throws {@link NadoRejection} on failure.
*
* @throws NadoRejection | NadoEnvelopeError
*/
export function unwrapEnvelope<T = unknown>(body: unknown, path: 'query' | 'execute', requestType?: string): T {
const parsed = parseEnvelope<T>(body, requestType);
if (parsed.ok) return parsed.data;
throw new NadoRejection({ code: parsed.code, error: parsed.error, requestType: parsed.requestType, path });
}