@rebilly/api 0.0.0-stage → 0.1.0
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.
- package/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +230 -2
- package/dist/api-client-options.d.ts +13 -0
- package/dist/api-client-options.js +45 -0
- package/dist/core-client.d.ts +36842 -0
- package/dist/core-client.js +18406 -0
- package/dist/core-client.schemas.d.ts +28910 -0
- package/dist/core.d.ts +5 -0
- package/dist/core.js +7 -0
- package/dist/create-core-client.d.ts +8 -0
- package/dist/create-core-client.js +9 -0
- package/dist/create-storefront-client.d.ts +6 -0
- package/dist/create-storefront-client.js +12 -0
- package/dist/runtime/auth.d.ts +14 -0
- package/dist/runtime/auth.js +53 -0
- package/dist/runtime/create-client.d.ts +50 -0
- package/dist/runtime/create-client.js +345 -0
- package/dist/runtime/errors.d.ts +18 -0
- package/dist/runtime/errors.js +38 -0
- package/dist/runtime/factory.d.ts +11 -0
- package/dist/runtime/factory.js +16 -0
- package/dist/runtime/parse.d.ts +12 -0
- package/dist/runtime/parse.js +41 -0
- package/dist/runtime/retry.d.ts +15 -0
- package/dist/runtime/retry.js +62 -0
- package/dist/runtime/send.d.ts +35 -0
- package/dist/runtime/send.js +224 -0
- package/dist/runtime/types.d.ts +349 -0
- package/dist/runtime/url.d.ts +24 -0
- package/dist/runtime/url.js +62 -0
- package/dist/storefront-client.d.ts +4252 -0
- package/dist/storefront-client.js +1907 -0
- package/dist/storefront-client.schemas.d.ts +7649 -0
- package/dist/storefront.d.ts +5 -0
- package/dist/storefront.js +7 -0
- package/package.json +38 -3
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RetryConfig, RetryContext } from './types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The default retry predicate: idempotent methods — or any request carrying an
|
|
4
|
+
* `Idempotency-Key` header, which makes re-sending safe — on a transport error or a
|
|
5
|
+
* transient status. A custom `retryOn` fully replaces this (no method check kept).
|
|
6
|
+
*/
|
|
7
|
+
export declare function defaultRetryOn(ctx: RetryContext): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* The delay before the next attempt: a `Retry-After` header (seconds or HTTP-date)
|
|
10
|
+
* wins; otherwise fixed/exponential backoff over `retryDelay`, with full jitter
|
|
11
|
+
* unless `jitter === false`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function retryDelay(retry: RetryConfig, attempt: number, retryAfter: string | null): number;
|
|
14
|
+
/** Abort-aware sleep: resolves after `ms`, rejects with the abort reason immediately on abort. */
|
|
15
|
+
export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { abortError } from "./errors.js";
|
|
2
|
+
//#region src/runtime/retry.ts
|
|
3
|
+
var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
|
|
4
|
+
"GET",
|
|
5
|
+
"HEAD",
|
|
6
|
+
"PUT",
|
|
7
|
+
"DELETE",
|
|
8
|
+
"OPTIONS"
|
|
9
|
+
]);
|
|
10
|
+
var TRANSIENT_STATUS = /* @__PURE__ */ new Set([
|
|
11
|
+
408,
|
|
12
|
+
429,
|
|
13
|
+
500,
|
|
14
|
+
502,
|
|
15
|
+
503,
|
|
16
|
+
504
|
|
17
|
+
]);
|
|
18
|
+
/**
|
|
19
|
+
* The default retry predicate: idempotent methods — or any request carrying an
|
|
20
|
+
* `Idempotency-Key` header, which makes re-sending safe — on a transport error or a
|
|
21
|
+
* transient status. A custom `retryOn` fully replaces this (no method check kept).
|
|
22
|
+
*/
|
|
23
|
+
function defaultRetryOn(ctx) {
|
|
24
|
+
if (!(IDEMPOTENT_METHODS.has(ctx.request.method.toUpperCase()) || "Idempotency-Key" in ctx.request.headers || "idempotency-key" in ctx.request.headers)) return false;
|
|
25
|
+
return ctx.response === void 0 || TRANSIENT_STATUS.has(ctx.response.status);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The delay before the next attempt: a `Retry-After` header (seconds or HTTP-date)
|
|
29
|
+
* wins; otherwise fixed/exponential backoff over `retryDelay`, with full jitter
|
|
30
|
+
* unless `jitter === false`.
|
|
31
|
+
*/
|
|
32
|
+
function retryDelay(retry, attempt, retryAfter) {
|
|
33
|
+
if (retryAfter) {
|
|
34
|
+
const seconds = Number(retryAfter);
|
|
35
|
+
if (!Number.isNaN(seconds)) return seconds * 1e3;
|
|
36
|
+
const when = Date.parse(retryAfter);
|
|
37
|
+
if (!Number.isNaN(when)) return Math.max(0, when - Date.now());
|
|
38
|
+
}
|
|
39
|
+
const base = retry.retryDelay ?? 1e3;
|
|
40
|
+
const raw = retry.retryStrategy === "fixed" ? base : base * Math.pow(2, attempt - 1);
|
|
41
|
+
return retry.jitter === false ? raw : Math.random() * raw;
|
|
42
|
+
}
|
|
43
|
+
/** Abort-aware sleep: resolves after `ms`, rejects with the abort reason immediately on abort. */
|
|
44
|
+
function sleep(ms, signal) {
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
if (signal?.aborted) {
|
|
47
|
+
reject(abortError(signal));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const onAbort = () => {
|
|
51
|
+
clearTimeout(timer);
|
|
52
|
+
reject(abortError(signal));
|
|
53
|
+
};
|
|
54
|
+
const timer = setTimeout(() => {
|
|
55
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
56
|
+
resolve();
|
|
57
|
+
}, ms);
|
|
58
|
+
if (signal) signal.addEventListener("abort", onAbort, { once: true });
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { defaultRetryOn, retryDelay, sleep };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ClientConfig, Middleware, OperationContext, RequestContext, RequestOptions } from './types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Optional behaviors the send core can use but never statically imports — wired by
|
|
4
|
+
* `createClient` (the same seam the future inline-mode assembler relies on).
|
|
5
|
+
*/
|
|
6
|
+
export type SendCapabilities = {
|
|
7
|
+
/** Serialize a typed multipart body (a plain object) to FormData. */
|
|
8
|
+
serializeMultipart?: (body: Record<string, unknown>) => FormData;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Normalize a caller's `HeadersInit` (plain record, `Headers` instance, or entry pairs)
|
|
12
|
+
* to a plain record — spreading a `Headers` or an array contributes no entries.
|
|
13
|
+
*/
|
|
14
|
+
export declare function toHeaderRecord(headers: HeadersInit | undefined): Record<string, string>;
|
|
15
|
+
/**
|
|
16
|
+
* The effective middleware chain for a request: the single `onRequest`/`onResponse`/
|
|
17
|
+
* `onError` config hooks as one implicit first middleware, then `config.middleware`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function middlewareChain(config: ClientConfig): Middleware[];
|
|
20
|
+
/**
|
|
21
|
+
* The fetch core shared by every operation: default + config + per-call headers, the
|
|
22
|
+
* `onRequest` chain (BEFORE body serialization, so mutations are sent), body
|
|
23
|
+
* serialization (JSON, or FormData via the multipart capability; a stream passes
|
|
24
|
+
* through in one attempt), the retry loop
|
|
25
|
+
* (idempotent-only defaults, `Retry-After`, abandoned-body drain), and the reverse
|
|
26
|
+
* `onResponse` onion, then the `onSettled` chain once with the final outcome. Returns the
|
|
27
|
+
* final response plus the request context.
|
|
28
|
+
*/
|
|
29
|
+
export declare function send(config: ClientConfig, op: OperationContext, url: string, init: RequestOptions, body: unknown | undefined, bodySpec: {
|
|
30
|
+
contentType: string;
|
|
31
|
+
multipart?: boolean;
|
|
32
|
+
} | undefined, caps: SendCapabilities, accept?: string): Promise<{
|
|
33
|
+
response: Response;
|
|
34
|
+
context: RequestContext;
|
|
35
|
+
}>;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { TimeoutError, abortError } from "./errors.js";
|
|
2
|
+
import { defaultRetryOn, retryDelay, sleep } from "./retry.js";
|
|
3
|
+
//#region src/runtime/send.ts
|
|
4
|
+
/**
|
|
5
|
+
* Normalize a caller's `HeadersInit` (plain record, `Headers` instance, or entry pairs)
|
|
6
|
+
* to a plain record — spreading a `Headers` or an array contributes no entries.
|
|
7
|
+
*/
|
|
8
|
+
function toHeaderRecord(headers) {
|
|
9
|
+
if (headers === void 0) return {};
|
|
10
|
+
if (headers instanceof Headers) {
|
|
11
|
+
const record = {};
|
|
12
|
+
headers.forEach((value, key) => {
|
|
13
|
+
record[key] = value;
|
|
14
|
+
});
|
|
15
|
+
return record;
|
|
16
|
+
}
|
|
17
|
+
if (Array.isArray(headers)) return Object.fromEntries(headers);
|
|
18
|
+
return headers;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The effective middleware chain for a request: the single `onRequest`/`onResponse`/
|
|
22
|
+
* `onError` config hooks as one implicit first middleware, then `config.middleware`.
|
|
23
|
+
*/
|
|
24
|
+
function middlewareChain(config) {
|
|
25
|
+
return [...config.onRequest || config.onResponse || config.onError ? [{
|
|
26
|
+
onRequest: config.onRequest,
|
|
27
|
+
onResponse: config.onResponse,
|
|
28
|
+
onError: config.onError
|
|
29
|
+
}] : [], ...config.middleware ?? []];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* `AbortSignal.timeout()` where the platform has it, else an `AbortController` armed by
|
|
33
|
+
* `setTimeout`: Chrome before 103, Safari before 16, and Firefox before 100 lack the
|
|
34
|
+
* static, and every request with a timeout would throw before `fetch` runs.
|
|
35
|
+
*/
|
|
36
|
+
function timeoutSignal(ms) {
|
|
37
|
+
if (typeof AbortSignal.timeout === "function") return AbortSignal.timeout(ms);
|
|
38
|
+
const controller = new AbortController();
|
|
39
|
+
setTimeout(() => {
|
|
40
|
+
controller.abort(new DOMException(`Timed out after ${ms} ms`, "TimeoutError"));
|
|
41
|
+
}, ms);
|
|
42
|
+
return controller.signal;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* `AbortSignal.any()` where the platform has it, else a controller that follows whichever
|
|
46
|
+
* signal aborts first: Chrome before 116, Safari before 17.4, and Firefox before 124 lack
|
|
47
|
+
* the static. The listeners come off once one signal fires, so a caller's long-lived
|
|
48
|
+
* signal does not keep one per request.
|
|
49
|
+
*/
|
|
50
|
+
function anySignal(signals) {
|
|
51
|
+
if (typeof AbortSignal.any === "function") return AbortSignal.any(signals);
|
|
52
|
+
const controller = new AbortController();
|
|
53
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
54
|
+
const abort = (reason) => {
|
|
55
|
+
for (const [signal, listener] of listeners) signal.removeEventListener("abort", listener);
|
|
56
|
+
controller.abort(reason);
|
|
57
|
+
};
|
|
58
|
+
for (const signal of signals) {
|
|
59
|
+
const listener = () => abort(signal.reason);
|
|
60
|
+
listeners.set(signal, listener);
|
|
61
|
+
signal.addEventListener("abort", listener);
|
|
62
|
+
}
|
|
63
|
+
return controller.signal;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The fetch core shared by every operation: default + config + per-call headers, the
|
|
67
|
+
* `onRequest` chain (BEFORE body serialization, so mutations are sent), body
|
|
68
|
+
* serialization (JSON, or FormData via the multipart capability; a stream passes
|
|
69
|
+
* through in one attempt), the retry loop
|
|
70
|
+
* (idempotent-only defaults, `Retry-After`, abandoned-body drain), and the reverse
|
|
71
|
+
* `onResponse` onion, then the `onSettled` chain once with the final outcome. Returns the
|
|
72
|
+
* final response plus the request context.
|
|
73
|
+
*/
|
|
74
|
+
async function send(config, op, url, init, body, bodySpec, caps, accept = "application/json") {
|
|
75
|
+
const { retry: callRetry, timeout: callTimeout, idempotencyKey: callKey, ...fetchInit } = init;
|
|
76
|
+
const retry = {
|
|
77
|
+
...config.retry,
|
|
78
|
+
...callRetry
|
|
79
|
+
};
|
|
80
|
+
const timeout = callTimeout ?? config.timeout;
|
|
81
|
+
const idempotency = callKey ?? config.idempotencyKey;
|
|
82
|
+
const headers = {
|
|
83
|
+
Accept: accept,
|
|
84
|
+
...typeof config.headers === "function" ? await config.headers() : config.headers,
|
|
85
|
+
...toHeaderRecord(fetchInit.headers)
|
|
86
|
+
};
|
|
87
|
+
const method = (fetchInit.method ?? "GET").toUpperCase();
|
|
88
|
+
if (idempotency !== void 0 && idempotency !== false && (method === "POST" || method === "PATCH") && !("Idempotency-Key" in headers) && !("idempotency-key" in headers)) headers["Idempotency-Key"] = typeof idempotency === "string" ? idempotency : typeof idempotency === "function" ? idempotency() : crypto.randomUUID();
|
|
89
|
+
if (typeof config.clientHeader === "string" && typeof document === "undefined" && !("X-Redocly-Client" in headers) && !("x-redocly-client" in headers)) headers["X-Redocly-Client"] = config.clientHeader;
|
|
90
|
+
const context = {
|
|
91
|
+
url,
|
|
92
|
+
method: fetchInit.method ?? "GET",
|
|
93
|
+
headers,
|
|
94
|
+
body,
|
|
95
|
+
operation: op
|
|
96
|
+
};
|
|
97
|
+
const middleware = middlewareChain(config);
|
|
98
|
+
for (const mw of middleware) if (mw.onRequest) await mw.onRequest(context);
|
|
99
|
+
let payload;
|
|
100
|
+
let isStream = false;
|
|
101
|
+
if (context.body !== void 0) {
|
|
102
|
+
const value = context.body;
|
|
103
|
+
const isBinary = value instanceof Blob || value instanceof ArrayBuffer || ArrayBuffer.isView(value);
|
|
104
|
+
const isFormData = typeof FormData !== "undefined" && value instanceof FormData;
|
|
105
|
+
const isURLSearchParams = value instanceof URLSearchParams;
|
|
106
|
+
isStream = typeof ReadableStream !== "undefined" && value instanceof ReadableStream || value !== null && typeof value[Symbol.asyncIterator] === "function";
|
|
107
|
+
const hasContentType = "Content-Type" in context.headers || "content-type" in context.headers;
|
|
108
|
+
if (isFormData || isURLSearchParams || isBinary || isStream || typeof value === "string") {
|
|
109
|
+
payload = value;
|
|
110
|
+
const declared = bodySpec?.contentType;
|
|
111
|
+
if (!isFormData && !isURLSearchParams && !hasContentType && declared !== void 0 && !declared.toLowerCase().includes("multipart")) context.headers["Content-Type"] = declared;
|
|
112
|
+
} else if (bodySpec?.multipart === true) {
|
|
113
|
+
if (!caps.serializeMultipart) throw new Error("Multipart capability not wired: cannot serialize the request body");
|
|
114
|
+
payload = caps.serializeMultipart(value);
|
|
115
|
+
} else {
|
|
116
|
+
payload = JSON.stringify(value);
|
|
117
|
+
if (!hasContentType) context.headers["Content-Type"] = bodySpec?.contentType ?? "application/json";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const doFetch = config.fetch ?? fetch;
|
|
121
|
+
const maxAttempts = isStream ? 1 : 1 + (retry.retries ?? 0);
|
|
122
|
+
const retryOn = retry.retryOn ?? defaultRetryOn;
|
|
123
|
+
const signal = fetchInit.signal ?? void 0;
|
|
124
|
+
const startedAt = Date.now();
|
|
125
|
+
let attempt = 0;
|
|
126
|
+
let finalResponse;
|
|
127
|
+
let lastResponse;
|
|
128
|
+
try {
|
|
129
|
+
while (true) {
|
|
130
|
+
if (signal?.aborted) throw abortError(signal);
|
|
131
|
+
attempt++;
|
|
132
|
+
const budget = timeout ? timeoutSignal(timeout) : void 0;
|
|
133
|
+
const attemptSignal = budget && signal ? anySignal([signal, budget]) : budget ?? signal;
|
|
134
|
+
let response;
|
|
135
|
+
try {
|
|
136
|
+
response = await doFetch(context.url, {
|
|
137
|
+
...fetchInit,
|
|
138
|
+
...isStream ? { duplex: "half" } : {},
|
|
139
|
+
signal: attemptSignal,
|
|
140
|
+
method: context.method,
|
|
141
|
+
headers: context.headers,
|
|
142
|
+
body: payload
|
|
143
|
+
});
|
|
144
|
+
} catch (error) {
|
|
145
|
+
const timedOut = timeout !== void 0 && budget?.aborted && !signal?.aborted;
|
|
146
|
+
if (attempt < maxAttempts && !signal?.aborted && await retryOn({
|
|
147
|
+
attempt,
|
|
148
|
+
request: context,
|
|
149
|
+
error
|
|
150
|
+
})) {
|
|
151
|
+
await sleep(retryDelay(retry, attempt, null), signal);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (timedOut) throw new TimeoutError(op.id, timeout, attempt);
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
lastResponse = response;
|
|
158
|
+
for (let i = middleware.length - 1; i >= 0; i--) {
|
|
159
|
+
const onResponse = middleware[i].onResponse;
|
|
160
|
+
if (onResponse) {
|
|
161
|
+
const replaced = await onResponse(response, context);
|
|
162
|
+
if (replaced && replaced !== response) {
|
|
163
|
+
await response.body?.cancel().catch(() => void 0);
|
|
164
|
+
response = replaced;
|
|
165
|
+
lastResponse = replaced;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (!response.ok && attempt < maxAttempts && !signal?.aborted && await retryOn({
|
|
170
|
+
attempt,
|
|
171
|
+
request: context,
|
|
172
|
+
response
|
|
173
|
+
})) {
|
|
174
|
+
const retryAfter = response.headers.get("retry-after");
|
|
175
|
+
await response.body?.cancel().catch(() => void 0);
|
|
176
|
+
lastResponse = void 0;
|
|
177
|
+
await sleep(retryDelay(retry, attempt, retryAfter), signal);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
finalResponse = response;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
} catch (error) {
|
|
184
|
+
const settled = {
|
|
185
|
+
response: lastResponse,
|
|
186
|
+
error,
|
|
187
|
+
attempts: attempt,
|
|
188
|
+
durationMs: Date.now() - startedAt
|
|
189
|
+
};
|
|
190
|
+
for (const mw of middleware) {
|
|
191
|
+
if (!mw.onSettled) continue;
|
|
192
|
+
try {
|
|
193
|
+
await mw.onSettled(context, settled);
|
|
194
|
+
} catch (hookError) {
|
|
195
|
+
globalThis.console?.warn("onSettled hook failed", hookError);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
const settled = {
|
|
201
|
+
response: finalResponse,
|
|
202
|
+
attempts: attempt,
|
|
203
|
+
durationMs: Date.now() - startedAt
|
|
204
|
+
};
|
|
205
|
+
const hookErrors = [];
|
|
206
|
+
for (const mw of middleware) {
|
|
207
|
+
if (!mw.onSettled) continue;
|
|
208
|
+
try {
|
|
209
|
+
await mw.onSettled(context, settled);
|
|
210
|
+
} catch (hookError) {
|
|
211
|
+
hookErrors.push(hookError);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (hookErrors.length > 0) {
|
|
215
|
+
await finalResponse.body?.cancel().catch(() => void 0);
|
|
216
|
+
throw hookErrors[0];
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
response: finalResponse,
|
|
220
|
+
context
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
224
|
+
export { middlewareChain, send, toHeaderRecord };
|