@sdg.la/wishlist-stack-sdk 0.12.0 → 0.14.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/README.md +105 -22
- package/dist/index.cjs +200 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +130 -24
- package/dist/index.d.ts +130 -24
- package/dist/index.js +198 -37
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -51,6 +51,10 @@ declare function withPagination<TExtra extends Query>(query?: TExtra, pagination
|
|
|
51
51
|
*/
|
|
52
52
|
declare function withSort<TExtra extends Query>(query?: TExtra, sort?: SortParams): (TExtra & SortParams) | undefined;
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Create a new list. Do **not** send `variantIds` — the API may reject them.
|
|
56
|
+
* Create the list, then call `lists.addItems` or `lists.addItemsBatched`.
|
|
57
|
+
*/
|
|
54
58
|
type CreateListBody = {
|
|
55
59
|
name?: string;
|
|
56
60
|
description?: string;
|
|
@@ -64,6 +68,7 @@ type UpdateListBody = {
|
|
|
64
68
|
type AddItemsToListBody = {
|
|
65
69
|
items?: Array<{
|
|
66
70
|
variantId?: string;
|
|
71
|
+
/** Quantity for the item (1–999). */
|
|
67
72
|
quantity?: number;
|
|
68
73
|
note?: string;
|
|
69
74
|
properties?: Record<string, unknown> | null;
|
|
@@ -82,6 +87,10 @@ type ReorderListItemsBody = {
|
|
|
82
87
|
position: number;
|
|
83
88
|
}>;
|
|
84
89
|
};
|
|
90
|
+
type DuplicateListBody = {
|
|
91
|
+
/** Destination group. Omit to keep source group; `null` to ungroup. */
|
|
92
|
+
groupId?: string | null;
|
|
93
|
+
};
|
|
85
94
|
|
|
86
95
|
type OkResponse = {
|
|
87
96
|
ok: true;
|
|
@@ -170,14 +179,23 @@ type GetListsResponse = {
|
|
|
170
179
|
lists: ListSummary[];
|
|
171
180
|
pagination: Pagination;
|
|
172
181
|
};
|
|
173
|
-
/** GET `/api/lists/:listId` */
|
|
174
|
-
type GetListResponse = ListDetail
|
|
182
|
+
/** GET `/api/lists/:listId` — items are paginated (max pageSize 25). */
|
|
183
|
+
type GetListResponse = ListDetail & {
|
|
184
|
+
pagination: Pagination;
|
|
185
|
+
};
|
|
175
186
|
type ListMutationResponse = Omit<ListSummary, "featuredProducts">;
|
|
176
187
|
type CreateListResponse = ListMutationResponse;
|
|
177
188
|
type DuplicateListResponse = ListMutationResponse;
|
|
178
189
|
type UpdateListDetailsResponse = ListMutationResponse;
|
|
179
190
|
type RemoveListResponse = OkResponse;
|
|
180
|
-
|
|
191
|
+
/** POST `/api/lists/:listId/add` — newly added items only. */
|
|
192
|
+
type AddItemsToListResponse = {
|
|
193
|
+
listId: string;
|
|
194
|
+
addedItems: HydratedWishlistItem[];
|
|
195
|
+
addedCount: number;
|
|
196
|
+
};
|
|
197
|
+
/** @deprecated Use `AddItemsToListResponse`. */
|
|
198
|
+
type AddItemsToListDeltaResponse = AddItemsToListResponse;
|
|
181
199
|
type UpdateListItemResponse = unknown;
|
|
182
200
|
type RemoveItemFromListResponse = OkResponse;
|
|
183
201
|
type ReorderListItemsResponse = {
|
|
@@ -192,12 +210,29 @@ type RevokeListSharedResponse = ListMutationResponse;
|
|
|
192
210
|
type RequestFn$2 = <TResponse = unknown, TBody = unknown>(args: RequestArgs<TBody>) => Promise<TResponse>;
|
|
193
211
|
type GetListsQuery = PaginatedQuery;
|
|
194
212
|
type GetListQuery = Omit<PaginatedQuery, "query">;
|
|
213
|
+
type AddItemsBatchedOptions = {
|
|
214
|
+
/** Max items per POST (API hard max is 25). */
|
|
215
|
+
batchSize?: number;
|
|
216
|
+
};
|
|
217
|
+
type GetByIdAllItemsOptions = {
|
|
218
|
+
pageSize?: number;
|
|
219
|
+
sortBy?: GetListQuery["sortBy"];
|
|
220
|
+
sortDirection?: GetListQuery["sortDirection"];
|
|
221
|
+
};
|
|
195
222
|
declare function createListsResource(request: RequestFn$2): {
|
|
196
223
|
/** Returns all lists for the authenticated customer under the current merchant. */
|
|
197
224
|
getAll: (query?: GetListsQuery) => Promise<GetListsResponse>;
|
|
198
225
|
/** Fetch list details by id. */
|
|
199
|
-
getById: (listId: string, query?: GetListQuery) => Promise<
|
|
200
|
-
/**
|
|
226
|
+
getById: (listId: string, query?: GetListQuery) => Promise<GetListResponse>;
|
|
227
|
+
/**
|
|
228
|
+
* Fetch every item on a list by walking pages (list detail is paginated;
|
|
229
|
+
* max pageSize 25).
|
|
230
|
+
*/
|
|
231
|
+
getByIdAllItems: (listId: string, opts?: GetByIdAllItemsOptions) => Promise<GetListResponse>;
|
|
232
|
+
/**
|
|
233
|
+
* Create a new list. Do not send `variantIds` — add items via `addItems`
|
|
234
|
+
* or `addItemsBatched` after create.
|
|
235
|
+
*/
|
|
201
236
|
create: (body: CreateListBody) => Promise<ListMutationResponse>;
|
|
202
237
|
/** Update list name/description. */
|
|
203
238
|
update: (listId: string, body: UpdateListBody) => Promise<ListMutationResponse>;
|
|
@@ -205,12 +240,22 @@ declare function createListsResource(request: RequestFn$2): {
|
|
|
205
240
|
remove: (listId: string) => Promise<OkResponse>;
|
|
206
241
|
/**
|
|
207
242
|
* Duplicate a list (including all items) in a single call.
|
|
208
|
-
*
|
|
209
|
-
*
|
|
243
|
+
* By default the copy stays in the same group as the source. Pass `groupId`
|
|
244
|
+
* to place it in another group, or `groupId: null` to leave it ungrouped.
|
|
245
|
+
* The copy is named `Copy of {original name}` and is not shared.
|
|
246
|
+
*/
|
|
247
|
+
duplicate: (listId: string, body?: DuplicateListBody) => Promise<ListMutationResponse>;
|
|
248
|
+
/**
|
|
249
|
+
* Add one or more items to a list.
|
|
250
|
+
* Returns `{ listId, addedItems, addedCount }`. Prefer `addItemsBatched`
|
|
251
|
+
* when sending more than 25 items.
|
|
210
252
|
*/
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
|
|
253
|
+
addItems: (listId: string, body: AddItemsToListBody) => Promise<AddItemsToListResponse>;
|
|
254
|
+
/**
|
|
255
|
+
* Add items in sequential batches of ≤25 (API hard max).
|
|
256
|
+
* Merges `{ addedItems, addedCount }` across batches.
|
|
257
|
+
*/
|
|
258
|
+
addItemsBatched: (listId: string, body: AddItemsToListBody, opts?: AddItemsBatchedOptions) => Promise<AddItemsToListResponse>;
|
|
214
259
|
/** Update an item on a list. */
|
|
215
260
|
updateItem: (listId: string, itemId: string, body: UpdateListItemBody) => Promise<unknown>;
|
|
216
261
|
/** Remove an item from a list. */
|
|
@@ -225,7 +270,7 @@ declare function createListsResource(request: RequestFn$2): {
|
|
|
225
270
|
* Legacy endpoint kept in the OpenAPI spec (`/api/wishlists/{id}/add`).
|
|
226
271
|
* Prefer `addItems(listId, body)` unless your server only supports the legacy route.
|
|
227
272
|
*/
|
|
228
|
-
addItemsLegacy: (id: string, body: AddItemsToListBody) => Promise<
|
|
273
|
+
addItemsLegacy: (id: string, body: AddItemsToListBody) => Promise<AddItemsToListResponse>;
|
|
229
274
|
};
|
|
230
275
|
|
|
231
276
|
type CreateGroupBody = {
|
|
@@ -236,13 +281,20 @@ type UpdateGroupBody = {
|
|
|
236
281
|
name?: string;
|
|
237
282
|
description?: string;
|
|
238
283
|
};
|
|
239
|
-
|
|
240
|
-
|
|
284
|
+
/**
|
|
285
|
+
* Body for `POST /api/groups/reorder` — reorder the customer's groups.
|
|
286
|
+
* `groupIds` must list every group CUID in the desired order.
|
|
287
|
+
*/
|
|
288
|
+
type ReorderGroupsBody = {
|
|
289
|
+
groupIds: string[];
|
|
241
290
|
};
|
|
291
|
+
/** @deprecated Use `ReorderGroupsBody` (`groupIds` on `POST /api/groups/reorder`). */
|
|
292
|
+
type ReorderGroupBody = ReorderGroupsBody;
|
|
242
293
|
|
|
243
294
|
/**
|
|
244
295
|
* List embedded on a group when `includeLists=1` is passed to `groups.getAll()`.
|
|
245
|
-
* Items are
|
|
296
|
+
* Items are hydrated (same shape as `lists.getById` items).
|
|
297
|
+
* Expect at most **10 lists × 25 items** per group.
|
|
246
298
|
*/
|
|
247
299
|
type GroupSummaryList = {
|
|
248
300
|
id: string;
|
|
@@ -262,7 +314,10 @@ type GroupSummary = {
|
|
|
262
314
|
position: number;
|
|
263
315
|
shared: boolean;
|
|
264
316
|
listCount: number;
|
|
265
|
-
/**
|
|
317
|
+
/**
|
|
318
|
+
* Empty unless `includeLists=1` was requested.
|
|
319
|
+
* Cap: 10 lists × 25 items each.
|
|
320
|
+
*/
|
|
266
321
|
lists: GroupSummaryList[];
|
|
267
322
|
featuredItems: FeaturedItem[];
|
|
268
323
|
createdAt: string;
|
|
@@ -312,23 +367,40 @@ type CreateGroupResponse = GroupMutationResponse;
|
|
|
312
367
|
type DuplicateGroupResponse = GroupMutationResponse;
|
|
313
368
|
type UpdateGroupDetailsResponse = GroupMutationResponse;
|
|
314
369
|
type RemoveGroupResponse = OkResponse;
|
|
315
|
-
type
|
|
370
|
+
type ReorderGroupsResponse = {
|
|
371
|
+
updated: Array<{
|
|
372
|
+
id: string;
|
|
373
|
+
position: number;
|
|
374
|
+
}>;
|
|
375
|
+
};
|
|
376
|
+
/** @deprecated Prefer `ReorderGroupsResponse`. */
|
|
377
|
+
type ReorderGroupResponse = ReorderGroupsResponse;
|
|
316
378
|
type MarkGroupSharedResponse = GroupMutationResponse;
|
|
317
379
|
type RevokeGroupSharedResponse = GroupMutationResponse;
|
|
318
380
|
|
|
319
381
|
type RequestFn$1 = <TResponse = unknown, TBody = unknown>(args: RequestArgs<TBody>) => Promise<TResponse>;
|
|
320
382
|
type GetGroupsQuery = PaginatedQuery<{
|
|
321
383
|
/**
|
|
322
|
-
* When truthy (`true`, `1`, or `"1"`), each group includes
|
|
323
|
-
*
|
|
384
|
+
* When truthy (`true`, `1`, or `"1"`), each group includes embedded `lists`
|
|
385
|
+
* with hydrated items (`includeLists=1`).
|
|
386
|
+
*
|
|
387
|
+
* Expect at most **10 lists × 25 items** per group. For full data use
|
|
388
|
+
* `groups.getById` plus `lists.getById` / `lists.getByIdAllItems`. Groups
|
|
389
|
+
* endpoints may return **503** until the Groups API is enabled for the merchant.
|
|
324
390
|
*/
|
|
325
391
|
includeLists?: boolean | 1 | "1";
|
|
326
392
|
}>;
|
|
327
393
|
type GetGroupQuery = PaginatedQuery;
|
|
328
394
|
declare function createGroupsResource(request: RequestFn$1): {
|
|
329
|
-
/**
|
|
395
|
+
/**
|
|
396
|
+
* Fetch groups for the authenticated customer.
|
|
397
|
+
* May return **503** when the Groups API is disabled for this merchant.
|
|
398
|
+
*/
|
|
330
399
|
getAll: (query?: GetGroupsQuery) => Promise<GetGroupsResponse>;
|
|
331
|
-
/**
|
|
400
|
+
/**
|
|
401
|
+
* Fetch group details by id.
|
|
402
|
+
* May return **503** when the Groups API is disabled for this merchant.
|
|
403
|
+
*/
|
|
332
404
|
getById: (groupId: string, query?: GetGroupQuery) => Promise<GroupDetail>;
|
|
333
405
|
/** Create a new group. */
|
|
334
406
|
create: (body: CreateGroupBody) => Promise<GroupMutationResponse>;
|
|
@@ -341,8 +413,12 @@ declare function createGroupsResource(request: RequestFn$1): {
|
|
|
341
413
|
* The copy is named `Copy of {original name}` and is not shared.
|
|
342
414
|
*/
|
|
343
415
|
duplicate: (groupId: string) => Promise<GroupMutationResponse>;
|
|
344
|
-
/**
|
|
345
|
-
|
|
416
|
+
/**
|
|
417
|
+
* Reorder the customer's groups (`POST /api/groups/reorder`).
|
|
418
|
+
* Body must include `groupIds` in the desired order — not list IDs, and
|
|
419
|
+
* not a per-group path.
|
|
420
|
+
*/
|
|
421
|
+
reorder: (body: ReorderGroupsBody) => Promise<ReorderGroupsResponse>;
|
|
346
422
|
/** Mark group as shared (public read-only). */
|
|
347
423
|
share: (groupId: string) => Promise<GroupMutationResponse>;
|
|
348
424
|
/** Revoke group sharing. */
|
|
@@ -354,7 +430,7 @@ type GetSharedListQuery = Omit<PaginatedQuery, "query">;
|
|
|
354
430
|
type GetSharedGroupQuery = PaginatedQuery;
|
|
355
431
|
declare function createSharedResource(request: RequestFn): {
|
|
356
432
|
/** Public read-only view of a shared list. Requires only merchant apiKey. */
|
|
357
|
-
getSharedList: (listId: string, query?: GetSharedListQuery) => Promise<
|
|
433
|
+
getSharedList: (listId: string, query?: GetSharedListQuery) => Promise<GetListResponse>;
|
|
358
434
|
/** Public read-only view of a shared group. Requires only merchant apiKey. */
|
|
359
435
|
getSharedGroup: (groupId: string, query?: GetSharedGroupQuery) => Promise<GroupDetail>;
|
|
360
436
|
};
|
|
@@ -364,6 +440,10 @@ type CreateWishlistStackClientOptions = {
|
|
|
364
440
|
apiKey: string;
|
|
365
441
|
customerAccessToken?: string;
|
|
366
442
|
fetch?: FetchLike;
|
|
443
|
+
/** Default request timeout in ms when a call does not pass `timeoutMs`. */
|
|
444
|
+
defaultTimeoutMs?: number;
|
|
445
|
+
/** When true, retry once on HTTP 429 with jittered backoff (default: false). */
|
|
446
|
+
retryOnRateLimit?: boolean;
|
|
367
447
|
};
|
|
368
448
|
type WishlistStackClient$1 = {
|
|
369
449
|
lists: ReturnType<typeof createListsResource>;
|
|
@@ -384,6 +464,12 @@ type ApiErrorResponse = {
|
|
|
384
464
|
type GetSharedListResponse = GetListResponse;
|
|
385
465
|
type GetSharedGroupResponse = GetGroupResponse;
|
|
386
466
|
|
|
467
|
+
/**
|
|
468
|
+
* True when the value matches the add-items response shape
|
|
469
|
+
* (`{ listId, addedItems, addedCount }`).
|
|
470
|
+
*/
|
|
471
|
+
declare function isAddItemsDeltaResponse(value: AddItemsToListResponse | unknown): value is AddItemsToListResponse;
|
|
472
|
+
|
|
387
473
|
/**
|
|
388
474
|
* Class wrapper around `createWishlistStackClient()` for users who prefer `new`.
|
|
389
475
|
* Exposes `lists`, `groups`, and `shared` resources.
|
|
@@ -407,6 +493,14 @@ type WishlistStackApiErrorDetails<TBody = unknown> = {
|
|
|
407
493
|
method: string;
|
|
408
494
|
requestId?: string;
|
|
409
495
|
body?: TBody;
|
|
496
|
+
/** Value of the `Retry-After` response header when present (seconds or HTTP-date). */
|
|
497
|
+
retryAfter?: string;
|
|
498
|
+
/** Selected rate-limit response headers when present. */
|
|
499
|
+
rateLimit?: {
|
|
500
|
+
limit?: string;
|
|
501
|
+
remaining?: string;
|
|
502
|
+
reset?: string;
|
|
503
|
+
};
|
|
410
504
|
};
|
|
411
505
|
declare class WishlistStackApiError<TBody = unknown> extends Error {
|
|
412
506
|
readonly name = "WishlistStackApiError";
|
|
@@ -415,6 +509,8 @@ declare class WishlistStackApiError<TBody = unknown> extends Error {
|
|
|
415
509
|
readonly method: string;
|
|
416
510
|
readonly requestId?: string;
|
|
417
511
|
readonly body?: TBody;
|
|
512
|
+
readonly retryAfter?: string;
|
|
513
|
+
readonly rateLimit?: WishlistStackApiErrorDetails["rateLimit"];
|
|
418
514
|
constructor(message: string, details: WishlistStackApiErrorDetails<TBody>);
|
|
419
515
|
/**
|
|
420
516
|
* Best-effort extraction of API error items from the response body.
|
|
@@ -434,4 +530,14 @@ declare class WishlistStackApiError<TBody = unknown> extends Error {
|
|
|
434
530
|
*/
|
|
435
531
|
declare function isWishlistStackApiError(value: unknown): value is WishlistStackApiError<unknown>;
|
|
436
532
|
|
|
437
|
-
|
|
533
|
+
/**
|
|
534
|
+
* Clamp a page size to the API-allowed range (1–25) so requests
|
|
535
|
+
* do not send invalid `pageSize` values.
|
|
536
|
+
*/
|
|
537
|
+
declare function clampPageSize(n: number): number;
|
|
538
|
+
/**
|
|
539
|
+
* Clamp a 1-based page index to at least 1.
|
|
540
|
+
*/
|
|
541
|
+
declare function clampPage(n: number): number;
|
|
542
|
+
|
|
543
|
+
export { type AddItemsToListBody, type AddItemsToListDeltaResponse, type AddItemsToListResponse, type ApiErrorItem, type ApiErrorResponse, type CreateGroupBody, type CreateGroupResponse, type CreateListBody, type CreateListResponse, type CreateWishlistStackClientOptions, DEFAULT_PAGE, DEFAULT_PAGE_SIZE, type DuplicateGroupResponse, type DuplicateListBody, type DuplicateListResponse, type FeaturedItem, type GetGroupResponse, type GetGroupsResponse, type GetListResponse, type GetListsResponse, type GetSharedGroupResponse, type GetSharedListResponse, type GroupDetail, type GroupDetailList, type GroupMutationResponse, type GroupSummary, type GroupSummaryList, type HydratedProduct, type HydratedVariant, type HydratedWishlistItem, type Id, type Image, type ListDetail, type ListMutationResponse, type ListSummary, type MarkGroupSharedResponse, type MarkListSharedResponse, type Metafield, type Money, type OkResponse, type PaginatedQuery, type Pagination, type PaginationParams, type RemoveGroupResponse, type RemoveItemFromListResponse, type RemoveListResponse, type ReorderGroupBody, type ReorderGroupResponse, type ReorderGroupsBody, type ReorderGroupsResponse, type ReorderListItemsBody, type ReorderListItemsResponse, type RevokeGroupSharedResponse, type RevokeListSharedResponse, SORT_DIRECTIONS, SORT_FIELDS, type SelectedOption, type SortDirection, type SortField, type SortParams, type UpdateGroupBody, type UpdateGroupDetailsResponse, type UpdateListBody, type UpdateListDetailsResponse, type UpdateListItemBody, type UpdateListItemResponse, WishlistStackApiError, type WishlistStackClient$1 as WishlistStackClient, WishlistStackClient as WishlistStackSDK, clampPage, clampPageSize, createWishlistStackClient, isAddItemsDeltaResponse, isWishlistStackApiError, withPagination, withSort };
|
package/dist/index.js
CHANGED
|
@@ -55,11 +55,15 @@ var WishlistStackApiError = class extends Error {
|
|
|
55
55
|
__publicField(this, "method");
|
|
56
56
|
__publicField(this, "requestId");
|
|
57
57
|
__publicField(this, "body");
|
|
58
|
+
__publicField(this, "retryAfter");
|
|
59
|
+
__publicField(this, "rateLimit");
|
|
58
60
|
this.status = details.status;
|
|
59
61
|
this.url = details.url;
|
|
60
62
|
this.method = details.method;
|
|
61
63
|
this.requestId = details.requestId;
|
|
62
64
|
this.body = details.body;
|
|
65
|
+
this.retryAfter = details.retryAfter;
|
|
66
|
+
this.rateLimit = details.rateLimit;
|
|
63
67
|
}
|
|
64
68
|
/**
|
|
65
69
|
* Best-effort extraction of API error items from the response body.
|
|
@@ -91,6 +95,42 @@ async function safeParseJson(res) {
|
|
|
91
95
|
return void 0;
|
|
92
96
|
}
|
|
93
97
|
}
|
|
98
|
+
function pickRequestId(res) {
|
|
99
|
+
return res.headers.get("fly-request-id") ?? res.headers.get("x-request-id") ?? res.headers.get("x-nf-request-id") ?? res.headers.get("x-netlify-request-id") ?? void 0;
|
|
100
|
+
}
|
|
101
|
+
function pickRateLimit(res) {
|
|
102
|
+
const limit = res.headers.get("x-ratelimit-limit") ?? void 0;
|
|
103
|
+
const remaining = res.headers.get("x-ratelimit-remaining") ?? void 0;
|
|
104
|
+
const reset = res.headers.get("x-ratelimit-reset") ?? void 0;
|
|
105
|
+
if (!limit && !remaining && !reset) return void 0;
|
|
106
|
+
return { limit, remaining, reset };
|
|
107
|
+
}
|
|
108
|
+
function errorMessageFor(status, path) {
|
|
109
|
+
if (status === 503 && path.includes("/api/groups")) {
|
|
110
|
+
return "Groups API disabled for this merchant";
|
|
111
|
+
}
|
|
112
|
+
return `Wishlist Stack API request failed (${status})`;
|
|
113
|
+
}
|
|
114
|
+
function parseRetryAfterMs(retryAfter) {
|
|
115
|
+
if (!retryAfter) return 0;
|
|
116
|
+
const asSeconds = Number(retryAfter);
|
|
117
|
+
if (Number.isFinite(asSeconds) && asSeconds >= 0) {
|
|
118
|
+
return Math.min(asSeconds * 1e3, 3e4);
|
|
119
|
+
}
|
|
120
|
+
const asDate = Date.parse(retryAfter);
|
|
121
|
+
if (!Number.isNaN(asDate)) {
|
|
122
|
+
return Math.min(Math.max(0, asDate - Date.now()), 3e4);
|
|
123
|
+
}
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
function jitterDelayMs(baseMs) {
|
|
127
|
+
const base = Math.max(100, baseMs || 500);
|
|
128
|
+
const jitter = Math.floor(Math.random() * 250);
|
|
129
|
+
return Math.min(base + jitter, 3e4);
|
|
130
|
+
}
|
|
131
|
+
function sleep(ms) {
|
|
132
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
133
|
+
}
|
|
94
134
|
function createRequest(ctx) {
|
|
95
135
|
return async function request(args) {
|
|
96
136
|
const url = `${ctx.baseUrl}${args.path}${toQueryString(args.query)}`;
|
|
@@ -107,25 +147,39 @@ function createRequest(ctx) {
|
|
|
107
147
|
headers["content-type"] ?? (headers["content-type"] = "application/json");
|
|
108
148
|
body = JSON.stringify(args.body);
|
|
109
149
|
}
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
150
|
+
const timeoutMs = args.timeoutMs ?? ctx.defaultTimeoutMs;
|
|
151
|
+
const doFetch = async () => {
|
|
152
|
+
const controller = timeoutMs ? new AbortController() : void 0;
|
|
153
|
+
const timeout = timeoutMs && controller ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
|
|
154
|
+
const signal = args.signal ? args.signal : controller ? controller.signal : void 0;
|
|
155
|
+
try {
|
|
156
|
+
return await ctx.fetch(url, {
|
|
157
|
+
method: args.method,
|
|
158
|
+
headers,
|
|
159
|
+
body,
|
|
160
|
+
signal
|
|
161
|
+
});
|
|
162
|
+
} finally {
|
|
163
|
+
if (timeout) clearTimeout(timeout);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
let res = await doFetch();
|
|
167
|
+
if (!res.ok && res.status === 429 && ctx.retryOnRateLimit) {
|
|
168
|
+
const retryAfter = res.headers.get("retry-after") ?? void 0;
|
|
169
|
+
await sleep(jitterDelayMs(parseRetryAfterMs(retryAfter)));
|
|
170
|
+
res = await doFetch();
|
|
171
|
+
}
|
|
120
172
|
if (!res.ok) {
|
|
121
173
|
const errBody = await safeParseJson(res);
|
|
122
|
-
const
|
|
123
|
-
throw new WishlistStackApiError(
|
|
174
|
+
const retryAfter = res.headers.get("retry-after") ?? void 0;
|
|
175
|
+
throw new WishlistStackApiError(errorMessageFor(res.status, args.path), {
|
|
124
176
|
status: res.status,
|
|
125
177
|
url,
|
|
126
178
|
method: args.method,
|
|
127
|
-
requestId,
|
|
128
|
-
body: errBody
|
|
179
|
+
requestId: pickRequestId(res),
|
|
180
|
+
body: errBody,
|
|
181
|
+
retryAfter,
|
|
182
|
+
rateLimit: pickRateLimit(res)
|
|
129
183
|
});
|
|
130
184
|
}
|
|
131
185
|
if (res.status === 204) return void 0;
|
|
@@ -138,8 +192,38 @@ function createRequest(ctx) {
|
|
|
138
192
|
};
|
|
139
193
|
}
|
|
140
194
|
|
|
195
|
+
// src/helpers/pagination.ts
|
|
196
|
+
function clampPageSize(n) {
|
|
197
|
+
if (!Number.isFinite(n)) return MAX_PAGINATION_SIZE;
|
|
198
|
+
return Math.min(MAX_PAGINATION_SIZE, Math.max(1, Math.trunc(n)));
|
|
199
|
+
}
|
|
200
|
+
function clampPage(n) {
|
|
201
|
+
if (!Number.isFinite(n)) return 1;
|
|
202
|
+
return Math.max(1, Math.trunc(n));
|
|
203
|
+
}
|
|
204
|
+
|
|
141
205
|
// src/resources/lists/index.ts
|
|
206
|
+
function chunkItems(items, batchSize) {
|
|
207
|
+
const size = Math.max(1, Math.min(MAX_PAGINATION_SIZE, Math.trunc(batchSize) || MAX_PAGINATION_SIZE));
|
|
208
|
+
const chunks = [];
|
|
209
|
+
for (let i = 0; i < items.length; i += size) {
|
|
210
|
+
chunks.push(items.slice(i, i + size));
|
|
211
|
+
}
|
|
212
|
+
return chunks.length ? chunks : [[]];
|
|
213
|
+
}
|
|
142
214
|
function createListsResource(request) {
|
|
215
|
+
const getById = (listId, query) => request({
|
|
216
|
+
method: "GET",
|
|
217
|
+
path: `/api/lists/${encodeURIComponent(listId)}`,
|
|
218
|
+
auth: "authenticated",
|
|
219
|
+
query
|
|
220
|
+
});
|
|
221
|
+
const addItems = (listId, body) => request({
|
|
222
|
+
method: "POST",
|
|
223
|
+
path: `/api/lists/${encodeURIComponent(listId)}/add`,
|
|
224
|
+
auth: "authenticated",
|
|
225
|
+
body
|
|
226
|
+
});
|
|
143
227
|
return {
|
|
144
228
|
/** Returns all lists for the authenticated customer under the current merchant. */
|
|
145
229
|
getAll: (query) => request({
|
|
@@ -149,13 +233,47 @@ function createListsResource(request) {
|
|
|
149
233
|
query
|
|
150
234
|
}),
|
|
151
235
|
/** Fetch list details by id. */
|
|
152
|
-
getById
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
236
|
+
getById,
|
|
237
|
+
/**
|
|
238
|
+
* Fetch every item on a list by walking pages (list detail is paginated;
|
|
239
|
+
* max pageSize 25).
|
|
240
|
+
*/
|
|
241
|
+
getByIdAllItems: async (listId, opts) => {
|
|
242
|
+
const pageSize = clampPageSize(opts?.pageSize ?? MAX_PAGINATION_SIZE);
|
|
243
|
+
const sortBy = opts?.sortBy;
|
|
244
|
+
const sortDirection = opts?.sortDirection;
|
|
245
|
+
const first = await getById(listId, {
|
|
246
|
+
page: 1,
|
|
247
|
+
pageSize,
|
|
248
|
+
...sortBy ? { sortBy } : {},
|
|
249
|
+
...sortDirection ? { sortDirection } : {}
|
|
250
|
+
});
|
|
251
|
+
const totalPages = first.pagination.totalPages;
|
|
252
|
+
const allItems = [...first.items ?? []];
|
|
253
|
+
for (let page = 2; page <= totalPages; page++) {
|
|
254
|
+
const next = await getById(listId, {
|
|
255
|
+
page: clampPage(page),
|
|
256
|
+
pageSize,
|
|
257
|
+
...sortBy ? { sortBy } : {},
|
|
258
|
+
...sortDirection ? { sortDirection } : {}
|
|
259
|
+
});
|
|
260
|
+
allItems.push(...next.items ?? []);
|
|
261
|
+
}
|
|
262
|
+
const pagination = {
|
|
263
|
+
...first.pagination,
|
|
264
|
+
page: 1,
|
|
265
|
+
pageSize
|
|
266
|
+
};
|
|
267
|
+
return {
|
|
268
|
+
...first,
|
|
269
|
+
items: allItems,
|
|
270
|
+
pagination
|
|
271
|
+
};
|
|
272
|
+
},
|
|
273
|
+
/**
|
|
274
|
+
* Create a new list. Do not send `variantIds` — add items via `addItems`
|
|
275
|
+
* or `addItemsBatched` after create.
|
|
276
|
+
*/
|
|
159
277
|
create: (body) => request({
|
|
160
278
|
method: "POST",
|
|
161
279
|
path: "/api/lists",
|
|
@@ -177,21 +295,43 @@ function createListsResource(request) {
|
|
|
177
295
|
}),
|
|
178
296
|
/**
|
|
179
297
|
* Duplicate a list (including all items) in a single call.
|
|
180
|
-
*
|
|
181
|
-
*
|
|
298
|
+
* By default the copy stays in the same group as the source. Pass `groupId`
|
|
299
|
+
* to place it in another group, or `groupId: null` to leave it ungrouped.
|
|
300
|
+
* The copy is named `Copy of {original name}` and is not shared.
|
|
182
301
|
*/
|
|
183
|
-
duplicate: (listId) => request({
|
|
302
|
+
duplicate: (listId, body) => request({
|
|
184
303
|
method: "POST",
|
|
185
304
|
path: `/api/lists/${encodeURIComponent(listId)}/duplicate`,
|
|
186
|
-
auth: "authenticated"
|
|
187
|
-
}),
|
|
188
|
-
/** Add one or more items to a list. */
|
|
189
|
-
addItems: (listId, body) => request({
|
|
190
|
-
method: "POST",
|
|
191
|
-
path: `/api/lists/${encodeURIComponent(listId)}/add`,
|
|
192
305
|
auth: "authenticated",
|
|
193
306
|
body
|
|
194
307
|
}),
|
|
308
|
+
/**
|
|
309
|
+
* Add one or more items to a list.
|
|
310
|
+
* Returns `{ listId, addedItems, addedCount }`. Prefer `addItemsBatched`
|
|
311
|
+
* when sending more than 25 items.
|
|
312
|
+
*/
|
|
313
|
+
addItems,
|
|
314
|
+
/**
|
|
315
|
+
* Add items in sequential batches of ≤25 (API hard max).
|
|
316
|
+
* Merges `{ addedItems, addedCount }` across batches.
|
|
317
|
+
*/
|
|
318
|
+
addItemsBatched: async (listId, body, opts) => {
|
|
319
|
+
const items = body.items ?? [];
|
|
320
|
+
const batchSize = opts?.batchSize ?? MAX_PAGINATION_SIZE;
|
|
321
|
+
const chunks = chunkItems(items, batchSize);
|
|
322
|
+
const merged = {
|
|
323
|
+
listId,
|
|
324
|
+
addedItems: [],
|
|
325
|
+
addedCount: 0
|
|
326
|
+
};
|
|
327
|
+
for (const chunk of chunks) {
|
|
328
|
+
const res = await addItems(listId, { items: chunk });
|
|
329
|
+
merged.listId = res.listId;
|
|
330
|
+
merged.addedItems.push(...res.addedItems);
|
|
331
|
+
merged.addedCount += res.addedCount;
|
|
332
|
+
}
|
|
333
|
+
return merged;
|
|
334
|
+
},
|
|
195
335
|
/** Update an item on a list. */
|
|
196
336
|
updateItem: (listId, itemId, body) => request({
|
|
197
337
|
method: "PATCH",
|
|
@@ -248,14 +388,20 @@ function normalizeGetGroupsQuery(query) {
|
|
|
248
388
|
}
|
|
249
389
|
function createGroupsResource(request) {
|
|
250
390
|
return {
|
|
251
|
-
/**
|
|
391
|
+
/**
|
|
392
|
+
* Fetch groups for the authenticated customer.
|
|
393
|
+
* May return **503** when the Groups API is disabled for this merchant.
|
|
394
|
+
*/
|
|
252
395
|
getAll: (query) => request({
|
|
253
396
|
method: "GET",
|
|
254
397
|
path: "/api/groups",
|
|
255
398
|
auth: "authenticated",
|
|
256
399
|
query: normalizeGetGroupsQuery(query)
|
|
257
400
|
}),
|
|
258
|
-
/**
|
|
401
|
+
/**
|
|
402
|
+
* Fetch group details by id.
|
|
403
|
+
* May return **503** when the Groups API is disabled for this merchant.
|
|
404
|
+
*/
|
|
259
405
|
getById: (groupId, query) => request({
|
|
260
406
|
method: "GET",
|
|
261
407
|
path: `/api/groups/${encodeURIComponent(groupId)}`,
|
|
@@ -291,10 +437,14 @@ function createGroupsResource(request) {
|
|
|
291
437
|
path: `/api/groups/${encodeURIComponent(groupId)}/duplicate`,
|
|
292
438
|
auth: "authenticated"
|
|
293
439
|
}),
|
|
294
|
-
/**
|
|
295
|
-
|
|
440
|
+
/**
|
|
441
|
+
* Reorder the customer's groups (`POST /api/groups/reorder`).
|
|
442
|
+
* Body must include `groupIds` in the desired order — not list IDs, and
|
|
443
|
+
* not a per-group path.
|
|
444
|
+
*/
|
|
445
|
+
reorder: (body) => request({
|
|
296
446
|
method: "POST",
|
|
297
|
-
path:
|
|
447
|
+
path: "/api/groups/reorder",
|
|
298
448
|
auth: "authenticated",
|
|
299
449
|
body
|
|
300
450
|
}),
|
|
@@ -350,7 +500,9 @@ function createWishlistStackClient(opts) {
|
|
|
350
500
|
baseUrl: opts.baseUrl ?? BASE_URL,
|
|
351
501
|
apiKey: opts.apiKey,
|
|
352
502
|
customerAccessToken: opts.customerAccessToken,
|
|
353
|
-
fetch: fetchImpl
|
|
503
|
+
fetch: fetchImpl,
|
|
504
|
+
defaultTimeoutMs: opts.defaultTimeoutMs,
|
|
505
|
+
retryOnRateLimit: opts.retryOnRateLimit
|
|
354
506
|
});
|
|
355
507
|
return {
|
|
356
508
|
lists: createListsResource(request),
|
|
@@ -400,6 +552,15 @@ function withSort(query, sort) {
|
|
|
400
552
|
return { ...query ?? {}, ...sort ?? {} };
|
|
401
553
|
}
|
|
402
554
|
|
|
403
|
-
|
|
555
|
+
// src/types/guards.ts
|
|
556
|
+
function isRecord2(value) {
|
|
557
|
+
return typeof value === "object" && value !== null;
|
|
558
|
+
}
|
|
559
|
+
function isAddItemsDeltaResponse(value) {
|
|
560
|
+
if (!isRecord2(value)) return false;
|
|
561
|
+
return typeof value.listId === "string" && Array.isArray(value.addedItems) && typeof value.addedCount === "number";
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export { DEFAULT_PAGE, DEFAULT_PAGE_SIZE, SORT_DIRECTIONS, SORT_FIELDS, WishlistStackApiError, WishlistStackClient as WishlistStackSDK, clampPage, clampPageSize, createWishlistStackClient, isAddItemsDeltaResponse, isWishlistStackApiError, withPagination, withSort };
|
|
404
565
|
//# sourceMappingURL=index.js.map
|
|
405
566
|
//# sourceMappingURL=index.js.map
|