@deepraxis/sdk 0.0.0-stage → 0.1.0-dev.ea77ce8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/LICENSE +202 -0
  2. package/METHODS.md +1126 -0
  3. package/README.md +279 -2
  4. package/dist/automation.d.ts +151 -0
  5. package/dist/automation.js +8 -0
  6. package/dist/core/bindings.d.ts +25 -0
  7. package/dist/core/bindings.js +45 -0
  8. package/dist/core/client.d.ts +15 -0
  9. package/dist/core/client.js +15 -0
  10. package/dist/core/credentials.d.ts +14 -0
  11. package/dist/core/credentials.js +33 -0
  12. package/dist/core/errors.d.ts +38 -0
  13. package/dist/core/errors.js +60 -0
  14. package/dist/core/multipart.d.ts +2 -0
  15. package/dist/core/multipart.js +25 -0
  16. package/dist/core/pagination.d.ts +12 -0
  17. package/dist/core/pagination.js +26 -0
  18. package/dist/core/retry.d.ts +13 -0
  19. package/dist/core/retry.js +56 -0
  20. package/dist/core/sse.d.ts +13 -0
  21. package/dist/core/sse.js +67 -0
  22. package/dist/core/transport.d.ts +36 -0
  23. package/dist/core/transport.js +121 -0
  24. package/dist/generated/client/client.gen.d.ts +2 -0
  25. package/dist/generated/client/client.gen.js +216 -0
  26. package/dist/generated/client/index.d.ts +10 -0
  27. package/dist/generated/client/index.js +6 -0
  28. package/dist/generated/client/types.gen.d.ts +120 -0
  29. package/dist/generated/client/types.gen.js +2 -0
  30. package/dist/generated/client/utils.gen.d.ts +37 -0
  31. package/dist/generated/client/utils.gen.js +228 -0
  32. package/dist/generated/client.gen.d.ts +12 -0
  33. package/dist/generated/client.gen.js +3 -0
  34. package/dist/generated/core/auth.gen.d.ts +25 -0
  35. package/dist/generated/core/auth.gen.js +14 -0
  36. package/dist/generated/core/bodySerializer.gen.d.ts +25 -0
  37. package/dist/generated/core/bodySerializer.gen.js +57 -0
  38. package/dist/generated/core/params.gen.d.ts +43 -0
  39. package/dist/generated/core/params.gen.js +109 -0
  40. package/dist/generated/core/pathSerializer.gen.d.ts +33 -0
  41. package/dist/generated/core/pathSerializer.gen.js +106 -0
  42. package/dist/generated/core/queryKeySerializer.gen.d.ts +18 -0
  43. package/dist/generated/core/queryKeySerializer.gen.js +92 -0
  44. package/dist/generated/core/serverSentEvents.gen.d.ts +71 -0
  45. package/dist/generated/core/serverSentEvents.gen.js +132 -0
  46. package/dist/generated/core/types.gen.d.ts +83 -0
  47. package/dist/generated/core/types.gen.js +2 -0
  48. package/dist/generated/core/utils.gen.d.ts +19 -0
  49. package/dist/generated/core/utils.gen.js +87 -0
  50. package/dist/generated/index.d.ts +2 -0
  51. package/dist/generated/index.js +2 -0
  52. package/dist/generated/sdk.gen.d.ts +2172 -0
  53. package/dist/generated/sdk.gen.js +6579 -0
  54. package/dist/generated/types.gen.d.ts +56802 -0
  55. package/dist/generated/types.gen.js +2 -0
  56. package/dist/helpers/application-publish.d.ts +41 -0
  57. package/dist/helpers/application-publish.js +116 -0
  58. package/dist/index.d.ts +9 -0
  59. package/dist/index.js +5 -0
  60. package/dist/operations.generated.d.ts +10965 -0
  61. package/dist/operations.generated.js +13837 -0
  62. package/dist/resources.generated.d.ts +3852 -0
  63. package/dist/resources.generated.js +724 -0
  64. package/package.json +50 -4
@@ -0,0 +1,60 @@
1
+ /** Pure normalization shared by HTTP clients and the automation call ledger.
2
+ * Numeric transport codes (E2B's `code: 400`) are not domain error codes.
3
+ */
4
+ export function problemFrom(status, headers, body, statusText) {
5
+ const value = body && typeof body === 'object' ? body : {};
6
+ const nested = value.error && typeof value.error === 'object' ? value.error : {};
7
+ const string = (v) => typeof v === 'string' && v.trim() ? v : undefined;
8
+ const fallback = statusText || { 400: 'Bad Request', 401: 'Unauthorized', 403: 'Forbidden', 404: 'Not Found', 405: 'Method Not Allowed', 408: 'Request Timeout', 409: 'Conflict', 413: 'Payload Too Large', 415: 'Unsupported Media Type', 422: 'Unprocessable Entity', 429: 'Too Many Requests', 500: 'Internal Server Error', 502: 'Bad Gateway', 503: 'Service Unavailable', 504: 'Gateway Timeout' }[status] || `HTTP ${status}`;
9
+ const detail = string(value.detail) ?? string(nested.message) ?? string(value.message) ?? string(body);
10
+ const title = string(value.title) ?? fallback;
11
+ const param = nested.param ?? value.param;
12
+ return {
13
+ status, message: detail ?? title, title, detail,
14
+ code: string(value.code) ?? string(nested.code) ?? string(nested.type) ?? string(value.error_code),
15
+ type: string(nested.type) ?? string(value.type),
16
+ param: typeof param === 'string' ? param : param === null || nested.param === null ? null : undefined,
17
+ errors: Array.isArray(value.errors) ? value.errors : [],
18
+ requestId: headers.get('x-request-id') ?? string(value.requestId) ?? string(value.request_id),
19
+ raw: body,
20
+ };
21
+ }
22
+ export class PraxisError extends Error {
23
+ status;
24
+ code;
25
+ type;
26
+ title;
27
+ detail;
28
+ errors;
29
+ requestId;
30
+ param;
31
+ raw;
32
+ constructor(problem = {}, options) {
33
+ super(problem.message ?? problem.detail ?? problem.title ?? `Praxis request failed${problem.status ? ` (${problem.status})` : ''}`, options);
34
+ this.name = 'PraxisError';
35
+ this.status = problem.status;
36
+ this.code = problem.code;
37
+ this.type = problem.type;
38
+ this.title = problem.title;
39
+ this.detail = problem.detail;
40
+ this.errors = problem.errors ?? [];
41
+ this.requestId = problem.requestId;
42
+ this.param = problem.param;
43
+ this.raw = problem.raw;
44
+ }
45
+ static fromResponse(response, body) {
46
+ return new PraxisError(problemFrom(response.status, response.headers, body, response.statusText));
47
+ }
48
+ }
49
+ export class PraxisConnectionError extends PraxisError {
50
+ constructor(cause) {
51
+ super({ code: 'CONNECTION_ERROR', title: 'Unable to connect to Praxis' }, { cause });
52
+ this.name = 'PraxisConnectionError';
53
+ }
54
+ }
55
+ export class PraxisTimeoutError extends PraxisError {
56
+ constructor(timeoutMs) {
57
+ super({ code: 'TIMEOUT', title: `Praxis request exceeded ${timeoutMs} ms` });
58
+ this.name = 'PraxisTimeoutError';
59
+ }
60
+ }
@@ -0,0 +1,2 @@
1
+ /** Expand plain objects into bracketed fields; arrays repeat the same field name. */
2
+ export declare function multipartBodySerializer(body: unknown): FormData;
@@ -0,0 +1,25 @@
1
+ /** Expand plain objects into bracketed fields; arrays repeat the same field name. */
2
+ export function multipartBodySerializer(body) {
3
+ const data = new FormData();
4
+ function append(key, value) {
5
+ if (value === undefined || value === null)
6
+ return;
7
+ if (value instanceof Blob) {
8
+ data.append(key, value);
9
+ }
10
+ else if (Array.isArray(value)) {
11
+ for (const item of value)
12
+ append(key, item);
13
+ }
14
+ else if (typeof value === 'object' && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)) {
15
+ for (const [subkey, item] of Object.entries(value))
16
+ append(`${key}[${subkey}]`, item);
17
+ }
18
+ else {
19
+ data.append(key, String(value));
20
+ }
21
+ }
22
+ for (const [key, value] of Object.entries((body ?? {})))
23
+ append(key, value);
24
+ return data;
25
+ }
@@ -0,0 +1,12 @@
1
+ export interface PageData<T> {
2
+ data: T[];
3
+ next_cursor?: string | null;
4
+ }
5
+ export declare class Page<T> implements AsyncIterable<T> {
6
+ private readonly load;
7
+ readonly data: T[];
8
+ readonly next_cursor: string | null;
9
+ constructor(value: PageData<T>, load: (cursor: string) => Promise<Page<T>>);
10
+ nextPage(): Promise<Page<T> | null>;
11
+ [Symbol.asyncIterator](): AsyncGenerator<T>;
12
+ }
@@ -0,0 +1,26 @@
1
+ export class Page {
2
+ load;
3
+ data;
4
+ next_cursor;
5
+ constructor(value, load) {
6
+ this.load = load;
7
+ this.data = value.data;
8
+ this.next_cursor = value.next_cursor ?? null;
9
+ }
10
+ nextPage() {
11
+ return this.next_cursor ? this.load(this.next_cursor) : Promise.resolve(null);
12
+ }
13
+ async *[Symbol.asyncIterator]() {
14
+ let page = this;
15
+ const seen = new Set();
16
+ while (page) {
17
+ yield* page.data;
18
+ if (page.next_cursor) {
19
+ if (seen.has(page.next_cursor))
20
+ throw new Error('Praxis pagination repeated a cursor');
21
+ seen.add(page.next_cursor);
22
+ }
23
+ page = await page.nextPage();
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,13 @@
1
+ export declare function canRetry(request: Request): boolean;
2
+ export declare function retryAfterMs(value: string | null, now?: number): number | undefined;
3
+ export declare function retryDelay(attempt: number, retryAfter: string | null, random?: () => number): number;
4
+ export declare function abortable<T>(promise: Promise<T>, signal: AbortSignal): Promise<T>;
5
+ export declare function sleep(ms: number, signal: AbortSignal): Promise<void>;
6
+ export interface RetryOptions {
7
+ maxRetries: number;
8
+ signal: AbortSignal;
9
+ /** Deterministic test seam; production uses real cancellable timers. */
10
+ wait?: (ms: number, signal: AbortSignal) => Promise<void>;
11
+ random?: () => number;
12
+ }
13
+ export declare function fetchWithRetry(request: Request, fetcher: typeof globalThis.fetch, options: RetryOptions): Promise<Response>;
@@ -0,0 +1,56 @@
1
+ import { PraxisConnectionError } from "./errors.js";
2
+ export function canRetry(request) {
3
+ return request.method !== 'POST' || Boolean(request.headers.get('Idempotency-Key')?.trim());
4
+ }
5
+ export function retryAfterMs(value, now = Date.now()) {
6
+ if (!value)
7
+ return undefined;
8
+ if (/^\d+(?:\.\d+)?$/.test(value.trim()))
9
+ return Number(value) * 1000;
10
+ const date = Date.parse(value);
11
+ return Number.isFinite(date) ? Math.max(0, date - now) : undefined;
12
+ }
13
+ export function retryDelay(attempt, retryAfter, random = Math.random) {
14
+ return retryAfterMs(retryAfter) ?? Math.min(30_000, 500 * 2 ** attempt) * (0.5 + random() * 0.5);
15
+ }
16
+ export function abortable(promise, signal) {
17
+ signal.throwIfAborted();
18
+ return new Promise((resolve, reject) => {
19
+ const abort = () => reject(signal.reason);
20
+ signal.addEventListener('abort', abort, { once: true });
21
+ promise.then(resolve, reject).finally(() => signal.removeEventListener('abort', abort));
22
+ });
23
+ }
24
+ export function sleep(ms, signal) {
25
+ signal.throwIfAborted();
26
+ return new Promise((resolve, reject) => {
27
+ const abort = () => { clearTimeout(timer); reject(signal.reason); };
28
+ const timer = setTimeout(() => { signal.removeEventListener('abort', abort); resolve(); }, ms);
29
+ signal.addEventListener('abort', abort, { once: true });
30
+ });
31
+ }
32
+ export async function fetchWithRetry(request, fetcher, options) {
33
+ const eligible = canRetry(request);
34
+ const wait = options.wait ?? sleep;
35
+ for (let attempt = 0;; attempt++) {
36
+ options.signal.throwIfAborted();
37
+ let response;
38
+ try {
39
+ // Preserve a replayable request; never reuse a consumed body on the next attempt.
40
+ response = await abortable(fetcher(request.clone()), options.signal);
41
+ }
42
+ catch (cause) {
43
+ options.signal.throwIfAborted();
44
+ if (!eligible || attempt >= options.maxRetries)
45
+ throw new PraxisConnectionError(cause);
46
+ await wait(retryDelay(attempt, null, options.random), options.signal);
47
+ continue;
48
+ }
49
+ const retriable = response.status === 408 || response.status === 429 || (response.status >= 500 && response.status <= 599);
50
+ if (!eligible || !retriable || attempt >= options.maxRetries)
51
+ return response;
52
+ const delay = retryDelay(attempt, response.headers.get('Retry-After'), options.random);
53
+ await response.body?.cancel();
54
+ await wait(delay, options.signal);
55
+ }
56
+ }
@@ -0,0 +1,13 @@
1
+ export interface SSEEvent<T = unknown> {
2
+ id?: string;
3
+ event?: string;
4
+ data: T;
5
+ }
6
+ export interface SSEOptions {
7
+ lastEventId?: string;
8
+ signal?: AbortSignal;
9
+ onId?: (id: string) => void;
10
+ onRetry?: (milliseconds: number) => void;
11
+ }
12
+ /** Incremental SSE framing: UTF-8, LF/CR/CRLF, comments and multiline data. */
13
+ export declare function parseSSE(body: ReadableStream<Uint8Array>, options?: SSEOptions): AsyncGenerator<SSEEvent>;
@@ -0,0 +1,67 @@
1
+ import { abortable } from "./retry.js";
2
+ /** Incremental SSE framing: UTF-8, LF/CR/CRLF, comments and multiline data. */
3
+ export async function* parseSSE(body, options = {}) {
4
+ const reader = body.getReader();
5
+ const decoder = new TextDecoder();
6
+ let pending = '', data = [], event = '', id = options.lastEventId;
7
+ function line(value) {
8
+ if (value === '') {
9
+ const kind = event;
10
+ event = '';
11
+ if (!data.length && kind !== 'end')
12
+ return;
13
+ const text = data.join('\n');
14
+ data = [];
15
+ let parsed = text;
16
+ try {
17
+ parsed = JSON.parse(text);
18
+ }
19
+ catch { /* SSE data can be plain text. */ }
20
+ return { ...(id === undefined ? {} : { id }), ...(kind ? { event: kind } : {}), data: parsed };
21
+ }
22
+ if (value.startsWith(':'))
23
+ return;
24
+ const colon = value.indexOf(':');
25
+ const field = colon < 0 ? value : value.slice(0, colon);
26
+ let content = colon < 0 ? '' : value.slice(colon + 1);
27
+ if (content.startsWith(' '))
28
+ content = content.slice(1);
29
+ if (field === 'data')
30
+ data.push(content);
31
+ else if (field === 'event')
32
+ event = content;
33
+ else if (field === 'id' && !content.includes('\0')) {
34
+ id = content;
35
+ options.onId?.(id);
36
+ }
37
+ else if (field === 'retry' && /^\d+$/.test(content) && Number.isSafeInteger(Number(content)))
38
+ options.onRetry?.(Number(content));
39
+ }
40
+ try {
41
+ for (;;) {
42
+ const result = options.signal ? await abortable(reader.read(), options.signal) : await reader.read();
43
+ pending += result.done ? decoder.decode() : decoder.decode(result.value, { stream: true });
44
+ for (;;) {
45
+ const at = pending.search(/[\r\n]/);
46
+ if (at < 0 || (!result.done && pending[at] === '\r' && at === pending.length - 1))
47
+ break;
48
+ const value = pending.slice(0, at);
49
+ const width = pending[at] === '\r' && pending[at + 1] === '\n' ? 2 : 1;
50
+ pending = pending.slice(at + width);
51
+ const frame = line(value);
52
+ if (frame) {
53
+ yield frame;
54
+ if (frame.event === 'end')
55
+ return;
56
+ }
57
+ }
58
+ // An incomplete event at EOF is discarded, as required by SSE framing.
59
+ if (result.done)
60
+ return;
61
+ }
62
+ }
63
+ finally {
64
+ await reader.cancel().catch(() => { });
65
+ reader.releaseLock();
66
+ }
67
+ }
@@ -0,0 +1,36 @@
1
+ import { type SSEEvent } from './sse.js';
2
+ export interface PraxisOptions {
3
+ /** Explicit key. Omitted on a Praxis computer: PRAXIS_API_TOKEN, then ~/.praxis/credentials, are used. */
4
+ apiKey?: string;
5
+ baseUrl?: string;
6
+ timeoutMs?: number;
7
+ maxRetries?: number;
8
+ fetch?: typeof globalThis.fetch;
9
+ }
10
+ export interface RequestOptions {
11
+ headers?: Record<string, string>;
12
+ signal?: AbortSignal;
13
+ timeoutMs?: number;
14
+ maxRetries?: number;
15
+ }
16
+ export type CallOptions<D> = Omit<D, 'url'> & RequestOptions;
17
+ export type StreamOptions<D> = CallOptions<D> & {
18
+ lastEventId?: string;
19
+ };
20
+ export type ResponseData<R> = R extends Record<string | number, unknown> ? R[keyof R] : R;
21
+ export type Operation = (options: any) => Promise<any>;
22
+ type InternalOptions = RequestOptions & {
23
+ body?: unknown;
24
+ path?: unknown;
25
+ query?: unknown;
26
+ lastEventId?: string;
27
+ };
28
+ export declare class Transport {
29
+ private readonly config;
30
+ constructor(options: PraxisOptions);
31
+ private validate;
32
+ private scope;
33
+ request<R>(operation: Operation, options?: InternalOptions): Promise<R>;
34
+ stream<E extends SSEEvent = SSEEvent>(operation: Operation, options?: InternalOptions): AsyncGenerator<E>;
35
+ }
36
+ export {};
@@ -0,0 +1,121 @@
1
+ import { createClient } from "../generated/client/index.js";
2
+ import { PraxisError, PraxisTimeoutError } from "./errors.js";
3
+ import { abortable, fetchWithRetry } from "./retry.js";
4
+ import { parseSSE } from "./sse.js";
5
+ import { resolveDefaultCredentials } from "./credentials.js";
6
+ /** Our own per-call keys, beyond what the generated hey-api client accepts. */
7
+ const SDK_OPTION_KEYS = ['timeoutMs', 'maxRetries', 'stream', 'lastEventId'];
8
+ /** Every key of the generated client's per-call RequestOptions (request parts, fetch/RequestInit, serializers,
9
+ * auth, SSE hooks, ...). Typed as a total record so a regenerated client that adds a key fails to typecheck here
10
+ * until the key is classified, instead of being rejected at runtime by the guard below. */
11
+ const HEY_API_OPTION_KEYS = {
12
+ body: true, path: true, query: true, headers: true, security: true, baseUrl: true, fetch: true, parseAs: true,
13
+ responseStyle: true, throwOnError: true, auth: true, bodySerializer: true, querySerializer: true,
14
+ requestValidator: true, responseTransformer: true, responseValidator: true,
15
+ onRequest: true, onSseError: true, onSseEvent: true, sseDefaultRetryDelay: true, sseMaxRetryAttempts: true, sseMaxRetryDelay: true,
16
+ // Config extends RequestInit (minus body/headers/method).
17
+ signal: true, cache: true, credentials: true, integrity: true, keepalive: true, mode: true, priority: true,
18
+ redirect: true, referrer: true, referrerPolicy: true, window: true,
19
+ };
20
+ const OPTION_KEYS = new Set([...Object.keys(HEY_API_OPTION_KEYS), ...SDK_OPTION_KEYS]);
21
+ /** Client-side guard: flat params like `{ limit: 20 }` would otherwise be silently dropped by the generated client. */
22
+ function assertKnownOptions(options) {
23
+ if (options === null || typeof options !== 'object')
24
+ return;
25
+ const stray = Object.keys(options).filter(key => !OPTION_KEYS.has(key));
26
+ if (stray.length === 0)
27
+ return;
28
+ const names = stray.map(key => `\`${key}\``).join(', ');
29
+ throw new PraxisError({
30
+ code: 'INVALID_OPTIONS', param: stray[0],
31
+ title: 'Invalid options',
32
+ detail: `Unrecognised option ${stray.length > 1 ? 'keys' : 'key'} ${names}. Request parameters are not flat: put each under \`query\` (GET/DELETE: filters, pagination), \`path\` (URL segments) or \`body\` (POST/PATCH/PUT payload), e.g. call({ query: { ${stray[0]} } }) or call({ body: { ${stray[0]} } }). Common top-level keys: path, query, body, headers, signal, timeoutMs, maxRetries.`,
33
+ });
34
+ }
35
+ export class Transport {
36
+ config;
37
+ constructor(options) {
38
+ this.config = { timeoutMs: 60_000, maxRetries: 2, fetch: globalThis.fetch, ...options };
39
+ this.validate(this.config.timeoutMs, this.config.maxRetries);
40
+ }
41
+ validate(timeout, retries) {
42
+ if (!Number.isFinite(timeout) || timeout <= 0)
43
+ throw new RangeError('timeoutMs must be positive and finite');
44
+ if (!Number.isSafeInteger(retries) || retries < 0)
45
+ throw new RangeError('maxRetries must be a non-negative integer');
46
+ }
47
+ scope(options, streaming) {
48
+ assertKnownOptions(options);
49
+ const timeoutMs = options.timeoutMs ?? this.config.timeoutMs;
50
+ const maxRetries = options.maxRetries ?? this.config.maxRetries;
51
+ this.validate(timeoutMs, maxRetries);
52
+ const controller = new AbortController();
53
+ const external = options.signal;
54
+ const abort = () => controller.abort(external?.reason);
55
+ if (external?.aborted)
56
+ abort();
57
+ else
58
+ external?.addEventListener('abort', abort, { once: true });
59
+ const timer = setTimeout(() => controller.abort(new PraxisTimeoutError(timeoutMs)), timeoutMs);
60
+ const signal = controller.signal;
61
+ // An explicit apiKey wins outright. Otherwise the computer's credentials apply, resolved now so a
62
+ // renewed token is used; their api_url only applies together with their token.
63
+ const defaults = this.config.apiKey === undefined ? resolveDefaultCredentials() : {};
64
+ const apiKey = this.config.apiKey ?? defaults.token;
65
+ const baseUrl = this.config.baseUrl ?? defaults.apiUrl ?? 'https://api.deepraxis.com';
66
+ const headers = new Headers(apiKey ? { 'X-API-Key': apiKey, Authorization: `Bearer ${apiKey}` } : {});
67
+ for (const [name, value] of Object.entries(options.headers ?? {}))
68
+ headers.set(name, value);
69
+ if (streaming)
70
+ headers.set('Accept', 'text/event-stream');
71
+ if (options.lastEventId !== undefined)
72
+ headers.set('Last-Event-ID', options.lastEventId);
73
+ const client = createClient({ baseUrl: baseUrl.replace(/\/$/, ''), headers, throwOnError: true,
74
+ fetch: ((request) => fetchWithRetry(request, this.config.fetch, { signal, maxRetries })),
75
+ });
76
+ client.interceptors.error.use((error, response) => {
77
+ if (signal.aborted)
78
+ return signal.reason;
79
+ if (error instanceof PraxisError)
80
+ return error;
81
+ return response && !response.ok ? PraxisError.fromResponse(response, error) : error;
82
+ });
83
+ if (streaming) {
84
+ // Pure SSE generated functions call client.sse.<verb>. Route those through
85
+ // the same generated Fetch client, preserving serialization and our policy.
86
+ client.sse = Object.fromEntries(['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace', 'connect'].map(method => [method, client[method]]));
87
+ }
88
+ return {
89
+ signal,
90
+ options: { ...options, headers: Object.fromEntries(headers), client, signal, throwOnError: true, responseStyle: 'fields', ...(streaming ? { parseAs: 'stream' } : {}) },
91
+ close() { clearTimeout(timer); external?.removeEventListener('abort', abort); },
92
+ };
93
+ }
94
+ async request(operation, options = {}) {
95
+ const scope = this.scope(options, false);
96
+ try {
97
+ return (await abortable(operation(scope.options), scope.signal)).data;
98
+ }
99
+ finally {
100
+ scope.close();
101
+ }
102
+ }
103
+ async *stream(operation, options = {}) {
104
+ const scope = this.scope(options, true);
105
+ try {
106
+ const result = await abortable(operation(scope.options), scope.signal);
107
+ if (!result.response.headers.get('content-type')?.split(';')[0]?.trim().toLowerCase().includes('text/event-stream')) {
108
+ await result.response.body?.cancel();
109
+ throw new PraxisError({ status: result.response.status, code: 'INVALID_RESPONSE', title: 'Expected text/event-stream' });
110
+ }
111
+ if (!result.data)
112
+ throw new PraxisError({ code: 'INVALID_RESPONSE', title: 'Missing SSE response body' });
113
+ // Types come from the spec's envelope; like the generated client this does
114
+ // not perform runtime schema validation of server payloads.
115
+ yield* parseSSE(result.data, { signal: scope.signal, lastEventId: options.lastEventId });
116
+ }
117
+ finally {
118
+ scope.close();
119
+ }
120
+ }
121
+ }
@@ -0,0 +1,2 @@
1
+ import type { Client, Config } from './types.gen.js';
2
+ export declare const createClient: (config?: Config) => Client;
@@ -0,0 +1,216 @@
1
+ // This file is auto-generated by @hey-api/openapi-ts
2
+ import { createSseClient } from '../core/serverSentEvents.gen.js';
3
+ import { getValidRequestBody } from '../core/utils.gen.js';
4
+ import { buildUrl, createConfig, createInterceptors, getParseAs, mergeConfigs, mergeHeaders, setAuthParams, } from './utils.gen.js';
5
+ export const createClient = (config = {}) => {
6
+ let _config = mergeConfigs(createConfig(), config);
7
+ const getConfig = () => ({ ..._config });
8
+ const setConfig = (config) => {
9
+ _config = mergeConfigs(_config, config);
10
+ return getConfig();
11
+ };
12
+ const interceptors = createInterceptors();
13
+ const beforeRequest = async (options) => {
14
+ const opts = {
15
+ ..._config,
16
+ ...options,
17
+ fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
18
+ headers: mergeHeaders(_config.headers, options.headers),
19
+ serializedBody: undefined,
20
+ };
21
+ if (opts.security) {
22
+ await setAuthParams(opts);
23
+ }
24
+ if (opts.requestValidator) {
25
+ await opts.requestValidator(opts);
26
+ }
27
+ if (opts.body !== undefined && opts.bodySerializer) {
28
+ opts.serializedBody = opts.bodySerializer(opts.body);
29
+ }
30
+ // remove Content-Type header if body is empty to avoid sending invalid requests
31
+ if (opts.body === undefined || opts.serializedBody === '') {
32
+ opts.headers.delete('Content-Type');
33
+ }
34
+ const resolvedOpts = opts;
35
+ const url = buildUrl(resolvedOpts);
36
+ return { opts: resolvedOpts, url };
37
+ };
38
+ const request = async (options) => {
39
+ const throwOnError = options.throwOnError ?? _config.throwOnError;
40
+ const responseStyle = options.responseStyle ?? _config.responseStyle;
41
+ let request;
42
+ let response;
43
+ try {
44
+ const { opts, url } = await beforeRequest(options);
45
+ const requestInit = {
46
+ redirect: 'follow',
47
+ ...opts,
48
+ body: getValidRequestBody(opts),
49
+ };
50
+ request = new Request(url, requestInit);
51
+ for (const fn of interceptors.request.fns) {
52
+ if (fn) {
53
+ request = await fn(request, opts);
54
+ }
55
+ }
56
+ // fetch must be assigned here, otherwise it would throw the error:
57
+ // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
58
+ const _fetch = opts.fetch;
59
+ response = await _fetch(request);
60
+ for (const fn of interceptors.response.fns) {
61
+ if (fn) {
62
+ response = await fn(response, request, opts);
63
+ }
64
+ }
65
+ const result = {
66
+ request,
67
+ response,
68
+ };
69
+ if (response.ok) {
70
+ const parseAs = (opts.parseAs === 'auto'
71
+ ? getParseAs(response.headers.get('Content-Type'))
72
+ : opts.parseAs) ?? 'json';
73
+ if (response.status === 204 || response.headers.get('Content-Length') === '0') {
74
+ let emptyData;
75
+ switch (parseAs) {
76
+ case 'arrayBuffer':
77
+ case 'blob':
78
+ case 'text':
79
+ emptyData = await response[parseAs]();
80
+ break;
81
+ case 'formData':
82
+ emptyData = new FormData();
83
+ break;
84
+ case 'stream':
85
+ emptyData = response.body;
86
+ break;
87
+ case 'json':
88
+ default:
89
+ emptyData = {};
90
+ break;
91
+ }
92
+ return opts.responseStyle === 'data'
93
+ ? emptyData
94
+ : {
95
+ data: emptyData,
96
+ ...result,
97
+ };
98
+ }
99
+ let data;
100
+ switch (parseAs) {
101
+ case 'arrayBuffer':
102
+ case 'blob':
103
+ case 'formData':
104
+ case 'text':
105
+ data = await response[parseAs]();
106
+ break;
107
+ case 'json': {
108
+ // Some servers return 200 with no Content-Length and empty body.
109
+ // response.json() would throw; read as text and parse if non-empty.
110
+ const text = await response.text();
111
+ data = text ? JSON.parse(text) : {};
112
+ break;
113
+ }
114
+ case 'stream':
115
+ return opts.responseStyle === 'data'
116
+ ? response.body
117
+ : {
118
+ data: response.body,
119
+ ...result,
120
+ };
121
+ }
122
+ if (parseAs === 'json') {
123
+ if (opts.responseValidator) {
124
+ await opts.responseValidator(data);
125
+ }
126
+ if (opts.responseTransformer) {
127
+ data = await opts.responseTransformer(data);
128
+ }
129
+ }
130
+ return opts.responseStyle === 'data'
131
+ ? data
132
+ : {
133
+ data,
134
+ ...result,
135
+ };
136
+ }
137
+ const textError = await response.text();
138
+ let jsonError;
139
+ try {
140
+ jsonError = JSON.parse(textError);
141
+ }
142
+ catch {
143
+ // noop
144
+ }
145
+ throw jsonError ?? textError;
146
+ }
147
+ catch (error) {
148
+ let finalError = error;
149
+ for (const fn of interceptors.error.fns) {
150
+ if (fn) {
151
+ finalError = await fn(finalError, response, request, options);
152
+ }
153
+ }
154
+ finalError = finalError || {};
155
+ if (throwOnError) {
156
+ throw finalError;
157
+ }
158
+ // TODO: we probably want to return error and improve types
159
+ return responseStyle === 'data'
160
+ ? undefined
161
+ : {
162
+ error: finalError,
163
+ request,
164
+ response,
165
+ };
166
+ }
167
+ };
168
+ const makeMethodFn = (method) => (options) => request({ ...options, method });
169
+ const makeSseFn = (method) => async (options) => {
170
+ const { opts, url } = await beforeRequest(options);
171
+ return createSseClient({
172
+ ...opts,
173
+ body: opts.body,
174
+ method,
175
+ onRequest: async (url, init) => {
176
+ let request = new Request(url, init);
177
+ for (const fn of interceptors.request.fns) {
178
+ if (fn) {
179
+ request = await fn(request, opts);
180
+ }
181
+ }
182
+ return request;
183
+ },
184
+ serializedBody: getValidRequestBody(opts),
185
+ url,
186
+ });
187
+ };
188
+ const _buildUrl = (options) => buildUrl({ ..._config, ...options });
189
+ return {
190
+ buildUrl: _buildUrl,
191
+ connect: makeMethodFn('CONNECT'),
192
+ delete: makeMethodFn('DELETE'),
193
+ get: makeMethodFn('GET'),
194
+ getConfig,
195
+ head: makeMethodFn('HEAD'),
196
+ interceptors,
197
+ options: makeMethodFn('OPTIONS'),
198
+ patch: makeMethodFn('PATCH'),
199
+ post: makeMethodFn('POST'),
200
+ put: makeMethodFn('PUT'),
201
+ request,
202
+ setConfig,
203
+ sse: {
204
+ connect: makeSseFn('CONNECT'),
205
+ delete: makeSseFn('DELETE'),
206
+ get: makeSseFn('GET'),
207
+ head: makeSseFn('HEAD'),
208
+ options: makeSseFn('OPTIONS'),
209
+ patch: makeSseFn('PATCH'),
210
+ post: makeSseFn('POST'),
211
+ put: makeSseFn('PUT'),
212
+ trace: makeSseFn('TRACE'),
213
+ },
214
+ trace: makeMethodFn('TRACE'),
215
+ };
216
+ };