@usethrottle/catalog 0.1.1 → 1.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 +18 -0
- package/README.md +142 -11
- package/dist/admin.cjs +681 -0
- package/dist/admin.d.cts +660 -0
- package/dist/admin.d.ts +660 -0
- package/dist/admin.js +681 -0
- package/dist/{catalog-error-BWUoZCzY.d.ts → catalog-error-C_zLOtYA.d.ts} +3 -3
- package/dist/{catalog-error-DO6ANrGR.d.cts → catalog-error-s1BINNkt.d.cts} +3 -3
- package/dist/{chunk-CR7ILYCT.cjs → chunk-4ORXUW2U.cjs} +4 -2
- package/dist/{chunk-I3QRBPWD.js → chunk-JK2UY4CD.js} +3 -1
- package/dist/{client-BNtBTEyh.d.cts → client-BAoq6jmM.d.cts} +1 -1
- package/dist/{client-CHLvULRQ.d.ts → client-c2F-eCaL.d.ts} +1 -1
- package/dist/{types-DYfIvIaI.d.cts → contract-types-DhiThMEr.d.cts} +1 -272
- package/dist/{types-DYfIvIaI.d.ts → contract-types-DhiThMEr.d.ts} +1 -272
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +1 -1
- package/dist/react.cjs +36 -25
- package/dist/react.d.cts +11 -5
- package/dist/react.d.ts +11 -5
- package/dist/react.js +20 -9
- package/dist/server.cjs +14 -13
- package/dist/server.d.cts +3 -2
- package/dist/server.d.ts +3 -2
- package/dist/server.js +3 -2
- package/dist/types-DsjoG8ce.d.cts +276 -0
- package/dist/types-xQLID-4j.d.ts +276 -0
- package/dist/url.d.cts +1 -0
- package/dist/url.d.ts +1 -0
- package/package.json +11 -1
package/dist/admin.cjs
ADDED
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
var _chunk4ORXUW2Ucjs = require('./chunk-4ORXUW2U.cjs');
|
|
14
|
+
require('./chunk-ETV4XYOV.cjs');
|
|
15
|
+
|
|
16
|
+
// src/generated/push-types.ts
|
|
17
|
+
var PUSH_ERRORS = {
|
|
18
|
+
AUTHENTICATION_FAILED: { statuses: [401], retryable: false },
|
|
19
|
+
VENDOR_PAUSED: { statuses: [403], retryable: false },
|
|
20
|
+
VENDOR_KEY_NOT_ALLOWED: { statuses: [403], retryable: false },
|
|
21
|
+
INVALID_REQUEST: { statuses: [400], retryable: false },
|
|
22
|
+
IDEMPOTENCY_KEY_REQUIRED: { statuses: [400], retryable: false },
|
|
23
|
+
NOT_FOUND: { statuses: [404], retryable: false },
|
|
24
|
+
PROFILE_NOT_FOUND: { statuses: [404], retryable: false },
|
|
25
|
+
VENDOR_NOT_FOUND: { statuses: [404], retryable: false },
|
|
26
|
+
IDEMPOTENCY_CONFLICT: { statuses: [409], retryable: false },
|
|
27
|
+
RUN_IN_PROGRESS: { statuses: [409], retryable: true },
|
|
28
|
+
JOB_CLOSED: { statuses: [409], retryable: false },
|
|
29
|
+
JOB_FULL: { statuses: [409], retryable: false },
|
|
30
|
+
TOO_MANY_OPEN_JOBS: { statuses: [409], retryable: true },
|
|
31
|
+
REPORT_TOO_LARGE: { statuses: [409], retryable: false },
|
|
32
|
+
HANDLE_TAKEN: { statuses: [409], retryable: false },
|
|
33
|
+
IDENTIFIER_LOCKED: { statuses: [409], retryable: false },
|
|
34
|
+
REPORT_EXPIRED: { statuses: [410], retryable: false },
|
|
35
|
+
REQUEST_BODY_TOO_LARGE: { statuses: [413], retryable: false },
|
|
36
|
+
MAPPING_INVALID: { statuses: [422], retryable: false },
|
|
37
|
+
RATE_LIMITED: { statuses: [429], retryable: true },
|
|
38
|
+
PUSH_QUEUE_FULL: { statuses: [429], retryable: true },
|
|
39
|
+
INTERNAL_ERROR: { statuses: [500], retryable: true },
|
|
40
|
+
INFRASTRUCTURE_UNAVAILABLE: { statuses: [503], retryable: true }
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// src/admin/http.ts
|
|
44
|
+
var MAX_IDEMPOTENCY_KEY = 200;
|
|
45
|
+
var HEADER_SAFE = /^[\x21-\x7e]+$/u;
|
|
46
|
+
var MAX_RETRY_AFTER_S = 86400;
|
|
47
|
+
function invalidRequest(message) {
|
|
48
|
+
return new (0, _chunk4ORXUW2Ucjs.CatalogError)({
|
|
49
|
+
code: "INVALID_REQUEST",
|
|
50
|
+
message,
|
|
51
|
+
statusCode: 0,
|
|
52
|
+
source: "client",
|
|
53
|
+
retryable: false
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function clientError(code, message) {
|
|
57
|
+
return new (0, _chunk4ORXUW2Ucjs.CatalogError)({
|
|
58
|
+
code,
|
|
59
|
+
message,
|
|
60
|
+
statusCode: 0,
|
|
61
|
+
source: "client",
|
|
62
|
+
retryable: false
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function resolveIdempotencyKey(key) {
|
|
66
|
+
if (key === void 0) return globalThis.crypto.randomUUID();
|
|
67
|
+
if (typeof key !== "string" || key.length === 0 || key.length > MAX_IDEMPOTENCY_KEY || !HEADER_SAFE.test(key))
|
|
68
|
+
throw invalidRequest(
|
|
69
|
+
`idempotencyKey must be 1 to ${MAX_IDEMPOTENCY_KEY} visible characters with no spaces.`
|
|
70
|
+
);
|
|
71
|
+
return key;
|
|
72
|
+
}
|
|
73
|
+
function queryString(pairs) {
|
|
74
|
+
if (pairs === void 0 || pairs.length === 0) return "";
|
|
75
|
+
return `?${pairs.map(
|
|
76
|
+
([name, value]) => `${encodeURIComponent(name)}=${encodeURIComponent(value)}`
|
|
77
|
+
).join("&")}`;
|
|
78
|
+
}
|
|
79
|
+
function isRecord(value) {
|
|
80
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
81
|
+
}
|
|
82
|
+
function parseJson(text) {
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(text);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
return void 0;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function issuesOf(value) {
|
|
90
|
+
if (!Array.isArray(value)) return [];
|
|
91
|
+
return value.flatMap(
|
|
92
|
+
(issue) => isRecord(issue) && typeof issue.path === "string" && typeof issue.message === "string" ? [{ path: issue.path, message: issue.message }] : []
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
function retryAfterOf(headers) {
|
|
96
|
+
const value = _nullishCoalesce(_optionalChain([headers, 'access', _2 => _2.get, 'call', _3 => _3("retry-after"), 'optionalAccess', _4 => _4.trim, 'call', _5 => _5()]), () => ( ""));
|
|
97
|
+
if (!/^\d{1,6}$/u.test(value)) return null;
|
|
98
|
+
const seconds = Number(value);
|
|
99
|
+
return seconds <= MAX_RETRY_AFTER_S ? seconds : null;
|
|
100
|
+
}
|
|
101
|
+
function isPushCode(code) {
|
|
102
|
+
return Object.prototype.hasOwnProperty.call(PUSH_ERRORS, code);
|
|
103
|
+
}
|
|
104
|
+
function toError(status, headers, text) {
|
|
105
|
+
const body = parseJson(text);
|
|
106
|
+
const envelope = isRecord(body) && isRecord(body.error) ? body.error : void 0;
|
|
107
|
+
const code = typeof _optionalChain([envelope, 'optionalAccess', _6 => _6.code]) === "string" && envelope.code !== "" ? envelope.code : void 0;
|
|
108
|
+
const retryAfter = retryAfterOf(headers);
|
|
109
|
+
const requestId = typeof _optionalChain([envelope, 'optionalAccess', _7 => _7.requestId]) === "string" && envelope.requestId !== "" ? envelope.requestId : _nullishCoalesce(headers.get("x-request-id"), () => ( void 0));
|
|
110
|
+
const init = envelope !== void 0 && code !== void 0 ? {
|
|
111
|
+
code,
|
|
112
|
+
message: typeof envelope.message === "string" && envelope.message !== "" ? envelope.message : `Request failed with status ${status}.`,
|
|
113
|
+
statusCode: status,
|
|
114
|
+
source: "catalog",
|
|
115
|
+
retryable: isPushCode(code) ? PUSH_ERRORS[code].retryable : status === 429 || status === 503,
|
|
116
|
+
retryAfterSeconds: retryAfter,
|
|
117
|
+
issues: issuesOf(envelope.issues),
|
|
118
|
+
body,
|
|
119
|
+
requestId
|
|
120
|
+
} : {
|
|
121
|
+
code: "INVALID_RESPONSE",
|
|
122
|
+
message: `The server answered ${status} with an unreadable body.`,
|
|
123
|
+
statusCode: status,
|
|
124
|
+
source: "catalog",
|
|
125
|
+
retryAfterSeconds: retryAfter,
|
|
126
|
+
requestId
|
|
127
|
+
};
|
|
128
|
+
if (status === 429) {
|
|
129
|
+
return new (0, _chunk4ORXUW2Ucjs.RateLimitedError)({
|
|
130
|
+
...init,
|
|
131
|
+
retryable: true,
|
|
132
|
+
retryAfterSeconds: retryAfter !== null && retryAfter >= 1 ? retryAfter : _chunk4ORXUW2Ucjs.RATE_LIMIT_DEFAULT_RETRY_S
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return new (0, _chunk4ORXUW2Ucjs.CatalogError)(init);
|
|
136
|
+
}
|
|
137
|
+
function guarded(work, callerSignal, timeoutMs) {
|
|
138
|
+
if (_optionalChain([callerSignal, 'optionalAccess', _8 => _8.aborted])) return Promise.reject(callerSignal.reason);
|
|
139
|
+
const controller = new AbortController();
|
|
140
|
+
return new Promise((resolve, reject) => {
|
|
141
|
+
const finish = () => {
|
|
142
|
+
clearTimeout(timer);
|
|
143
|
+
_optionalChain([callerSignal, 'optionalAccess', _9 => _9.removeEventListener, 'call', _10 => _10("abort", onAbort)]);
|
|
144
|
+
};
|
|
145
|
+
const onAbort = () => {
|
|
146
|
+
finish();
|
|
147
|
+
const reason = _optionalChain([callerSignal, 'optionalAccess', _11 => _11.reason]);
|
|
148
|
+
controller.abort(reason);
|
|
149
|
+
reject(reason);
|
|
150
|
+
};
|
|
151
|
+
const timer = setTimeout(() => {
|
|
152
|
+
finish();
|
|
153
|
+
controller.abort();
|
|
154
|
+
reject(new (0, _chunk4ORXUW2Ucjs.TimeoutError)({ timeoutMs }));
|
|
155
|
+
}, timeoutMs);
|
|
156
|
+
_optionalChain([callerSignal, 'optionalAccess', _12 => _12.addEventListener, 'call', _13 => _13("abort", onAbort, { once: true })]);
|
|
157
|
+
work(controller.signal).then(
|
|
158
|
+
(value) => {
|
|
159
|
+
finish();
|
|
160
|
+
resolve(value);
|
|
161
|
+
},
|
|
162
|
+
(error) => {
|
|
163
|
+
finish();
|
|
164
|
+
reject(error);
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function createHttp(key, baseUrl, fetchFn, timeoutMs) {
|
|
170
|
+
async function exchange(spec, accept) {
|
|
171
|
+
const headers = {
|
|
172
|
+
authorization: `Bearer ${key}`,
|
|
173
|
+
accept
|
|
174
|
+
};
|
|
175
|
+
let body;
|
|
176
|
+
if (spec.body !== void 0) {
|
|
177
|
+
headers["content-type"] = "application/json";
|
|
178
|
+
body = JSON.stringify(spec.body);
|
|
179
|
+
}
|
|
180
|
+
if (spec.idempotencyKey !== void 0)
|
|
181
|
+
headers["idempotency-key"] = spec.idempotencyKey;
|
|
182
|
+
const url = baseUrl + spec.path + queryString(spec.query);
|
|
183
|
+
const attempts = spec.idempotencyKey === void 0 ? 1 : 2;
|
|
184
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
185
|
+
try {
|
|
186
|
+
return await guarded(
|
|
187
|
+
async (signal) => {
|
|
188
|
+
let response;
|
|
189
|
+
try {
|
|
190
|
+
response = await fetchFn(url, {
|
|
191
|
+
method: spec.method,
|
|
192
|
+
headers,
|
|
193
|
+
...body === void 0 ? {} : { body },
|
|
194
|
+
signal
|
|
195
|
+
});
|
|
196
|
+
} catch (cause) {
|
|
197
|
+
throw new (0, _chunk4ORXUW2Ucjs.NetworkError)({ cause });
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
return {
|
|
201
|
+
status: response.status,
|
|
202
|
+
headers: response.headers,
|
|
203
|
+
text: await response.text()
|
|
204
|
+
};
|
|
205
|
+
} catch (cause) {
|
|
206
|
+
throw new (0, _chunk4ORXUW2Ucjs.NetworkError)({ cause });
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
spec.signal,
|
|
210
|
+
timeoutMs
|
|
211
|
+
);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
if (error instanceof _chunk4ORXUW2Ucjs.NetworkError && attempt < attempts) continue;
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
keyKind: key.startsWith("pc_sk_") ? "store" : "vendor",
|
|
220
|
+
async json(spec) {
|
|
221
|
+
const answer = await exchange(spec, "application/json");
|
|
222
|
+
if (answer.status < 200 || answer.status >= 300)
|
|
223
|
+
throw toError(answer.status, answer.headers, answer.text);
|
|
224
|
+
const parsed = parseJson(answer.text);
|
|
225
|
+
if (parsed === void 0)
|
|
226
|
+
throw new (0, _chunk4ORXUW2Ucjs.CatalogError)({
|
|
227
|
+
code: "INVALID_RESPONSE",
|
|
228
|
+
message: "The server answered with a body that is not JSON.",
|
|
229
|
+
statusCode: answer.status,
|
|
230
|
+
source: "catalog"
|
|
231
|
+
});
|
|
232
|
+
return parsed;
|
|
233
|
+
},
|
|
234
|
+
async text(spec) {
|
|
235
|
+
const answer = await exchange(spec, "text/csv");
|
|
236
|
+
if (answer.status < 200 || answer.status >= 300)
|
|
237
|
+
throw toError(answer.status, answer.headers, answer.text);
|
|
238
|
+
return answer.text;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/admin/inventory.ts
|
|
244
|
+
function createInventory(http) {
|
|
245
|
+
return {
|
|
246
|
+
set: (rows) => http.json({
|
|
247
|
+
method: "PATCH",
|
|
248
|
+
path: "/v1/catalog/inventory",
|
|
249
|
+
body: { rows }
|
|
250
|
+
}),
|
|
251
|
+
async adjust(rows, options) {
|
|
252
|
+
const idempotencyKey = resolveIdempotencyKey(_optionalChain([options, 'optionalAccess', _14 => _14.idempotencyKey]));
|
|
253
|
+
const adjusted = rows.map((row) => ({
|
|
254
|
+
...row,
|
|
255
|
+
quantityMode: "adjust"
|
|
256
|
+
}));
|
|
257
|
+
return http.json({
|
|
258
|
+
method: "PATCH",
|
|
259
|
+
path: "/v1/catalog/inventory",
|
|
260
|
+
body: { rows: adjusted },
|
|
261
|
+
idempotencyKey
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/admin/products.ts
|
|
268
|
+
var REF_FIELDS = ["externalId", "sku", "handle"];
|
|
269
|
+
function refQuery(ref) {
|
|
270
|
+
const record = ref;
|
|
271
|
+
const present = REF_FIELDS.filter((field2) => record[field2] !== void 0);
|
|
272
|
+
const [field] = present;
|
|
273
|
+
const value = field === void 0 ? void 0 : record[field];
|
|
274
|
+
if (present.length !== 1 || field === void 0 || typeof value !== "string" || value === "")
|
|
275
|
+
throw invalidRequest(
|
|
276
|
+
"A product ref is exactly one of externalId, sku or handle, as a non-empty string."
|
|
277
|
+
);
|
|
278
|
+
return [[field, value]];
|
|
279
|
+
}
|
|
280
|
+
function unreadable() {
|
|
281
|
+
return new (0, _chunk4ORXUW2Ucjs.CatalogError)({
|
|
282
|
+
code: "INVALID_RESPONSE",
|
|
283
|
+
message: "The server answered with an unexpected shape.",
|
|
284
|
+
statusCode: 200,
|
|
285
|
+
source: "catalog"
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
function createProducts(http) {
|
|
289
|
+
const op = (name) => (refs) => http.json({
|
|
290
|
+
method: "POST",
|
|
291
|
+
path: `/v1/catalog/products/${name}`,
|
|
292
|
+
body: { products: refs }
|
|
293
|
+
});
|
|
294
|
+
return {
|
|
295
|
+
async get(ref) {
|
|
296
|
+
const answer = await http.json({
|
|
297
|
+
method: "GET",
|
|
298
|
+
path: "/v1/catalog/products",
|
|
299
|
+
query: refQuery(ref)
|
|
300
|
+
});
|
|
301
|
+
if ("product" in answer) return answer.product;
|
|
302
|
+
throw unreadable();
|
|
303
|
+
},
|
|
304
|
+
async list(options = {}) {
|
|
305
|
+
const query = [];
|
|
306
|
+
if (options.cursor !== void 0) query.push(["cursor", options.cursor]);
|
|
307
|
+
if (options.limit !== void 0)
|
|
308
|
+
query.push(["limit", String(options.limit)]);
|
|
309
|
+
const answer = await http.json({
|
|
310
|
+
method: "GET",
|
|
311
|
+
path: "/v1/catalog/products",
|
|
312
|
+
query
|
|
313
|
+
});
|
|
314
|
+
if ("products" in answer)
|
|
315
|
+
return { products: answer.products, nextCursor: answer.nextCursor };
|
|
316
|
+
throw unreadable();
|
|
317
|
+
},
|
|
318
|
+
archive: op("archive"),
|
|
319
|
+
restore: op("restore"),
|
|
320
|
+
async rename(ref, handle) {
|
|
321
|
+
const body = { product: ref, handle };
|
|
322
|
+
const answer = await http.json({
|
|
323
|
+
method: "POST",
|
|
324
|
+
path: "/v1/catalog/products/rename",
|
|
325
|
+
body
|
|
326
|
+
});
|
|
327
|
+
return answer.product;
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/admin/runs.ts
|
|
333
|
+
var MAX_BATCH_ROWS = 1e3;
|
|
334
|
+
var MAX_BATCH_BYTES = 5e6;
|
|
335
|
+
var ENVELOPE_BYTES = '{"rows":[]}'.length;
|
|
336
|
+
var STOCK_PAGE_LIMIT = 500;
|
|
337
|
+
var QUEUE_RETRIES = 3;
|
|
338
|
+
var DEFAULT_INTERVAL_MS = 5e3;
|
|
339
|
+
var DEFAULT_WAIT_MS = 864e5;
|
|
340
|
+
function sleep(ms, signal) {
|
|
341
|
+
if (_optionalChain([signal, 'optionalAccess', _15 => _15.aborted])) return Promise.reject(signal.reason);
|
|
342
|
+
return new Promise((resolve, reject) => {
|
|
343
|
+
const onAbort = () => {
|
|
344
|
+
clearTimeout(timer);
|
|
345
|
+
reject(_optionalChain([signal, 'optionalAccess', _16 => _16.reason]));
|
|
346
|
+
};
|
|
347
|
+
const timer = setTimeout(() => {
|
|
348
|
+
_optionalChain([signal, 'optionalAccess', _17 => _17.removeEventListener, 'call', _18 => _18("abort", onAbort)]);
|
|
349
|
+
resolve();
|
|
350
|
+
}, ms);
|
|
351
|
+
_optionalChain([signal, 'optionalAccess', _19 => _19.addEventListener, 'call', _20 => _20("abort", onAbort, { once: true })]);
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
function checkMode(mode) {
|
|
355
|
+
if (mode !== void 0 && mode !== "merge" && mode !== "replace")
|
|
356
|
+
throw invalidRequest("mode must be 'merge' or 'replace'.");
|
|
357
|
+
}
|
|
358
|
+
async function pollUntil(read, done, options = {}) {
|
|
359
|
+
const intervalMs = _nullishCoalesce(options.intervalMs, () => ( DEFAULT_INTERVAL_MS));
|
|
360
|
+
const timeoutMs = _nullishCoalesce(options.timeoutMs, () => ( DEFAULT_WAIT_MS));
|
|
361
|
+
if (!(intervalMs >= 0 && Number.isFinite(intervalMs)))
|
|
362
|
+
throw invalidRequest("intervalMs must be a finite number of 0 or more.");
|
|
363
|
+
if (!(timeoutMs > 0 && Number.isFinite(timeoutMs)))
|
|
364
|
+
throw invalidRequest("timeoutMs must be a finite number above 0.");
|
|
365
|
+
const deadline = Date.now() + timeoutMs;
|
|
366
|
+
for (; ; ) {
|
|
367
|
+
const value = await read(options.signal);
|
|
368
|
+
if (done(value)) return value;
|
|
369
|
+
const remaining = deadline - Date.now();
|
|
370
|
+
if (remaining <= 0) throw new (0, _chunk4ORXUW2Ucjs.TimeoutError)({ timeoutMs });
|
|
371
|
+
await sleep(Math.min(intervalMs, remaining), options.signal);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
async function* stockRows(http, base) {
|
|
375
|
+
let cursor = null;
|
|
376
|
+
do {
|
|
377
|
+
const query = [];
|
|
378
|
+
if (cursor !== null) query.push(["cursor", cursor]);
|
|
379
|
+
query.push(["limit", String(STOCK_PAGE_LIMIT)]);
|
|
380
|
+
const page = await http.json({
|
|
381
|
+
method: "GET",
|
|
382
|
+
path: `${base}/stock`,
|
|
383
|
+
query
|
|
384
|
+
});
|
|
385
|
+
yield* page.rows;
|
|
386
|
+
cursor = page.nextCursor;
|
|
387
|
+
} while (cursor !== null);
|
|
388
|
+
}
|
|
389
|
+
var encode = (id) => encodeURIComponent(id);
|
|
390
|
+
function createHandle(http, jobId) {
|
|
391
|
+
const base = `/v1/catalog/jobs/${encode(jobId)}`;
|
|
392
|
+
const read = async (options = {}, signal) => (await http.json({
|
|
393
|
+
method: "GET",
|
|
394
|
+
path: base,
|
|
395
|
+
...options.includeBatches === true ? { query: [["include", "batches"]] } : {},
|
|
396
|
+
signal
|
|
397
|
+
})).job;
|
|
398
|
+
return {
|
|
399
|
+
jobId,
|
|
400
|
+
async send(rows, options) {
|
|
401
|
+
return http.json({
|
|
402
|
+
method: "POST",
|
|
403
|
+
path: `${base}/batches`,
|
|
404
|
+
body: { rows },
|
|
405
|
+
idempotencyKey: resolveIdempotencyKey(_optionalChain([options, 'optionalAccess', _21 => _21.idempotencyKey]))
|
|
406
|
+
});
|
|
407
|
+
},
|
|
408
|
+
async close() {
|
|
409
|
+
return (await http.json({
|
|
410
|
+
method: "POST",
|
|
411
|
+
path: `${base}/close`
|
|
412
|
+
})).job;
|
|
413
|
+
},
|
|
414
|
+
get: (options) => read(options),
|
|
415
|
+
wait: (options) => pollUntil(
|
|
416
|
+
(signal) => read({}, signal),
|
|
417
|
+
(job) => job.finishedAt !== null || job.state === "expired",
|
|
418
|
+
options
|
|
419
|
+
),
|
|
420
|
+
errorsCsv: () => http.text({ method: "GET", path: `${base}/errors.csv` }),
|
|
421
|
+
stockResults: () => stockRows(http, base),
|
|
422
|
+
stockCsv: () => http.text({ method: "GET", path: `${base}/stock.csv` })
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
function productKey(row) {
|
|
426
|
+
const record = row;
|
|
427
|
+
for (const field of ["productHandle", "externalId"]) {
|
|
428
|
+
const value = record[field];
|
|
429
|
+
if (typeof value === "string" && value !== "") return `${field}:${value}`;
|
|
430
|
+
}
|
|
431
|
+
return `sku:${String(record.sku)}`;
|
|
432
|
+
}
|
|
433
|
+
var utf8 = new TextEncoder();
|
|
434
|
+
function createJobs(http) {
|
|
435
|
+
const open = async (options = {}) => {
|
|
436
|
+
checkMode(options.mode);
|
|
437
|
+
const body = {
|
|
438
|
+
...options.mode === void 0 ? {} : { mode: options.mode },
|
|
439
|
+
...options.fullSync === void 0 ? {} : { fullSync: options.fullSync }
|
|
440
|
+
};
|
|
441
|
+
const { job } = await http.json({
|
|
442
|
+
method: "POST",
|
|
443
|
+
path: "/v1/catalog/jobs",
|
|
444
|
+
body
|
|
445
|
+
});
|
|
446
|
+
return createHandle(http, job.jobId);
|
|
447
|
+
};
|
|
448
|
+
async function sendBatch(handle, rows, signal) {
|
|
449
|
+
const idempotencyKey = resolveIdempotencyKey(void 0);
|
|
450
|
+
for (let retry = 0; ; retry += 1) {
|
|
451
|
+
try {
|
|
452
|
+
await handle.send(rows, { idempotencyKey });
|
|
453
|
+
return;
|
|
454
|
+
} catch (error) {
|
|
455
|
+
if (!(error instanceof _chunk4ORXUW2Ucjs.RateLimitedError) || retry >= QUEUE_RETRIES)
|
|
456
|
+
throw error;
|
|
457
|
+
await sleep(error.retryAfterSeconds * 1e3, signal);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
open,
|
|
463
|
+
async get(jobId) {
|
|
464
|
+
const handle = createHandle(http, jobId);
|
|
465
|
+
await handle.get();
|
|
466
|
+
return handle;
|
|
467
|
+
},
|
|
468
|
+
resume: (jobId) => createHandle(http, jobId),
|
|
469
|
+
async run(rows, options = {}) {
|
|
470
|
+
checkMode(options.mode);
|
|
471
|
+
const batchRows = _nullishCoalesce(options.batchRows, () => ( MAX_BATCH_ROWS));
|
|
472
|
+
if (!Number.isInteger(batchRows) || batchRows < 1 || batchRows > MAX_BATCH_ROWS)
|
|
473
|
+
throw invalidRequest(
|
|
474
|
+
"batchRows must be a whole number from 1 to 1,000."
|
|
475
|
+
);
|
|
476
|
+
let handle;
|
|
477
|
+
let batch = [];
|
|
478
|
+
let batchBytes = ENVELOPE_BYTES;
|
|
479
|
+
let group = [];
|
|
480
|
+
let groupBytes = 0;
|
|
481
|
+
let groupKey = "";
|
|
482
|
+
const flush = async () => {
|
|
483
|
+
if (batch.length === 0) return;
|
|
484
|
+
await _asyncNullishCoalesce(handle, async () => ( (handle = await open({
|
|
485
|
+
...options.mode === void 0 ? {} : { mode: options.mode },
|
|
486
|
+
...options.fullSync === void 0 ? {} : { fullSync: options.fullSync }
|
|
487
|
+
}))));
|
|
488
|
+
const sending = batch;
|
|
489
|
+
batch = [];
|
|
490
|
+
batchBytes = ENVELOPE_BYTES;
|
|
491
|
+
await sendBatch(handle, sending, options.signal);
|
|
492
|
+
};
|
|
493
|
+
const place = async () => {
|
|
494
|
+
if (batch.length > 0 && (batch.length + group.length > batchRows || batchBytes + groupBytes > MAX_BATCH_BYTES))
|
|
495
|
+
await flush();
|
|
496
|
+
batch.push(...group);
|
|
497
|
+
batchBytes += groupBytes;
|
|
498
|
+
group = [];
|
|
499
|
+
groupBytes = 0;
|
|
500
|
+
};
|
|
501
|
+
for await (const row of rows) {
|
|
502
|
+
const key = productKey(row);
|
|
503
|
+
if (group.length > 0 && key !== groupKey) await place();
|
|
504
|
+
groupKey = key;
|
|
505
|
+
group.push(row);
|
|
506
|
+
groupBytes += utf8.encode(JSON.stringify(row)).length + 1;
|
|
507
|
+
if (group.length > MAX_BATCH_ROWS)
|
|
508
|
+
throw invalidRequest(
|
|
509
|
+
`A product may have at most ${MAX_BATCH_ROWS} rows in a job.`
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
if (group.length > 0) await place();
|
|
513
|
+
await flush();
|
|
514
|
+
if (handle === void 0)
|
|
515
|
+
throw invalidRequest("jobs.run needs at least one row.");
|
|
516
|
+
await handle.close();
|
|
517
|
+
return handle;
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
function createBatches(http) {
|
|
522
|
+
return {
|
|
523
|
+
async send(rows, options = {}) {
|
|
524
|
+
checkMode(options.mode);
|
|
525
|
+
if (options.mode !== void 0 && http.keyKind === "store")
|
|
526
|
+
throw invalidRequest(
|
|
527
|
+
"batches.send ignores mode for a store key (pc_sk_): the store's /variants route has no replace. Use jobs.run({ mode }) or a vendor key (pc_vk_)."
|
|
528
|
+
);
|
|
529
|
+
return http.json({
|
|
530
|
+
method: "POST",
|
|
531
|
+
path: "/v1/catalog/variants",
|
|
532
|
+
...options.mode === void 0 ? {} : { query: [["mode", options.mode]] },
|
|
533
|
+
body: { rows },
|
|
534
|
+
idempotencyKey: resolveIdempotencyKey(options.idempotencyKey)
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
function createRuns(http) {
|
|
540
|
+
const path = (runId) => `/v1/catalog/runs/${encode(runId)}`;
|
|
541
|
+
const get = async (runId, signal) => (await http.json({
|
|
542
|
+
method: "GET",
|
|
543
|
+
path: path(runId),
|
|
544
|
+
signal
|
|
545
|
+
})).run;
|
|
546
|
+
return {
|
|
547
|
+
get: (runId) => get(runId),
|
|
548
|
+
wait: (runId, options) => pollUntil(
|
|
549
|
+
(signal) => get(runId, signal),
|
|
550
|
+
(run) => run.status === "completed" || run.status === "failed",
|
|
551
|
+
options
|
|
552
|
+
),
|
|
553
|
+
errorsCsv: (runId) => http.text({ method: "GET", path: `${path(runId)}/errors.csv` }),
|
|
554
|
+
stockResults: (runId) => stockRows(http, path(runId)),
|
|
555
|
+
stockCsv: (runId) => http.text({ method: "GET", path: `${path(runId)}/stock.csv` })
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// src/admin/client.ts
|
|
560
|
+
var ADMIN_DEFAULT_TIMEOUT_MS = 6e4;
|
|
561
|
+
var MAX_TIMEOUT_MS = 2147483647;
|
|
562
|
+
function checkKey(key) {
|
|
563
|
+
if (typeof key === "string" && key.startsWith("pc_pk_")) {
|
|
564
|
+
throw clientError(
|
|
565
|
+
"INVALID_ADMIN_KEY",
|
|
566
|
+
"Publishable keys (pc_pk_) cannot use the admin API. Use a secret key (pc_sk_) or a vendor key (pc_vk_)."
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
if (typeof key !== "string" || !HEADER_SAFE.test(key) || !(key.startsWith("pc_sk_") || key.startsWith("pc_vk_"))) {
|
|
570
|
+
throw clientError(
|
|
571
|
+
"INVALID_ADMIN_KEY",
|
|
572
|
+
"key must be a secret key (pc_sk_) or a vendor key (pc_vk_), with no spaces."
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
return key;
|
|
576
|
+
}
|
|
577
|
+
function checkTimeout(timeoutMs) {
|
|
578
|
+
if (!(timeoutMs > 0 && Number.isFinite(timeoutMs)))
|
|
579
|
+
throw invalidRequest("timeoutMs must be a finite number above 0.");
|
|
580
|
+
return Math.min(timeoutMs, MAX_TIMEOUT_MS);
|
|
581
|
+
}
|
|
582
|
+
function assertServer() {
|
|
583
|
+
if (Reflect.get(globalThis, "window") !== void 0 && Reflect.get(globalThis, "document") !== void 0)
|
|
584
|
+
throw clientError(
|
|
585
|
+
"SERVER_ONLY",
|
|
586
|
+
"The admin client is server-only: it holds a secret or vendor key, which must never reach a browser."
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
function createCatalogAdmin(options) {
|
|
590
|
+
assertServer();
|
|
591
|
+
const key = checkKey(options.key);
|
|
592
|
+
const baseUrl = (_nullishCoalesce(options.baseUrl, () => ( _chunk4ORXUW2Ucjs.DEFAULT_BASE_URL))).replace(/\/+$/u, "");
|
|
593
|
+
if (!/^https?:\/\/[^\s/]+/iu.test(baseUrl))
|
|
594
|
+
throw invalidRequest("baseUrl must be an absolute http or https URL.");
|
|
595
|
+
const http = createHttp(
|
|
596
|
+
key,
|
|
597
|
+
baseUrl,
|
|
598
|
+
_nullishCoalesce(options.fetch, () => ( globalThis.fetch.bind(globalThis))),
|
|
599
|
+
checkTimeout(_nullishCoalesce(options.timeoutMs, () => ( ADMIN_DEFAULT_TIMEOUT_MS)))
|
|
600
|
+
);
|
|
601
|
+
const jobs = createJobs(http);
|
|
602
|
+
return {
|
|
603
|
+
jobs,
|
|
604
|
+
batches: createBatches(http),
|
|
605
|
+
runs: createRuns(http),
|
|
606
|
+
products: createProducts(http),
|
|
607
|
+
inventory: createInventory(http)
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// src/admin/webhooks.ts
|
|
612
|
+
var HEX_SHA256 = /^[0-9a-fA-F]{64}$/u;
|
|
613
|
+
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
614
|
+
var MAX_V1 = 8;
|
|
615
|
+
var MAX_HEADER_BYTES = 1024;
|
|
616
|
+
var encoder = new TextEncoder();
|
|
617
|
+
async function hmac(secret, payload) {
|
|
618
|
+
const key = await crypto.subtle.importKey(
|
|
619
|
+
"raw",
|
|
620
|
+
encoder.encode(secret),
|
|
621
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
622
|
+
false,
|
|
623
|
+
["sign"]
|
|
624
|
+
);
|
|
625
|
+
return new Uint8Array(
|
|
626
|
+
await crypto.subtle.sign("HMAC", key, encoder.encode(payload))
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
function fromHex(hex) {
|
|
630
|
+
return Uint8Array.from(
|
|
631
|
+
{ length: hex.length / 2 },
|
|
632
|
+
(_, i) => Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16)
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
function constantTimeEqual(left, right) {
|
|
636
|
+
const length = Math.max(left.length, right.length);
|
|
637
|
+
let difference = left.length ^ right.length;
|
|
638
|
+
for (let i = 0; i < length; i += 1)
|
|
639
|
+
difference |= (_nullishCoalesce(left[i], () => ( 0))) ^ (_nullishCoalesce(right[i], () => ( 0)));
|
|
640
|
+
return difference === 0;
|
|
641
|
+
}
|
|
642
|
+
async function verifyCatalogWebhook(input) {
|
|
643
|
+
try {
|
|
644
|
+
const { secret, rawBody, signature } = input;
|
|
645
|
+
const now = _nullishCoalesce(input.now, () => ( Math.floor(Date.now() / 1e3)));
|
|
646
|
+
const tolerance = _nullishCoalesce(input.toleranceSeconds, () => ( DEFAULT_TOLERANCE_SECONDS));
|
|
647
|
+
if (secret.length === 0 || rawBody.length === 0 || signature.length === 0 || encoder.encode(signature).byteLength > MAX_HEADER_BYTES || !Number.isSafeInteger(now) || !Number.isSafeInteger(tolerance) || tolerance < 0)
|
|
648
|
+
return false;
|
|
649
|
+
const parts = signature.split(",").map((part) => part.split("="));
|
|
650
|
+
if (parts.some((part) => part.length !== 2)) return false;
|
|
651
|
+
const timestamps = parts.filter(([k]) => k === "t").map(([, v]) => _nullishCoalesce(v, () => ( "")));
|
|
652
|
+
const digests = parts.filter(([k]) => k === "v1").map(([, v]) => _nullishCoalesce(v, () => ( "")));
|
|
653
|
+
if (parts.some(([k]) => k !== "t" && k !== "v1") || timestamps.length !== 1 || digests.length === 0 || digests.length > MAX_V1 || digests.some((d) => !HEX_SHA256.test(d)))
|
|
654
|
+
return false;
|
|
655
|
+
const timestampText = _nullishCoalesce(timestamps[0], () => ( ""));
|
|
656
|
+
if (!/^(?:0|[1-9][0-9]*)$/u.test(timestampText)) return false;
|
|
657
|
+
const timestamp = Number(timestampText);
|
|
658
|
+
if (!Number.isSafeInteger(timestamp) || Math.abs(now - timestamp) > tolerance)
|
|
659
|
+
return false;
|
|
660
|
+
const expected = await hmac(secret, `${timestampText}.${rawBody}`);
|
|
661
|
+
let matched = false;
|
|
662
|
+
for (const digest of digests)
|
|
663
|
+
matched = constantTimeEqual(expected, fromHex(digest)) || matched;
|
|
664
|
+
return matched;
|
|
665
|
+
} catch (e2) {
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
exports.CartLineError = _chunk4ORXUW2Ucjs.CartLineError; exports.CatalogError = _chunk4ORXUW2Ucjs.CatalogError; exports.CustomerTokenInvalidError = _chunk4ORXUW2Ucjs.CustomerTokenInvalidError; exports.FeatureUnavailableError = _chunk4ORXUW2Ucjs.FeatureUnavailableError; exports.NetworkError = _chunk4ORXUW2Ucjs.NetworkError; exports.OriginNotAllowedError = _chunk4ORXUW2Ucjs.OriginNotAllowedError; exports.RateLimitedError = _chunk4ORXUW2Ucjs.RateLimitedError; exports.TimeoutError = _chunk4ORXUW2Ucjs.TimeoutError; exports.createCatalogAdmin = createCatalogAdmin; exports.isCatalogError = _chunk4ORXUW2Ucjs.isCatalogError; exports.verifyCatalogWebhook = verifyCatalogWebhook;
|