@torvion/rascador 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/LICENSE +21 -0
- package/README.md +176 -0
- package/dist/index.cjs +387 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +315 -0
- package/dist/index.d.ts +315 -0
- package/dist/index.js +355 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var RascadorError = class extends Error {
|
|
3
|
+
name = "RascadorError";
|
|
4
|
+
code;
|
|
5
|
+
/** HTTP status, or 0 when no response arrived (network failure, timeout). */
|
|
6
|
+
status;
|
|
7
|
+
retryable;
|
|
8
|
+
retryAfterSeconds;
|
|
9
|
+
/** Quote this when contacting support. */
|
|
10
|
+
requestId;
|
|
11
|
+
/** This failure in your dashboard log. */
|
|
12
|
+
logUrl;
|
|
13
|
+
details;
|
|
14
|
+
constructor(init) {
|
|
15
|
+
super(init.message, init.cause === void 0 ? void 0 : { cause: init.cause });
|
|
16
|
+
this.code = init.code;
|
|
17
|
+
this.status = init.status;
|
|
18
|
+
this.retryable = init.retryable;
|
|
19
|
+
this.retryAfterSeconds = init.retryAfterSeconds;
|
|
20
|
+
this.requestId = init.requestId;
|
|
21
|
+
this.logUrl = init.logUrl;
|
|
22
|
+
this.details = init.details;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
function isRascadorError(error) {
|
|
26
|
+
return error instanceof RascadorError;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/pagination.ts
|
|
30
|
+
var PagePromise = class {
|
|
31
|
+
#fetchPage;
|
|
32
|
+
#startOffset;
|
|
33
|
+
#first;
|
|
34
|
+
constructor(fetchPage, startOffset) {
|
|
35
|
+
this.#fetchPage = fetchPage;
|
|
36
|
+
this.#startOffset = startOffset;
|
|
37
|
+
}
|
|
38
|
+
// Lazy, so a list you only iterate doesn't also fire a request for `then`.
|
|
39
|
+
#firstPage() {
|
|
40
|
+
this.#first ??= this.#fetchPage(this.#startOffset);
|
|
41
|
+
return this.#first;
|
|
42
|
+
}
|
|
43
|
+
then(onfulfilled, onrejected) {
|
|
44
|
+
return this.#firstPage().then(onfulfilled, onrejected);
|
|
45
|
+
}
|
|
46
|
+
catch(onrejected) {
|
|
47
|
+
return this.#firstPage().catch(onrejected);
|
|
48
|
+
}
|
|
49
|
+
finally(onfinally) {
|
|
50
|
+
return this.#firstPage().finally(onfinally);
|
|
51
|
+
}
|
|
52
|
+
/** Every page, in order. */
|
|
53
|
+
async *pages() {
|
|
54
|
+
let page = await this.#firstPage();
|
|
55
|
+
for (; ; ) {
|
|
56
|
+
yield page;
|
|
57
|
+
const p = page.meta.pagination;
|
|
58
|
+
if (!p?.has_more || p.limit <= 0) return;
|
|
59
|
+
page = await this.#fetchPage(p.offset + p.limit);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async *[Symbol.asyncIterator]() {
|
|
63
|
+
for await (const page of this.pages()) yield* page.data;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// src/version.ts
|
|
68
|
+
var VERSION = "0.1.0";
|
|
69
|
+
|
|
70
|
+
// src/client.ts
|
|
71
|
+
var DEFAULT_BASE_URL = "https://api.rascador.store";
|
|
72
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
73
|
+
var DEFAULT_LIVE_TIMEOUT_MS = 13e4;
|
|
74
|
+
var DEFAULT_MAX_RETRIES = 2;
|
|
75
|
+
var MAX_BACKOFF_MS = 8e3;
|
|
76
|
+
function readEnvKey() {
|
|
77
|
+
const env = globalThis.process?.env;
|
|
78
|
+
return env?.RASCADOR_API_KEY;
|
|
79
|
+
}
|
|
80
|
+
var Rascador = class {
|
|
81
|
+
#apiKey;
|
|
82
|
+
#baseUrl;
|
|
83
|
+
#source;
|
|
84
|
+
#timeoutMs;
|
|
85
|
+
#liveTimeoutMs;
|
|
86
|
+
#maxRetries;
|
|
87
|
+
#fetch;
|
|
88
|
+
#headers;
|
|
89
|
+
products;
|
|
90
|
+
categories;
|
|
91
|
+
sources;
|
|
92
|
+
constructor(options = {}) {
|
|
93
|
+
const apiKey = options.apiKey ?? readEnvKey();
|
|
94
|
+
if (!apiKey) {
|
|
95
|
+
throw new RascadorError({
|
|
96
|
+
code: "missing_credentials",
|
|
97
|
+
message: "No API key. Pass `apiKey` or set the RASCADOR_API_KEY environment variable.",
|
|
98
|
+
status: 0,
|
|
99
|
+
retryable: false
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
103
|
+
if (typeof fetchImpl !== "function") {
|
|
104
|
+
throw new TypeError("No global fetch found. Pass `fetch` in the options (Node 18+ has one built in).");
|
|
105
|
+
}
|
|
106
|
+
this.#apiKey = apiKey;
|
|
107
|
+
this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
108
|
+
this.#source = options.source;
|
|
109
|
+
this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
110
|
+
this.#liveTimeoutMs = options.liveTimeoutMs ?? DEFAULT_LIVE_TIMEOUT_MS;
|
|
111
|
+
this.#maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
112
|
+
this.#fetch = fetchImpl.bind(globalThis);
|
|
113
|
+
this.#headers = options.headers ?? {};
|
|
114
|
+
this.products = new ProductsResource(this);
|
|
115
|
+
this.categories = new CategoriesResource(this);
|
|
116
|
+
this.sources = new SourcesResource(this);
|
|
117
|
+
}
|
|
118
|
+
/** The key's scopes, limits and remaining quota. Never spends quota. */
|
|
119
|
+
me(options) {
|
|
120
|
+
return this._get({ path: "/v1/me", options });
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Live search on the source site. Slow (20–40s) and quota-heavy.
|
|
124
|
+
* Returns result cards; call `products.get` for full details.
|
|
125
|
+
*/
|
|
126
|
+
search(params, options) {
|
|
127
|
+
const { source, ...query } = params;
|
|
128
|
+
return this._get({ path: "/v1/search", query: { ...query, source: this._source(source) }, live: true, options });
|
|
129
|
+
}
|
|
130
|
+
/** @internal */
|
|
131
|
+
_source(source) {
|
|
132
|
+
return source ?? this.#source;
|
|
133
|
+
}
|
|
134
|
+
/** @internal */
|
|
135
|
+
async _get(spec) {
|
|
136
|
+
const url = this.#url(spec.path, spec.query);
|
|
137
|
+
const maxRetries = spec.options?.maxRetries ?? this.#maxRetries;
|
|
138
|
+
const timeoutMs = spec.options?.timeoutMs ?? (spec.live ? this.#liveTimeoutMs : this.#timeoutMs);
|
|
139
|
+
for (let attempt = 0; ; attempt++) {
|
|
140
|
+
try {
|
|
141
|
+
return await this.#once(url, timeoutMs, spec.options?.signal);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if (!(error instanceof RascadorError) || !error.retryable || attempt >= maxRetries) throw error;
|
|
144
|
+
await sleep(backoffMs(attempt, error.retryAfterSeconds), spec.options?.signal);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
#url(path, query) {
|
|
149
|
+
const url = new URL(this.#baseUrl + path);
|
|
150
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
151
|
+
if (value !== void 0) url.searchParams.set(key, String(value));
|
|
152
|
+
}
|
|
153
|
+
return url.toString();
|
|
154
|
+
}
|
|
155
|
+
async #once(url, timeoutMs, signal) {
|
|
156
|
+
const { signal: combined, cleanup, timedOut } = withTimeout(timeoutMs, signal);
|
|
157
|
+
let res;
|
|
158
|
+
try {
|
|
159
|
+
res = await this.#fetch(url, {
|
|
160
|
+
method: "GET",
|
|
161
|
+
headers: {
|
|
162
|
+
...this.#headers,
|
|
163
|
+
accept: "application/json",
|
|
164
|
+
authorization: `Bearer ${this.#apiKey}`
|
|
165
|
+
},
|
|
166
|
+
signal: combined
|
|
167
|
+
});
|
|
168
|
+
} catch (cause) {
|
|
169
|
+
cleanup();
|
|
170
|
+
if (timedOut()) {
|
|
171
|
+
throw new RascadorError({
|
|
172
|
+
code: "timeout",
|
|
173
|
+
message: `No response within ${timeoutMs}ms.`,
|
|
174
|
+
status: 0,
|
|
175
|
+
retryable: false,
|
|
176
|
+
cause
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (signal?.aborted) throw cause;
|
|
180
|
+
throw new RascadorError({
|
|
181
|
+
code: "network_error",
|
|
182
|
+
message: cause instanceof Error ? cause.message : "The request could not be sent.",
|
|
183
|
+
status: 0,
|
|
184
|
+
retryable: true,
|
|
185
|
+
cause
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
let body;
|
|
189
|
+
try {
|
|
190
|
+
const text = await res.text();
|
|
191
|
+
body = text ? JSON.parse(text) : null;
|
|
192
|
+
} catch (cause) {
|
|
193
|
+
if (timedOut()) {
|
|
194
|
+
throw new RascadorError({
|
|
195
|
+
code: "timeout",
|
|
196
|
+
message: `The response did not finish within ${timeoutMs}ms.`,
|
|
197
|
+
status: res.status,
|
|
198
|
+
retryable: false,
|
|
199
|
+
cause
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (signal?.aborted) throw cause;
|
|
203
|
+
throw new RascadorError({
|
|
204
|
+
code: "invalid_response",
|
|
205
|
+
message: `Expected JSON from the gateway (HTTP ${res.status}).`,
|
|
206
|
+
status: res.status,
|
|
207
|
+
retryable: res.status >= 500,
|
|
208
|
+
cause
|
|
209
|
+
});
|
|
210
|
+
} finally {
|
|
211
|
+
cleanup();
|
|
212
|
+
}
|
|
213
|
+
if (!res.ok) throw errorFrom(res, body);
|
|
214
|
+
if (!body || typeof body !== "object" || !("data" in body)) {
|
|
215
|
+
throw new RascadorError({
|
|
216
|
+
code: "invalid_response",
|
|
217
|
+
message: "The gateway response had no `data` field.",
|
|
218
|
+
status: res.status,
|
|
219
|
+
retryable: false
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
return body;
|
|
223
|
+
}
|
|
224
|
+
/** The SDK version. Include it in bug reports. */
|
|
225
|
+
static VERSION = VERSION;
|
|
226
|
+
};
|
|
227
|
+
var ProductsResource = class {
|
|
228
|
+
#client;
|
|
229
|
+
constructor(client) {
|
|
230
|
+
this.#client = client;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* One product. Instant when stored; otherwise (or with `refresh: true`) a
|
|
234
|
+
* live fetch, which needs the `search:live` scope.
|
|
235
|
+
*/
|
|
236
|
+
get(id, params = {}, options) {
|
|
237
|
+
const { source, ...query } = params;
|
|
238
|
+
return this.#client._get({
|
|
239
|
+
path: `/v1/products/${encodeURIComponent(String(id))}`,
|
|
240
|
+
query: { ...query, source: this.#client._source(source) },
|
|
241
|
+
live: params.refresh === true,
|
|
242
|
+
options
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/** Stored products. `await` for one page, `for await` for every product. */
|
|
246
|
+
list(params = {}, options) {
|
|
247
|
+
const { source, offset, ...query } = params;
|
|
248
|
+
return new PagePromise(
|
|
249
|
+
(next) => this.#client._get({
|
|
250
|
+
path: "/v1/products",
|
|
251
|
+
query: { ...query, offset: next, source: this.#client._source(source) },
|
|
252
|
+
options
|
|
253
|
+
}),
|
|
254
|
+
offset
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
var CategoriesResource = class {
|
|
259
|
+
#client;
|
|
260
|
+
constructor(client) {
|
|
261
|
+
this.#client = client;
|
|
262
|
+
}
|
|
263
|
+
/** The source's category tree. `await` for one page, `for await` for all. */
|
|
264
|
+
list(params = {}, options) {
|
|
265
|
+
const { source, offset, ...query } = params;
|
|
266
|
+
return new PagePromise(
|
|
267
|
+
(next) => this.#client._get({
|
|
268
|
+
path: "/v1/categories",
|
|
269
|
+
query: { ...query, offset: next, source: this.#client._source(source) },
|
|
270
|
+
options
|
|
271
|
+
}),
|
|
272
|
+
offset
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
var SourcesResource = class {
|
|
277
|
+
#client;
|
|
278
|
+
constructor(client) {
|
|
279
|
+
this.#client = client;
|
|
280
|
+
}
|
|
281
|
+
/** Every source and what it supports. Never spends quota. */
|
|
282
|
+
list(options) {
|
|
283
|
+
return this.#client._get({ path: "/v1/sources", options });
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
function errorFrom(res, body) {
|
|
287
|
+
const e = body?.error;
|
|
288
|
+
const headerRetry = Number(res.headers.get("retry-after"));
|
|
289
|
+
if (!e || typeof e.code !== "string") {
|
|
290
|
+
return new RascadorError({
|
|
291
|
+
code: res.status >= 500 ? "internal_error" : "invalid_response",
|
|
292
|
+
message: `The gateway returned HTTP ${res.status} without an error body.`,
|
|
293
|
+
status: res.status,
|
|
294
|
+
retryable: res.status >= 500 || res.status === 429,
|
|
295
|
+
retryAfterSeconds: Number.isFinite(headerRetry) && headerRetry > 0 ? headerRetry : void 0
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
const retryAfter = typeof e.retry_after_seconds === "number" ? e.retry_after_seconds : Number.isFinite(headerRetry) && headerRetry > 0 ? headerRetry : void 0;
|
|
299
|
+
return new RascadorError({
|
|
300
|
+
code: e.code,
|
|
301
|
+
message: typeof e.message === "string" ? e.message : e.code,
|
|
302
|
+
status: res.status,
|
|
303
|
+
retryable: e.retryable === true,
|
|
304
|
+
retryAfterSeconds: retryAfter,
|
|
305
|
+
requestId: typeof e.request_id === "string" ? e.request_id : void 0,
|
|
306
|
+
logUrl: typeof e.log_url === "string" ? e.log_url : void 0,
|
|
307
|
+
details: e.details ?? void 0
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
function backoffMs(attempt, retryAfterSeconds) {
|
|
311
|
+
const jittered = Math.random() * Math.min(MAX_BACKOFF_MS, 500 * 2 ** attempt);
|
|
312
|
+
return Math.max(jittered, (retryAfterSeconds ?? 0) * 1e3);
|
|
313
|
+
}
|
|
314
|
+
function sleep(ms, signal) {
|
|
315
|
+
return new Promise((resolve, reject) => {
|
|
316
|
+
if (signal?.aborted) return reject(signal.reason);
|
|
317
|
+
const timer = setTimeout(() => {
|
|
318
|
+
signal?.removeEventListener("abort", onAbort);
|
|
319
|
+
resolve();
|
|
320
|
+
}, ms);
|
|
321
|
+
const onAbort = () => {
|
|
322
|
+
clearTimeout(timer);
|
|
323
|
+
reject(signal?.reason);
|
|
324
|
+
};
|
|
325
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
function withTimeout(ms, signal) {
|
|
329
|
+
const controller = new AbortController();
|
|
330
|
+
let fired = false;
|
|
331
|
+
const timer = setTimeout(() => {
|
|
332
|
+
fired = true;
|
|
333
|
+
controller.abort();
|
|
334
|
+
}, ms);
|
|
335
|
+
const onAbort = () => controller.abort(signal?.reason);
|
|
336
|
+
if (signal?.aborted) controller.abort(signal.reason);
|
|
337
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
338
|
+
return {
|
|
339
|
+
signal: controller.signal,
|
|
340
|
+
cleanup: () => {
|
|
341
|
+
clearTimeout(timer);
|
|
342
|
+
signal?.removeEventListener("abort", onAbort);
|
|
343
|
+
},
|
|
344
|
+
timedOut: () => fired
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
export {
|
|
348
|
+
DEFAULT_BASE_URL,
|
|
349
|
+
PagePromise,
|
|
350
|
+
Rascador,
|
|
351
|
+
RascadorError,
|
|
352
|
+
VERSION,
|
|
353
|
+
isRascadorError
|
|
354
|
+
};
|
|
355
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/pagination.ts","../src/version.ts","../src/client.ts"],"sourcesContent":["/**\n * Codes the gateway can return, plus three the SDK raises itself:\n * `network_error`, `timeout` and `invalid_response`.\n *\n * The `(string & {})` keeps autocomplete for the known codes while still\n * accepting any code a newer gateway adds.\n */\nexport type RascadorErrorCode =\n | \"invalid_request\"\n | \"unknown_source\"\n | \"missing_credentials\"\n | \"invalid_token\"\n | \"quota_exceeded\"\n | \"coverage_exceeded\"\n | \"payment_required\"\n | \"insufficient_scope\"\n | \"client_suspended\"\n | \"not_found\"\n | \"product_not_found\"\n | \"rate_limited\"\n | \"platform_busy\"\n | \"internal_error\"\n | \"gateway_misconfigured\"\n | \"upstream_error\"\n | \"upstream_unavailable\"\n | \"upstream_busy\"\n | \"upstream_timeout\"\n | \"network_error\"\n | \"timeout\"\n | \"invalid_response\"\n | (string & {})\n\nexport interface RascadorErrorInit {\n code: RascadorErrorCode\n message: string\n status: number\n retryable: boolean\n retryAfterSeconds?: number\n requestId?: string\n logUrl?: string\n details?: Record<string, unknown>\n cause?: unknown\n}\n\n/** Thrown for every failed call. Branch on `code`, not on `message`. */\nexport class RascadorError extends Error {\n override readonly name = \"RascadorError\"\n readonly code: RascadorErrorCode\n /** HTTP status, or 0 when no response arrived (network failure, timeout). */\n readonly status: number\n readonly retryable: boolean\n readonly retryAfterSeconds: number | undefined\n /** Quote this when contacting support. */\n readonly requestId: string | undefined\n /** This failure in your dashboard log. */\n readonly logUrl: string | undefined\n readonly details: Record<string, unknown> | undefined\n\n constructor(init: RascadorErrorInit) {\n super(init.message, init.cause === undefined ? undefined : { cause: init.cause })\n this.code = init.code\n this.status = init.status\n this.retryable = init.retryable\n this.retryAfterSeconds = init.retryAfterSeconds\n this.requestId = init.requestId\n this.logUrl = init.logUrl\n this.details = init.details\n }\n}\n\nexport function isRascadorError(error: unknown): error is RascadorError {\n return error instanceof RascadorError\n}\n","import type { Response } from \"./types.ts\"\n\ntype FetchPage<T> = (offset: number | undefined) => Promise<Response<T[]>>\n\n/**\n * A single page you can `await`, or every item you can `for await`.\n *\n * `await list(...)` makes exactly one request. Iterating makes as many as\n * `meta.pagination.has_more` calls for, starting from the offset you passed.\n */\nexport class PagePromise<T> implements PromiseLike<Response<T[]>>, AsyncIterable<T> {\n readonly #fetchPage: FetchPage<T>\n readonly #startOffset: number | undefined\n #first: Promise<Response<T[]>> | undefined\n\n constructor(fetchPage: FetchPage<T>, startOffset: number | undefined) {\n this.#fetchPage = fetchPage\n this.#startOffset = startOffset\n }\n\n // Lazy, so a list you only iterate doesn't also fire a request for `then`.\n #firstPage(): Promise<Response<T[]>> {\n this.#first ??= this.#fetchPage(this.#startOffset)\n return this.#first\n }\n\n then<R1 = Response<T[]>, R2 = never>(\n onfulfilled?: ((value: Response<T[]>) => R1 | PromiseLike<R1>) | null,\n onrejected?: ((reason: unknown) => R2 | PromiseLike<R2>) | null\n ): Promise<R1 | R2> {\n return this.#firstPage().then(onfulfilled, onrejected)\n }\n\n catch<R = never>(onrejected?: ((reason: unknown) => R | PromiseLike<R>) | null): Promise<Response<T[]> | R> {\n return this.#firstPage().catch(onrejected)\n }\n\n finally(onfinally?: (() => void) | null): Promise<Response<T[]>> {\n return this.#firstPage().finally(onfinally)\n }\n\n /** Every page, in order. */\n async *pages(): AsyncGenerator<Response<T[]>, void, undefined> {\n let page = await this.#firstPage()\n for (;;) {\n yield page\n const p = page.meta.pagination\n if (!p?.has_more || p.limit <= 0) return\n // Advance by the page size, not by what came back: with `only_new` a page\n // can be shorter than `limit` without the listing being exhausted.\n page = await this.#fetchPage(p.offset + p.limit)\n }\n }\n\n async *[Symbol.asyncIterator](): AsyncIterator<T> {\n for await (const page of this.pages()) yield* page.data\n }\n}\n","// Written by scripts/sync-version.mjs from package.json. Do not edit by hand.\nexport const VERSION = \"0.1.0\"\n","import { RascadorError } from \"./errors.ts\"\nimport { PagePromise } from \"./pagination.ts\"\nimport type {\n Category,\n GetProductParams,\n ListCategoriesParams,\n ListProductsParams,\n Me,\n Product,\n RequestOptions,\n Response,\n SearchHit,\n SearchParams,\n Source,\n SourceId,\n} from \"./types.ts\"\nimport { VERSION } from \"./version.ts\"\n\nexport const DEFAULT_BASE_URL = \"https://api.rascador.store\"\n\n/** Stored-data reads answer in well under a second. */\nconst DEFAULT_TIMEOUT_MS = 30_000\n/** Live scrapes drive a real browser: 20-40s typical, up to ~90s for a product refresh. */\nconst DEFAULT_LIVE_TIMEOUT_MS = 130_000\nconst DEFAULT_MAX_RETRIES = 2\nconst MAX_BACKOFF_MS = 8_000\n\nexport interface RascadorOptions {\n /** Defaults to the `RASCADOR_API_KEY` environment variable where one exists. */\n apiKey?: string\n /** Defaults to the production gateway. */\n baseUrl?: string\n /** Source used when a call doesn't name one. The gateway's own default is `shein`. */\n source?: SourceId\n /** Timeout for stored-data calls. Default 30s. */\n timeoutMs?: number\n /** Timeout for live calls (`search`, `products.get` with `refresh`). Default 130s. */\n liveTimeoutMs?: number\n /** Retries for errors the gateway marks retryable. Default 2. */\n maxRetries?: number\n /** Custom fetch, e.g. for a proxy or tests. Defaults to the global `fetch`. */\n fetch?: typeof fetch\n /** Extra headers sent with every request. */\n headers?: Record<string, string>\n}\n\ntype Query = Record<string, string | number | boolean | undefined>\n\ninterface CallSpec {\n path: string\n query?: Query\n live?: boolean\n options?: RequestOptions\n}\n\nfunction readEnvKey(): string | undefined {\n // `process` doesn't exist in browsers or Deno without the node shim.\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env\n return env?.RASCADOR_API_KEY\n}\n\nexport class Rascador {\n readonly #apiKey: string\n readonly #baseUrl: string\n readonly #source: SourceId | undefined\n readonly #timeoutMs: number\n readonly #liveTimeoutMs: number\n readonly #maxRetries: number\n readonly #fetch: typeof fetch\n readonly #headers: Record<string, string>\n\n readonly products: ProductsResource\n readonly categories: CategoriesResource\n readonly sources: SourcesResource\n\n constructor(options: RascadorOptions = {}) {\n const apiKey = options.apiKey ?? readEnvKey()\n if (!apiKey) {\n throw new RascadorError({\n code: \"missing_credentials\",\n message: \"No API key. Pass `apiKey` or set the RASCADOR_API_KEY environment variable.\",\n status: 0,\n retryable: false,\n })\n }\n\n const fetchImpl = options.fetch ?? globalThis.fetch\n if (typeof fetchImpl !== \"function\") {\n throw new TypeError(\"No global fetch found. Pass `fetch` in the options (Node 18+ has one built in).\")\n }\n\n this.#apiKey = apiKey\n this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\")\n this.#source = options.source\n this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS\n this.#liveTimeoutMs = options.liveTimeoutMs ?? DEFAULT_LIVE_TIMEOUT_MS\n this.#maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES\n // Bound so a detached `fetch` (e.g. window.fetch) keeps its receiver.\n this.#fetch = fetchImpl.bind(globalThis)\n this.#headers = options.headers ?? {}\n\n this.products = new ProductsResource(this)\n this.categories = new CategoriesResource(this)\n this.sources = new SourcesResource(this)\n }\n\n /** The key's scopes, limits and remaining quota. Never spends quota. */\n me(options?: RequestOptions): Promise<Response<Me>> {\n return this._get({ path: \"/v1/me\", options })\n }\n\n /**\n * Live search on the source site. Slow (20–40s) and quota-heavy.\n * Returns result cards; call `products.get` for full details.\n */\n search(params: SearchParams, options?: RequestOptions): Promise<Response<SearchHit[]>> {\n const { source, ...query } = params\n return this._get({ path: \"/v1/search\", query: { ...query, source: this._source(source) }, live: true, options })\n }\n\n /** @internal */\n _source(source: SourceId | undefined): SourceId | undefined {\n return source ?? this.#source\n }\n\n /** @internal */\n async _get<T>(spec: CallSpec): Promise<Response<T>> {\n const url = this.#url(spec.path, spec.query)\n const maxRetries = spec.options?.maxRetries ?? this.#maxRetries\n const timeoutMs = spec.options?.timeoutMs ?? (spec.live ? this.#liveTimeoutMs : this.#timeoutMs)\n\n for (let attempt = 0; ; attempt++) {\n try {\n return await this.#once<T>(url, timeoutMs, spec.options?.signal)\n } catch (error) {\n if (!(error instanceof RascadorError) || !error.retryable || attempt >= maxRetries) throw error\n await sleep(backoffMs(attempt, error.retryAfterSeconds), spec.options?.signal)\n }\n }\n }\n\n #url(path: string, query: Query | undefined): string {\n const url = new URL(this.#baseUrl + path)\n for (const [key, value] of Object.entries(query ?? {})) {\n if (value !== undefined) url.searchParams.set(key, String(value))\n }\n return url.toString()\n }\n\n async #once<T>(url: string, timeoutMs: number, signal: AbortSignal | undefined): Promise<Response<T>> {\n const { signal: combined, cleanup, timedOut } = withTimeout(timeoutMs, signal)\n\n let res: globalThis.Response\n try {\n res = await this.#fetch(url, {\n method: \"GET\",\n headers: {\n ...this.#headers,\n accept: \"application/json\",\n authorization: `Bearer ${this.#apiKey}`,\n },\n signal: combined,\n })\n } catch (cause) {\n cleanup()\n if (timedOut()) {\n // Not retried: a live scrape that outran us may still be running\n // upstream, and firing another would only stack a second one on it.\n throw new RascadorError({\n code: \"timeout\",\n message: `No response within ${timeoutMs}ms.`,\n status: 0,\n retryable: false,\n cause,\n })\n }\n if (signal?.aborted) throw cause\n throw new RascadorError({\n code: \"network_error\",\n message: cause instanceof Error ? cause.message : \"The request could not be sent.\",\n status: 0,\n retryable: true,\n cause,\n })\n }\n\n let body: unknown\n try {\n const text = await res.text()\n body = text ? JSON.parse(text) : null\n } catch (cause) {\n if (timedOut()) {\n throw new RascadorError({\n code: \"timeout\",\n message: `The response did not finish within ${timeoutMs}ms.`,\n status: res.status,\n retryable: false,\n cause,\n })\n }\n if (signal?.aborted) throw cause\n throw new RascadorError({\n code: \"invalid_response\",\n message: `Expected JSON from the gateway (HTTP ${res.status}).`,\n status: res.status,\n retryable: res.status >= 500,\n cause,\n })\n } finally {\n cleanup()\n }\n\n if (!res.ok) throw errorFrom(res, body)\n\n if (!body || typeof body !== \"object\" || !(\"data\" in body)) {\n throw new RascadorError({\n code: \"invalid_response\",\n message: \"The gateway response had no `data` field.\",\n status: res.status,\n retryable: false,\n })\n }\n return body as Response<T>\n }\n\n /** The SDK version. Include it in bug reports. */\n static readonly VERSION: string = VERSION\n}\n\nexport class ProductsResource {\n readonly #client: Rascador\n constructor(client: Rascador) {\n this.#client = client\n }\n\n /**\n * One product. Instant when stored; otherwise (or with `refresh: true`) a\n * live fetch, which needs the `search:live` scope.\n */\n get(id: string | number, params: GetProductParams = {}, options?: RequestOptions): Promise<Response<Product>> {\n const { source, ...query } = params\n return this.#client._get({\n path: `/v1/products/${encodeURIComponent(String(id))}`,\n query: { ...query, source: this.#client._source(source) },\n live: params.refresh === true,\n options,\n })\n }\n\n /** Stored products. `await` for one page, `for await` for every product. */\n list(params: ListProductsParams = {}, options?: RequestOptions): PagePromise<Product> {\n const { source, offset, ...query } = params\n return new PagePromise<Product>(\n (next) =>\n this.#client._get({\n path: \"/v1/products\",\n query: { ...query, offset: next, source: this.#client._source(source) },\n options,\n }),\n offset\n )\n }\n}\n\nexport class CategoriesResource {\n readonly #client: Rascador\n constructor(client: Rascador) {\n this.#client = client\n }\n\n /** The source's category tree. `await` for one page, `for await` for all. */\n list(params: ListCategoriesParams = {}, options?: RequestOptions): PagePromise<Category> {\n const { source, offset, ...query } = params\n return new PagePromise<Category>(\n (next) =>\n this.#client._get({\n path: \"/v1/categories\",\n query: { ...query, offset: next, source: this.#client._source(source) },\n options,\n }),\n offset\n )\n }\n}\n\nexport class SourcesResource {\n readonly #client: Rascador\n constructor(client: Rascador) {\n this.#client = client\n }\n\n /** Every source and what it supports. Never spends quota. */\n list(options?: RequestOptions): Promise<Response<Source[]>> {\n return this.#client._get({ path: \"/v1/sources\", options })\n }\n}\n\nfunction errorFrom(res: globalThis.Response, body: unknown): RascadorError {\n const e = (body as { error?: Record<string, unknown> } | null)?.error\n const headerRetry = Number(res.headers.get(\"retry-after\"))\n\n if (!e || typeof e.code !== \"string\") {\n return new RascadorError({\n code: res.status >= 500 ? \"internal_error\" : \"invalid_response\",\n message: `The gateway returned HTTP ${res.status} without an error body.`,\n status: res.status,\n retryable: res.status >= 500 || res.status === 429,\n retryAfterSeconds: Number.isFinite(headerRetry) && headerRetry > 0 ? headerRetry : undefined,\n })\n }\n\n const retryAfter =\n typeof e.retry_after_seconds === \"number\"\n ? e.retry_after_seconds\n : Number.isFinite(headerRetry) && headerRetry > 0\n ? headerRetry\n : undefined\n\n return new RascadorError({\n code: e.code,\n message: typeof e.message === \"string\" ? e.message : e.code,\n status: res.status,\n retryable: e.retryable === true,\n retryAfterSeconds: retryAfter,\n requestId: typeof e.request_id === \"string\" ? e.request_id : undefined,\n logUrl: typeof e.log_url === \"string\" ? e.log_url : undefined,\n details: (e.details as Record<string, unknown> | undefined) ?? undefined,\n })\n}\n\n/** Exponential backoff with full jitter, never shorter than what the gateway asked for. */\nfunction backoffMs(attempt: number, retryAfterSeconds: number | undefined): number {\n const jittered = Math.random() * Math.min(MAX_BACKOFF_MS, 500 * 2 ** attempt)\n return Math.max(jittered, (retryAfterSeconds ?? 0) * 1000)\n}\n\nfunction sleep(ms: number, signal: AbortSignal | undefined): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) return reject(signal.reason)\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort)\n resolve()\n }, ms)\n const onAbort = () => {\n clearTimeout(timer)\n reject(signal?.reason)\n }\n signal?.addEventListener(\"abort\", onAbort, { once: true })\n })\n}\n\n/** AbortSignal.any isn't in Node 18, so combine the caller's signal and the timeout by hand. */\nfunction withTimeout(\n ms: number,\n signal: AbortSignal | undefined\n): { signal: AbortSignal; cleanup: () => void; timedOut: () => boolean } {\n const controller = new AbortController()\n let fired = false\n const timer = setTimeout(() => {\n fired = true\n controller.abort()\n }, ms)\n const onAbort = () => controller.abort(signal?.reason)\n if (signal?.aborted) controller.abort(signal.reason)\n else signal?.addEventListener(\"abort\", onAbort, { once: true })\n\n return {\n signal: controller.signal,\n cleanup: () => {\n clearTimeout(timer)\n signal?.removeEventListener(\"abort\", onAbort)\n },\n timedOut: () => fired,\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@torvion/rascador",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official TypeScript SDK for the Rascador product data API: SHEIN, AliExpress, Amazon and Back Market.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Torvion",
|
|
7
|
+
"homepage": "https://rascador.store/developers/sdk",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Torvion-Labs/rascador-js.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": "https://github.com/Torvion-Labs/rascador-js/issues",
|
|
13
|
+
"keywords": ["rascador", "scraping", "shein", "aliexpress", "amazon", "backmarket", "product-data", "sdk"],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"main": "./dist/index.cjs",
|
|
17
|
+
"module": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"import": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"require": {
|
|
26
|
+
"types": "./dist/index.d.cts",
|
|
27
|
+
"default": "./dist/index.cjs"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"test:watch": "vitest",
|
|
41
|
+
"lint:package": "publint && attw --pack .",
|
|
42
|
+
"changeset": "changeset",
|
|
43
|
+
"version": "changeset version && node scripts/sync-version.mjs",
|
|
44
|
+
"release": "npm run build && changeset publish",
|
|
45
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public",
|
|
49
|
+
"provenance": true
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@arethetypeswrong/cli": "^0.18.5",
|
|
53
|
+
"@changesets/cli": "^2.27.0",
|
|
54
|
+
"@types/node": "^22.0.0",
|
|
55
|
+
"publint": "^0.3.0",
|
|
56
|
+
"tsup": "^8.3.0",
|
|
57
|
+
"typescript": "^5.7.0",
|
|
58
|
+
"vitest": "^3.0.0"
|
|
59
|
+
}
|
|
60
|
+
}
|