@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/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
- type AddItemsToListResponse = unknown;
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<ListDetail>;
200
- /** Create a new list. */
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
- * The copy stays in the same group (if any), is named `Copy of {original name}`,
209
- * and is not shared.
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
- duplicate: (listId: string) => Promise<ListMutationResponse>;
212
- /** Add one or more items to a list. */
213
- addItems: (listId: string, body: AddItemsToListBody) => Promise<unknown>;
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<unknown>;
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
- type ReorderGroupBody = {
240
- listIds?: string[];
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 fully hydrated (same shape as `lists.getById`).
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
- /** Empty unless `includeLists=1` was requested. */
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 ReorderGroupResponse = unknown;
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 its full `lists`
323
- * array with hydrated items. Sent to the API as `includeLists=1`.
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
- /** Fetch groups for the authenticated customer. */
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
- /** Fetch group details by id. */
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
- /** Reorder a group (spec uses listIds payload). */
345
- reorder: (groupId: string, body: ReorderGroupBody) => Promise<unknown>;
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<ListDetail>;
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
- export { type AddItemsToListBody, type AddItemsToListResponse, type ApiErrorItem, type ApiErrorResponse, type CreateGroupBody, type CreateGroupResponse, type CreateListBody, type CreateListResponse, type CreateWishlistStackClientOptions, DEFAULT_PAGE, DEFAULT_PAGE_SIZE, type DuplicateGroupResponse, 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 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, createWishlistStackClient, isWishlistStackApiError, withPagination, withSort };
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 controller = args.timeoutMs ? new AbortController() : void 0;
111
- const timeout = args.timeoutMs && controller ? setTimeout(() => controller.abort(), args.timeoutMs) : void 0;
112
- const signal = args.signal ? args.signal : controller ? controller.signal : void 0;
113
- const res = await ctx.fetch(url, {
114
- method: args.method,
115
- headers,
116
- body,
117
- signal
118
- });
119
- if (timeout) clearTimeout(timeout);
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 requestId = res.headers.get("fly-request-id") ?? res.headers.get("x-request-id") ?? void 0;
123
- throw new WishlistStackApiError(`Wishlist Stack API request failed (${res.status})`, {
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: (listId, query) => request({
153
- method: "GET",
154
- path: `/api/lists/${encodeURIComponent(listId)}`,
155
- auth: "authenticated",
156
- query
157
- }),
158
- /** Create a new list. */
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
- * The copy stays in the same group (if any), is named `Copy of {original name}`,
181
- * and is not shared.
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
- /** Fetch groups for the authenticated customer. */
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
- /** Fetch group details by id. */
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
- /** Reorder a group (spec uses listIds payload). */
295
- reorder: (groupId, body) => request({
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: `/api/groups/${encodeURIComponent(groupId)}/reorder`,
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
- export { DEFAULT_PAGE, DEFAULT_PAGE_SIZE, SORT_DIRECTIONS, SORT_FIELDS, WishlistStackApiError, WishlistStackClient as WishlistStackSDK, createWishlistStackClient, isWishlistStackApiError, withPagination, withSort };
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