@malawistore/store-sdk 0.1.2 → 0.2.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 +45 -1
- package/README.md +94 -8
- package/dist/index.cjs +328 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +280 -13
- package/dist/index.d.ts +280 -13
- package/dist/index.js +328 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,51 @@ All notable changes to this project are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
-
## [
|
|
7
|
+
## [0.2.0] - 2026-11-04
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Bulk admin writes: `products.bulkCreate()`, `products.bulkUpdate()`,
|
|
12
|
+
`products.bulkDelete()`, `variants.createBulk()`, `variants.updateBulk()`,
|
|
13
|
+
and `inventory.setStockBulk()`. Every method accepts `atomic`, `dryRun`,
|
|
14
|
+
and an optional `idempotencyKey`.
|
|
15
|
+
- Bulk shopper writes: `cart.addItems()` (returns per-line results plus the
|
|
16
|
+
full updated cart), `wishlist.addBulk()`, and `wishlist.removeBulk()`.
|
|
17
|
+
- Product gallery admin endpoints: `products.listImages()`,
|
|
18
|
+
`products.uploadImages()` (multi-file), `products.deleteImage()`, and
|
|
19
|
+
`products.setPrimaryImage()`.
|
|
20
|
+
- `inventory.getStockByVariants()` batch availability read.
|
|
21
|
+
- `reviews.listMine()` and `reviews.listMinePage()` for the caller's review
|
|
22
|
+
history.
|
|
23
|
+
- `products.list()` / `listPage()` accept `ids` to restrict the page.
|
|
24
|
+
- `BulkOptions`, `BulkResult`, `BulkResponse`, `BulkSummary`, and the new
|
|
25
|
+
bulk/gallery wire types are exported.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- `users.get()` now calls `GET /admin/users/{id}`. The previous `/users/{id}`
|
|
30
|
+
route does not exist (owners use `users.me()`; lookups are admin-only).
|
|
31
|
+
- `ApiErrorCode` now includes `BULK_LIMIT_EXCEEDED`, `DUPLICATE_ITEM`, and
|
|
32
|
+
`IDEMPOTENCY_CONFLICT`.
|
|
33
|
+
|
|
34
|
+
## [0.1.3] - 2026-10-29
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- `cart.getPage()` with line filters (`q`, `product_id`, `variant_id`), sorting,
|
|
39
|
+
and paging metadata; cart totals still cover every line.
|
|
40
|
+
- `orders.listPage()` with status/currency/date/total filters and sorting.
|
|
41
|
+
- `wishlist.listPage()` with product search, category filtering, and sorting.
|
|
42
|
+
- Review filters and paging through `reviews.summary(productId, params)`.
|
|
43
|
+
- `ListCartParams`, `ListOrdersParams`, `ListWishlistParams`,
|
|
44
|
+
`ListReviewsParams`, and `CartResponse` wire types.
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
|
|
48
|
+
- Storefront and shopper lists default to 12 rows per page; the admin user
|
|
49
|
+
directory still defaults to 20. Every page remains capped at 100.
|
|
50
|
+
- Existing `list()`, `get()`, and `summary()` methods now accept the same
|
|
51
|
+
optional query parameters without changing their return shapes.
|
|
8
52
|
|
|
9
53
|
## [0.1.1] - 2026-10-28
|
|
10
54
|
|
package/README.md
CHANGED
|
@@ -115,14 +115,14 @@ several parallel queries does not rotate the token multiple times.
|
|
|
115
115
|
| `store.auth` | Register, login, refresh, logout, email verification, password reset |
|
|
116
116
|
| `store.users` | Current account, user lookup, role management (admin) |
|
|
117
117
|
| `store.categories` | Taxonomy reads, admin CRUD, image upload |
|
|
118
|
-
| `store.products` | Catalog reads, admin CRUD, image upload
|
|
119
|
-
| `store.variants` | SKU reads
|
|
120
|
-
| `store.cart` | Get, add, update quantity, remove, clear
|
|
121
|
-
| `store.inventory` | Stock lookup
|
|
118
|
+
| `store.products` | Catalog reads, admin CRUD, bulk writes, image + gallery upload |
|
|
119
|
+
| `store.variants` | SKU reads, admin CRUD, bulk create/update |
|
|
120
|
+
| `store.cart` | Get, add (single or bulk), update quantity, remove, clear |
|
|
121
|
+
| `store.inventory` | Stock lookup (single or batch), bulk set, reserve/release/confirm |
|
|
122
122
|
| `store.orders` | Checkout, list, get, cancel |
|
|
123
123
|
| `store.payments` | Stripe payment intents and hosted Checkout sessions |
|
|
124
|
-
| `store.wishlist` | List, add, remove
|
|
125
|
-
| `store.reviews` | Product summary, create, edit, delete,
|
|
124
|
+
| `store.wishlist` | List, add, remove, bulk heart/unheart |
|
|
125
|
+
| `store.reviews` | Product summary, own history, create, edit, delete, moderation |
|
|
126
126
|
|
|
127
127
|
### Auth
|
|
128
128
|
|
|
@@ -160,6 +160,9 @@ console.log(detail.product.price_cents); // 12500 → K125.00
|
|
|
160
160
|
console.log(detail.variants); // sellable SKUs
|
|
161
161
|
|
|
162
162
|
const skus = await store.variants.list(detail.product.id);
|
|
163
|
+
|
|
164
|
+
// Fetch a specific set of products (e.g. a "recently viewed" rail)
|
|
165
|
+
const picked = await store.products.list({ ids: [id1, id2, id3] });
|
|
163
166
|
```
|
|
164
167
|
|
|
165
168
|
For paginated grids use `listPage()`, which returns `{ data, meta }`:
|
|
@@ -183,8 +186,37 @@ page.meta; // page, limit, offset, total, total_pages, has_next, has_prev
|
|
|
183
186
|
|
|
184
187
|
Categories support `q`, `parent_id`, `root_only`, `has_products`, `sort`,
|
|
185
188
|
`page`, `limit` and `offset` through `categories.list()` or
|
|
186
|
-
`categories.listPage()`. `page` wins over `offset`;
|
|
187
|
-
|
|
189
|
+
`categories.listPage()`. `page` wins over `offset`; storefront and shopper
|
|
190
|
+
lists (products, carts, categories, orders, reviews and wishlists) default to
|
|
191
|
+
12 rows. Every page is capped at 100.
|
|
192
|
+
|
|
193
|
+
The shopper lists expose the same paging envelope:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
const cart = await store.cart.getPage({ page: 1, sort: "newest" });
|
|
197
|
+
cart.data.items; // current page; subtotal still covers the whole cart
|
|
198
|
+
cart.meta; // line-level page counters
|
|
199
|
+
|
|
200
|
+
const orders = await store.orders.listPage({
|
|
201
|
+
status: "paid",
|
|
202
|
+
created_after: "2026-01-01",
|
|
203
|
+
sort: "total_desc",
|
|
204
|
+
});
|
|
205
|
+
const wishlist = await store.wishlist.listPage({ q: "oil", sort: "price_asc" });
|
|
206
|
+
|
|
207
|
+
const reviews = await store.reviews.summary(productId, {
|
|
208
|
+
rating: 5,
|
|
209
|
+
sort: "highest",
|
|
210
|
+
page: 1,
|
|
211
|
+
limit: 12,
|
|
212
|
+
});
|
|
213
|
+
reviews.meta; // filtered review page counters
|
|
214
|
+
|
|
215
|
+
// The caller's own review history (with product context)
|
|
216
|
+
const mine = await store.reviews.listMine({ sort: "newest" });
|
|
217
|
+
const minePage = await store.reviews.listMinePage({ page: 1, limit: 12 });
|
|
218
|
+
minePage.meta;
|
|
219
|
+
```
|
|
188
220
|
|
|
189
221
|
Variant rows include `available` (on-hand minus reservations) and `in_stock`,
|
|
190
222
|
both in the product detail and `/products/:id/variants` responses.
|
|
@@ -198,6 +230,18 @@ tambala; the API converts those at charge time.
|
|
|
198
230
|
```ts
|
|
199
231
|
await store.cart.addItem({ product_id: "…", qty: 2 });
|
|
200
232
|
await store.cart.addItem({ product_id: "…", variant_id: "…", qty: 1 });
|
|
233
|
+
|
|
234
|
+
// Batch add: per-line results plus the full updated cart
|
|
235
|
+
const bulk = await store.cart.addItems({
|
|
236
|
+
atomic: true,
|
|
237
|
+
items: [
|
|
238
|
+
{ product_id: "…", qty: 2 },
|
|
239
|
+
{ product_id: "…", variant_id: "…", qty: 1 },
|
|
240
|
+
],
|
|
241
|
+
});
|
|
242
|
+
bulk.results; // [{ index, id, status, data, error? }]
|
|
243
|
+
bulk.cart; // no follow-up GET needed
|
|
244
|
+
|
|
201
245
|
await store.cart.setQty("cart-line-id", 3);
|
|
202
246
|
await store.cart.removeItem("cart-line-id");
|
|
203
247
|
|
|
@@ -231,6 +275,10 @@ await store.wishlist.add(productId);
|
|
|
231
275
|
const wishlist = await store.wishlist.list();
|
|
232
276
|
await store.wishlist.remove(productId); // product ID, not row ID
|
|
233
277
|
|
|
278
|
+
// Batch heart / unheart (idempotent; already-hearted rows succeed)
|
|
279
|
+
await store.wishlist.addBulk({ productIds: [id1, id2] });
|
|
280
|
+
await store.wishlist.removeBulk({ ids: [id1, id2], dryRun: false });
|
|
281
|
+
|
|
234
282
|
const summary = await store.reviews.summary(productId);
|
|
235
283
|
await store.reviews.create(productId, { rating: 5, title: "Great", body: "…" });
|
|
236
284
|
await store.reviews.update(reviewId, { rating: 4 });
|
|
@@ -256,6 +304,38 @@ await store.inventory.setStock({ product_id: product.id, qty: 50 });
|
|
|
256
304
|
await store.users.setRole(userId, { role: "admin" });
|
|
257
305
|
await store.reviews.deleteAny(reviewId); // moderation
|
|
258
306
|
|
|
307
|
+
// Bulk product + variant + stock writes. Per-item failures stay inside
|
|
308
|
+
// the 200 envelope; atomic rolls the whole batch back.
|
|
309
|
+
await store.products.bulkCreate({ items: [draft1, draft2] });
|
|
310
|
+
await store.products.bulkUpdate({
|
|
311
|
+
updates: [{ id: productId, patch: { is_active: false } }],
|
|
312
|
+
});
|
|
313
|
+
await store.products.bulkDelete({ ids: [id1, id2], dryRun: true });
|
|
314
|
+
|
|
315
|
+
await store.variants.createBulk(product.id, { items: [sku1, sku2] });
|
|
316
|
+
await store.variants.updateBulk({
|
|
317
|
+
updates: [{ id: skuId, patch: { price_cents: 2500 } }],
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
await store.inventory.setStockBulk({
|
|
321
|
+
atomic: true,
|
|
322
|
+
items: [
|
|
323
|
+
{ product_id: id1, qty: 50 },
|
|
324
|
+
{ product_id: id1, variant_id: skuId, qty: 20 },
|
|
325
|
+
],
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Batch availability read; unknown ids land in `notFound`
|
|
329
|
+
const stock = await store.inventory.getStockByVariants([skuId, otherSkuId]);
|
|
330
|
+
stock.stock;
|
|
331
|
+
stock.notFound;
|
|
332
|
+
|
|
333
|
+
// Bulk writes accept an optional replay-safe key (24h window)
|
|
334
|
+
await store.products.bulkCreate({
|
|
335
|
+
idempotencyKey: "catalog-import-2026-11-04",
|
|
336
|
+
items: [draft1, draft2],
|
|
337
|
+
});
|
|
338
|
+
|
|
259
339
|
// Admin user directory: filters + pagination
|
|
260
340
|
const users = await store.users.list({ role: "customer", q: "aisha", sort: "newest" });
|
|
261
341
|
const page = await store.users.listPage({ email_verified: false, page: 1, limit: 50 });
|
|
@@ -272,6 +352,12 @@ Cloudinary finishes. The method resolves as soon as the job is accepted.
|
|
|
272
352
|
const file = fileInput.files![0];
|
|
273
353
|
await store.products.uploadImage(productId, file);
|
|
274
354
|
|
|
355
|
+
// Gallery: list, batch upload, reorder/primary, delete
|
|
356
|
+
const images = await store.products.listImages(productId);
|
|
357
|
+
await store.products.uploadImages(productId, [file, otherFile]); // up to 20
|
|
358
|
+
await store.products.setPrimaryImage(productId, images[0].id);
|
|
359
|
+
await store.products.deleteImage(productId, images[0].id);
|
|
360
|
+
|
|
275
361
|
// Node: wrap a Buffer
|
|
276
362
|
import { readFile } from "node:fs/promises";
|
|
277
363
|
const data = await readFile("./photo.jpg");
|
package/dist/index.cjs
CHANGED
|
@@ -433,6 +433,11 @@ var AuthResource = class {
|
|
|
433
433
|
}
|
|
434
434
|
};
|
|
435
435
|
|
|
436
|
+
// src/headers.ts
|
|
437
|
+
function idempotencyHeaders(key) {
|
|
438
|
+
return key ? { "Idempotency-Key": key } : void 0;
|
|
439
|
+
}
|
|
440
|
+
|
|
436
441
|
// src/path.ts
|
|
437
442
|
function segment(value) {
|
|
438
443
|
return encodeURIComponent(value);
|
|
@@ -444,9 +449,34 @@ var CartResource = class {
|
|
|
444
449
|
this.http = http;
|
|
445
450
|
}
|
|
446
451
|
http;
|
|
447
|
-
/** Active cart, created empty on first use. */
|
|
448
|
-
get() {
|
|
449
|
-
return this.http.request("GET", "/cart"
|
|
452
|
+
/** Active cart, created empty on first use (paging meta is dropped). */
|
|
453
|
+
get(params = {}) {
|
|
454
|
+
return this.http.request("GET", "/cart", {
|
|
455
|
+
query: {
|
|
456
|
+
q: params.q,
|
|
457
|
+
product_id: params.product_id,
|
|
458
|
+
variant_id: params.variant_id,
|
|
459
|
+
sort: params.sort,
|
|
460
|
+
page: params.page,
|
|
461
|
+
limit: params.limit,
|
|
462
|
+
offset: params.offset
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
/** Same as `get`, plus the `meta` paging counters. */
|
|
467
|
+
getPage(params = {}) {
|
|
468
|
+
return this.http.request("GET", "/cart", {
|
|
469
|
+
query: {
|
|
470
|
+
q: params.q,
|
|
471
|
+
product_id: params.product_id,
|
|
472
|
+
variant_id: params.variant_id,
|
|
473
|
+
sort: params.sort,
|
|
474
|
+
page: params.page,
|
|
475
|
+
limit: params.limit,
|
|
476
|
+
offset: params.offset
|
|
477
|
+
},
|
|
478
|
+
unwrap: false
|
|
479
|
+
});
|
|
450
480
|
}
|
|
451
481
|
/** Validates against the catalog and snapshots the current price. */
|
|
452
482
|
addItem(input) {
|
|
@@ -455,6 +485,22 @@ var CartResource = class {
|
|
|
455
485
|
retry: false
|
|
456
486
|
});
|
|
457
487
|
}
|
|
488
|
+
/**
|
|
489
|
+
* Adds up to MAX_BULK_ITEMS lines in one request. Per-line results carry
|
|
490
|
+
* the written line, plus the full updated cart.
|
|
491
|
+
*/
|
|
492
|
+
addItems(input) {
|
|
493
|
+
const { idempotencyKey, ...body } = input;
|
|
494
|
+
return this.http.request(
|
|
495
|
+
"POST",
|
|
496
|
+
"/cart/items/bulk",
|
|
497
|
+
{
|
|
498
|
+
body,
|
|
499
|
+
headers: idempotencyHeaders(idempotencyKey),
|
|
500
|
+
retry: false
|
|
501
|
+
}
|
|
502
|
+
);
|
|
503
|
+
}
|
|
458
504
|
/** `qty: 0` removes the line. */
|
|
459
505
|
setQty(itemId, qty) {
|
|
460
506
|
return this.http.request(
|
|
@@ -477,12 +523,11 @@ var CartResource = class {
|
|
|
477
523
|
};
|
|
478
524
|
|
|
479
525
|
// src/upload.ts
|
|
480
|
-
function
|
|
481
|
-
const form = new FormData();
|
|
526
|
+
function appendFile(form, file, field) {
|
|
482
527
|
if (typeof Blob !== "undefined" && file instanceof Blob) {
|
|
483
528
|
const name = typeof File !== "undefined" && file instanceof File ? file.name : "upload";
|
|
484
529
|
form.append(field, file, name);
|
|
485
|
-
return
|
|
530
|
+
return;
|
|
486
531
|
}
|
|
487
532
|
if (typeof file !== "object" || file === null || !("data" in file) || typeof file.name !== "string") {
|
|
488
533
|
throw new TypeError(
|
|
@@ -493,6 +538,19 @@ function createUploadForm(file, field = "file") {
|
|
|
493
538
|
type: file.type ?? "application/octet-stream"
|
|
494
539
|
});
|
|
495
540
|
form.append(field, blob, file.name);
|
|
541
|
+
}
|
|
542
|
+
function createUploadForm(file, field = "file") {
|
|
543
|
+
const form = new FormData();
|
|
544
|
+
appendFile(form, file, field);
|
|
545
|
+
return form;
|
|
546
|
+
}
|
|
547
|
+
function createUploadFormMany(files, field = "files") {
|
|
548
|
+
const list = Array.isArray(files) ? files : [files];
|
|
549
|
+
if (list.length === 0) {
|
|
550
|
+
throw new TypeError("At least one UploadFile is required");
|
|
551
|
+
}
|
|
552
|
+
const form = new FormData();
|
|
553
|
+
for (const file of list) appendFile(form, file, field);
|
|
496
554
|
return form;
|
|
497
555
|
}
|
|
498
556
|
|
|
@@ -597,6 +655,21 @@ var InventoryResource = class {
|
|
|
597
655
|
}
|
|
598
656
|
});
|
|
599
657
|
}
|
|
658
|
+
/**
|
|
659
|
+
* Reads availability for up to MAX_BULK_ITEMS variants in one request.
|
|
660
|
+
* Unknown ids are returned in `notFound` instead of failing.
|
|
661
|
+
*/
|
|
662
|
+
getStockByVariants(variantIds) {
|
|
663
|
+
return this.http.request(
|
|
664
|
+
"GET",
|
|
665
|
+
"/admin/inventory/stock",
|
|
666
|
+
{
|
|
667
|
+
query: {
|
|
668
|
+
variantIds: Array.isArray(variantIds) ? variantIds : [variantIds]
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
);
|
|
672
|
+
}
|
|
600
673
|
/** Upserts the on-hand quantity (restocking). */
|
|
601
674
|
setStock(input) {
|
|
602
675
|
return this.http.request("POST", "/admin/inventory/stock", {
|
|
@@ -604,6 +677,19 @@ var InventoryResource = class {
|
|
|
604
677
|
retry: false
|
|
605
678
|
});
|
|
606
679
|
}
|
|
680
|
+
/** Sets on-hand stock for many SKUs; `atomic` commits all or none. */
|
|
681
|
+
setStockBulk(input) {
|
|
682
|
+
const { idempotencyKey, ...body } = input;
|
|
683
|
+
return this.http.request(
|
|
684
|
+
"POST",
|
|
685
|
+
"/admin/inventory/stock/bulk",
|
|
686
|
+
{
|
|
687
|
+
body,
|
|
688
|
+
headers: idempotencyHeaders(idempotencyKey),
|
|
689
|
+
retry: false
|
|
690
|
+
}
|
|
691
|
+
);
|
|
692
|
+
}
|
|
607
693
|
/** Holds all lines atomically; one short line fails the whole order. */
|
|
608
694
|
reserve(input) {
|
|
609
695
|
return this.http.request(
|
|
@@ -645,9 +731,40 @@ var OrdersResource = class {
|
|
|
645
731
|
retry: false
|
|
646
732
|
});
|
|
647
733
|
}
|
|
648
|
-
/** Newest first. */
|
|
649
|
-
list() {
|
|
650
|
-
return this.http.request("GET", "/orders"
|
|
734
|
+
/** Newest first (paging meta is dropped). */
|
|
735
|
+
list(params = {}) {
|
|
736
|
+
return this.http.request("GET", "/orders", {
|
|
737
|
+
query: {
|
|
738
|
+
status: params.status,
|
|
739
|
+
currency: params.currency,
|
|
740
|
+
created_after: params.created_after,
|
|
741
|
+
created_before: params.created_before,
|
|
742
|
+
min_total_cents: params.min_total_cents,
|
|
743
|
+
max_total_cents: params.max_total_cents,
|
|
744
|
+
sort: params.sort,
|
|
745
|
+
page: params.page,
|
|
746
|
+
limit: params.limit,
|
|
747
|
+
offset: params.offset
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
/** Same filters as `list`, plus the `meta` paging counters. */
|
|
752
|
+
listPage(params = {}) {
|
|
753
|
+
return this.http.request("GET", "/orders", {
|
|
754
|
+
query: {
|
|
755
|
+
status: params.status,
|
|
756
|
+
currency: params.currency,
|
|
757
|
+
created_after: params.created_after,
|
|
758
|
+
created_before: params.created_before,
|
|
759
|
+
min_total_cents: params.min_total_cents,
|
|
760
|
+
max_total_cents: params.max_total_cents,
|
|
761
|
+
sort: params.sort,
|
|
762
|
+
page: params.page,
|
|
763
|
+
limit: params.limit,
|
|
764
|
+
offset: params.offset
|
|
765
|
+
},
|
|
766
|
+
unwrap: false
|
|
767
|
+
});
|
|
651
768
|
}
|
|
652
769
|
get(id) {
|
|
653
770
|
return this.http.request("GET", `/orders/${segment(id)}`);
|
|
@@ -701,6 +818,7 @@ var ProductsResource = class {
|
|
|
701
818
|
return this.http.request("GET", "/products", {
|
|
702
819
|
query: {
|
|
703
820
|
q: params.q,
|
|
821
|
+
ids: params.ids,
|
|
704
822
|
category_id: params.category_id,
|
|
705
823
|
category_slug: params.category_slug,
|
|
706
824
|
min_price_cents: params.min_price_cents,
|
|
@@ -724,6 +842,7 @@ var ProductsResource = class {
|
|
|
724
842
|
return this.http.request("GET", "/products", {
|
|
725
843
|
query: {
|
|
726
844
|
q: params.q,
|
|
845
|
+
ids: params.ids,
|
|
727
846
|
category_id: params.category_id,
|
|
728
847
|
category_slug: params.category_slug,
|
|
729
848
|
min_price_cents: params.min_price_cents,
|
|
@@ -789,6 +908,86 @@ var ProductsResource = class {
|
|
|
789
908
|
{ form: createUploadForm(file), retry: false }
|
|
790
909
|
);
|
|
791
910
|
}
|
|
911
|
+
/**
|
|
912
|
+
* Admin only. Creates products in bulk. Per-item failures stay inside
|
|
913
|
+
* the 200 envelope; `atomic` rolls the whole batch back.
|
|
914
|
+
*/
|
|
915
|
+
bulkCreate(input) {
|
|
916
|
+
const { idempotencyKey, ...body } = input;
|
|
917
|
+
return this.http.request(
|
|
918
|
+
"POST",
|
|
919
|
+
"/admin/products/bulk",
|
|
920
|
+
{
|
|
921
|
+
body,
|
|
922
|
+
headers: idempotencyHeaders(idempotencyKey),
|
|
923
|
+
retry: false
|
|
924
|
+
}
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
/** Admin only. Applies one partial patch per product id. */
|
|
928
|
+
bulkUpdate(input) {
|
|
929
|
+
const { idempotencyKey, ...body } = input;
|
|
930
|
+
return this.http.request(
|
|
931
|
+
"PATCH",
|
|
932
|
+
"/admin/products/bulk",
|
|
933
|
+
{
|
|
934
|
+
body,
|
|
935
|
+
headers: idempotencyHeaders(idempotencyKey),
|
|
936
|
+
retry: false
|
|
937
|
+
}
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Admin only. POST rather than DELETE so the id list can travel in JSON.
|
|
942
|
+
* `dryRun` reports `wouldDelete` without writing.
|
|
943
|
+
*/
|
|
944
|
+
bulkDelete(input) {
|
|
945
|
+
const { idempotencyKey, ...body } = input;
|
|
946
|
+
return this.http.request(
|
|
947
|
+
"POST",
|
|
948
|
+
"/admin/products/bulk-delete",
|
|
949
|
+
{
|
|
950
|
+
body,
|
|
951
|
+
headers: idempotencyHeaders(idempotencyKey),
|
|
952
|
+
retry: false
|
|
953
|
+
}
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
/** Admin only. Gallery rows, including uploads still queued (empty url). */
|
|
957
|
+
async listImages(id) {
|
|
958
|
+
const result = await this.http.request(
|
|
959
|
+
"GET",
|
|
960
|
+
`/admin/products/${segment(id)}/images`
|
|
961
|
+
);
|
|
962
|
+
return result.images;
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Admin only. Multipart with repeated `files` parts (up to 20 per
|
|
966
|
+
* request). Each accepted file is queued asynchronously (HTTP 202).
|
|
967
|
+
*/
|
|
968
|
+
async uploadImages(id, files) {
|
|
969
|
+
return this.http.request(
|
|
970
|
+
"POST",
|
|
971
|
+
`/admin/products/${segment(id)}/images`,
|
|
972
|
+
{ form: createUploadFormMany(files), retry: false }
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
/** Admin only. Promotes the next ready image when the primary is deleted. */
|
|
976
|
+
deleteImage(id, imageId) {
|
|
977
|
+
return this.http.request(
|
|
978
|
+
"DELETE",
|
|
979
|
+
`/admin/products/${segment(id)}/images/${segment(imageId)}`,
|
|
980
|
+
{ retry: false }
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
/** Admin only. Points the storefront primary image at one gallery row. */
|
|
984
|
+
setPrimaryImage(id, imageId) {
|
|
985
|
+
return this.http.request(
|
|
986
|
+
"PUT",
|
|
987
|
+
`/admin/products/${segment(id)}/images/${segment(imageId)}/primary`,
|
|
988
|
+
{ retry: false }
|
|
989
|
+
);
|
|
990
|
+
}
|
|
792
991
|
};
|
|
793
992
|
|
|
794
993
|
// src/resources/reviews.ts
|
|
@@ -797,14 +996,47 @@ var ReviewsResource = class {
|
|
|
797
996
|
this.http = http;
|
|
798
997
|
}
|
|
799
998
|
http;
|
|
800
|
-
/** Public aggregate plus
|
|
801
|
-
summary(productId) {
|
|
999
|
+
/** Public aggregate plus one filtered review page (`summary.meta`). */
|
|
1000
|
+
summary(productId, params = {}) {
|
|
802
1001
|
return this.http.request(
|
|
803
1002
|
"GET",
|
|
804
1003
|
`/products/${segment(productId)}/reviews`,
|
|
805
|
-
{
|
|
1004
|
+
{
|
|
1005
|
+
query: {
|
|
1006
|
+
q: params.q,
|
|
1007
|
+
rating: params.rating,
|
|
1008
|
+
sort: params.sort,
|
|
1009
|
+
page: params.page,
|
|
1010
|
+
limit: params.limit,
|
|
1011
|
+
offset: params.offset
|
|
1012
|
+
},
|
|
1013
|
+
skipAuth: true
|
|
1014
|
+
}
|
|
806
1015
|
);
|
|
807
1016
|
}
|
|
1017
|
+
/** The caller's own reviews with product context (paging meta dropped). */
|
|
1018
|
+
listMine(params = {}) {
|
|
1019
|
+
return this.http.request("GET", "/reviews/me", {
|
|
1020
|
+
query: {
|
|
1021
|
+
sort: params.sort,
|
|
1022
|
+
page: params.page,
|
|
1023
|
+
limit: params.limit,
|
|
1024
|
+
offset: params.offset
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
/** Same as `listMine`, plus the `meta` paging counters. */
|
|
1029
|
+
listMinePage(params = {}) {
|
|
1030
|
+
return this.http.request("GET", "/reviews/me", {
|
|
1031
|
+
query: {
|
|
1032
|
+
sort: params.sort,
|
|
1033
|
+
page: params.page,
|
|
1034
|
+
limit: params.limit,
|
|
1035
|
+
offset: params.offset
|
|
1036
|
+
},
|
|
1037
|
+
unwrap: false
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
808
1040
|
/** One review per user per product. */
|
|
809
1041
|
create(productId, input) {
|
|
810
1042
|
return this.http.request(
|
|
@@ -849,8 +1081,9 @@ var UsersResource = class {
|
|
|
849
1081
|
me() {
|
|
850
1082
|
return this.http.request("GET", "/users/me");
|
|
851
1083
|
}
|
|
1084
|
+
/** Admin only. There is no public `GET /users/{id}` — owners use `me()`. */
|
|
852
1085
|
get(id) {
|
|
853
|
-
return this.http.request("GET", `/users/${segment(id)}`);
|
|
1086
|
+
return this.http.request("GET", `/admin/users/${segment(id)}`);
|
|
854
1087
|
}
|
|
855
1088
|
/** Admin only. User directory without envelope metadata. */
|
|
856
1089
|
list(params = {}) {
|
|
@@ -930,6 +1163,32 @@ var VariantsResource = class {
|
|
|
930
1163
|
{ retry: false }
|
|
931
1164
|
);
|
|
932
1165
|
}
|
|
1166
|
+
/** Admin only. Creates up to MAX_BULK_ITEMS SKUs for one product. */
|
|
1167
|
+
createBulk(productId, input) {
|
|
1168
|
+
const { idempotencyKey, ...body } = input;
|
|
1169
|
+
return this.http.request(
|
|
1170
|
+
"POST",
|
|
1171
|
+
`/products/${segment(productId)}/variants/bulk`,
|
|
1172
|
+
{
|
|
1173
|
+
body,
|
|
1174
|
+
headers: idempotencyHeaders(idempotencyKey),
|
|
1175
|
+
retry: false
|
|
1176
|
+
}
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
/** Admin only. Applies one partial patch per variant id. */
|
|
1180
|
+
updateBulk(input) {
|
|
1181
|
+
const { idempotencyKey, ...body } = input;
|
|
1182
|
+
return this.http.request(
|
|
1183
|
+
"PATCH",
|
|
1184
|
+
"/admin/variants/bulk",
|
|
1185
|
+
{
|
|
1186
|
+
body,
|
|
1187
|
+
headers: idempotencyHeaders(idempotencyKey),
|
|
1188
|
+
retry: false
|
|
1189
|
+
}
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
933
1192
|
};
|
|
934
1193
|
|
|
935
1194
|
// src/resources/wishlist.ts
|
|
@@ -938,9 +1197,32 @@ var WishlistResource = class {
|
|
|
938
1197
|
this.http = http;
|
|
939
1198
|
}
|
|
940
1199
|
http;
|
|
941
|
-
/** Newest first. */
|
|
942
|
-
list() {
|
|
943
|
-
return this.http.request("GET", "/wishlist"
|
|
1200
|
+
/** Newest first (paging meta is dropped). */
|
|
1201
|
+
list(params = {}) {
|
|
1202
|
+
return this.http.request("GET", "/wishlist", {
|
|
1203
|
+
query: {
|
|
1204
|
+
q: params.q,
|
|
1205
|
+
category_id: params.category_id,
|
|
1206
|
+
sort: params.sort,
|
|
1207
|
+
page: params.page,
|
|
1208
|
+
limit: params.limit,
|
|
1209
|
+
offset: params.offset
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
/** Same filters as `list`, plus the `meta` paging counters. */
|
|
1214
|
+
listPage(params = {}) {
|
|
1215
|
+
return this.http.request("GET", "/wishlist", {
|
|
1216
|
+
query: {
|
|
1217
|
+
q: params.q,
|
|
1218
|
+
category_id: params.category_id,
|
|
1219
|
+
sort: params.sort,
|
|
1220
|
+
page: params.page,
|
|
1221
|
+
limit: params.limit,
|
|
1222
|
+
offset: params.offset
|
|
1223
|
+
},
|
|
1224
|
+
unwrap: false
|
|
1225
|
+
});
|
|
944
1226
|
}
|
|
945
1227
|
/** Idempotent: re-hearting succeeds silently. */
|
|
946
1228
|
add(productId) {
|
|
@@ -957,6 +1239,35 @@ var WishlistResource = class {
|
|
|
957
1239
|
{ retry: false }
|
|
958
1240
|
);
|
|
959
1241
|
}
|
|
1242
|
+
/** Hearts up to MAX_BULK_ITEMS products; already-hearted rows succeed. */
|
|
1243
|
+
addBulk(input) {
|
|
1244
|
+
const { idempotencyKey, ...body } = input;
|
|
1245
|
+
return this.http.request(
|
|
1246
|
+
"POST",
|
|
1247
|
+
"/wishlist/bulk",
|
|
1248
|
+
{
|
|
1249
|
+
body,
|
|
1250
|
+
headers: idempotencyHeaders(idempotencyKey),
|
|
1251
|
+
retry: false
|
|
1252
|
+
}
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Unhearts by product id. `dryRun` reports `wouldDelete` without
|
|
1257
|
+
* writing; already-removed rows succeed.
|
|
1258
|
+
*/
|
|
1259
|
+
removeBulk(input) {
|
|
1260
|
+
const { idempotencyKey, ...body } = input;
|
|
1261
|
+
return this.http.request(
|
|
1262
|
+
"POST",
|
|
1263
|
+
"/wishlist/bulk-delete",
|
|
1264
|
+
{
|
|
1265
|
+
body,
|
|
1266
|
+
headers: idempotencyHeaders(idempotencyKey),
|
|
1267
|
+
retry: false
|
|
1268
|
+
}
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
960
1271
|
};
|
|
961
1272
|
|
|
962
1273
|
// src/client.ts
|
|
@@ -1118,6 +1429,7 @@ exports.MalawiStoreError = MalawiStoreError;
|
|
|
1118
1429
|
exports.buildQuery = buildQuery;
|
|
1119
1430
|
exports.constructStripeEvent = constructStripeEvent;
|
|
1120
1431
|
exports.createUploadForm = createUploadForm;
|
|
1432
|
+
exports.createUploadFormMany = createUploadFormMany;
|
|
1121
1433
|
exports.isMalawiStoreError = isMalawiStoreError;
|
|
1122
1434
|
exports.verifyStripeSignature = verifyStripeSignature;
|
|
1123
1435
|
//# sourceMappingURL=index.cjs.map
|