react-shopwave-connect 0.4.2 → 0.4.3
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 +14 -2
- package/dist/core/index.cjs +104 -44
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +54 -4
- package/dist/core/index.d.ts +54 -4
- package/dist/core/index.js +101 -45
- package/dist/core/index.js.map +1 -1
- package/dist/hooks/index.cjs +129 -44
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.cts +21 -1
- package/dist/hooks/index.d.ts +21 -1
- package/dist/hooks/index.js +128 -45
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.cjs +133 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +74 -4
- package/dist/index.d.ts +74 -4
- package/dist/index.js +128 -45
- package/dist/index.js.map +1 -1
- package/dist/next/index.cjs +24 -0
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +45 -4
- package/dist/next/index.d.ts +45 -4
- package/dist/next/index.js +24 -1
- package/dist/next/index.js.map +1 -1
- package/dist/server/index.cjs +21 -0
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +42 -4
- package/dist/server/index.d.ts +42 -4
- package/dist/server/index.js +21 -1
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -226,7 +226,7 @@ The shapes below are the ones adminV1 saves and adminV2 reads, so all three admi
|
|
|
226
226
|
| category | `fetchCategories`, `fetchCategory`, `saveCategory`, `deleteCategory` |
|
|
227
227
|
| consumer | `fetchConsumers`, `fetchConsumer` (read-only) |
|
|
228
228
|
| employee | `fetchEmployees`, `fetchEmployee`, `saveEmployee`, `deleteEmployee` |
|
|
229
|
-
| product | `fetchProducts`, `fetchProductsMap` (batched, keyed), `fetchProduct`, `saveProduct`, `deleteProduct`; prices `toProductInstancesForSave`, `findRepricedInstances`, `isSameInstancePrice` |
|
|
229
|
+
| product | `fetchProducts`, `fetchProductsMap` (batched, keyed), `fetchProduct`, `saveProduct`, `deleteProduct`; prices `toProductInstancesForSave`, `findRepricedInstances`, `isSameInstancePrice`; tag lists `fetchProductTags`, `fetchProductInstanceTags` (`string[]`), `normalizeTagList` |
|
|
230
230
|
| promotion | `fetchPromotions`, `fetchPromotion`, `savePromotion`, `deletePromotion` |
|
|
231
231
|
| store | `fetchStores` (now with `storeIds`), `fetchStore`, `saveStore`, `deleteStore` |
|
|
232
232
|
| report | `fetchReport` |
|
|
@@ -281,7 +281,7 @@ const { data } = useProduct({ storeId }, { baseUrl: "https://api.merchantstack.c
|
|
|
281
281
|
|
|
282
282
|
### Auto-fetch hooks (`useEffect`-based)
|
|
283
283
|
|
|
284
|
-
`useCategory`, `useConsumer`, `useEmployee`, `useProduct`, `usePromotion`, `useStore`, `useReport`, `useSession`, `useMerchant`, `useUser` (`useUser({ enabled: false })` stays idle).
|
|
284
|
+
`useCategory`, `useConsumer`, `useEmployee`, `useProduct`, `useProductTags`, `useProductInstanceTags`, `usePromotion`, `useStore`, `useReport`, `useSession`, `useMerchant`, `useUser` (`useUser({ enabled: false })` stays idle; so do the tag hooks, so a tag field can load its list the first time it opens).
|
|
285
285
|
They fetch on mount and re-run when their arguments change. `useConsumer` and `useReport` stay idle until you pass ids / a query (they return `loading: false`, `data: null` until then). `refetch()` replaces the old `reloadFlag` argument.
|
|
286
286
|
|
|
287
287
|
### Manually-triggered hooks (`useCallback`-based)
|
|
@@ -467,8 +467,15 @@ export const { GET } = shopwave.resource("user");
|
|
|
467
467
|
|
|
468
468
|
// app/api/upload/route.ts — image upload (uploadImage)
|
|
469
469
|
export const { POST } = shopwave.upload(); // or upload({ maxBytes, accept })
|
|
470
|
+
|
|
471
|
+
// app/api/products/tags/route.ts — the merchant's product tags (fetchProductTags)
|
|
472
|
+
export const { GET } = shopwave.lookup("productTags");
|
|
473
|
+
// app/api/products/instance-tags/route.ts — price-instance (channel) tags (fetchProductInstanceTags)
|
|
474
|
+
export const { GET } = shopwave.lookup("productInstanceTags");
|
|
470
475
|
```
|
|
471
476
|
|
|
477
|
+
- `lookup(kind)`: the read-only lists in `SHOPWAVE_LOOKUPS`. `productTags` → `GET /product/tags`, `productInstanceTags` → `GET /product/instance/tags`; both answer `{ tags: string[] }`. They take no parameters, so `extras` aren't forwarded. The app routes are `products/tags` and `products/instance-tags`: static segments, so they sit beside `products/[id]`.
|
|
478
|
+
|
|
472
479
|
- `resource("merchant")`: `PUT`/`POST` take `{ merchant: {…} }` or the bare object and send every field on as `postBody`. The `id` is required (400 without it), because a POST without one would create a second merchant. `resource("user")` answers 405 to writes.
|
|
473
480
|
- `upload()`: takes multipart `file` + `kind`, checks the size (default 10 MB → 413) and type (default `image/*` → 415), and `PUT`s only the file to `/uploader` with the `contentType` header. `extras` are ignored on uploads. Shopwave's reply (201 `{ fileName, path }`) is passed through.
|
|
474
481
|
|
|
@@ -503,6 +510,11 @@ Options: `apiVersion` (default `"2.0"`), `fetch`, `entities` (override a route/h
|
|
|
503
510
|
|
|
504
511
|
---
|
|
505
512
|
|
|
513
|
+
## Contributing
|
|
514
|
+
|
|
515
|
+
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md): how the package is built and the rules for changing it: layers, request flow, contract tables, endpoint kinds (and how to choose one), a checklist for adding an endpoint, code rules, known gaps, and versioning.
|
|
516
|
+
- [CLAUDE.md](CLAUDE.md): the short rules for coding agents working in this repo.
|
|
517
|
+
|
|
506
518
|
## Build
|
|
507
519
|
|
|
508
520
|
Built with [`tsdown`](https://tsdown.dev) into dual ESM + CJS with `.d.ts` types, via separate entries: `core` (no externals), `hooks` (`react`/`react-dom` external), `server` (no externals) and `next` (`next`/`iron-session` external — optional peer dependencies). `server` and `next` are never re-exported from the root entry, so they can't end up in client bundles.
|
package/dist/core/index.cjs
CHANGED
|
@@ -350,6 +350,106 @@ async function fetchProductsMap(productIds, options = {}, batchSize = 200) {
|
|
|
350
350
|
return result;
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
+
//#endregion
|
|
354
|
+
//#region src/core/resources.ts
|
|
355
|
+
/**
|
|
356
|
+
* - merchant: `GET /merchant`; `POST /merchant` with `{ merchant: { id, … } }`
|
|
357
|
+
* updates it (without an `id` the API would create a second merchant, so the
|
|
358
|
+
* route requires one).
|
|
359
|
+
* - user: `GET /user` (read-only here; the API's `POST /user` invites users and
|
|
360
|
+
* needs the admin scope).
|
|
361
|
+
*/
|
|
362
|
+
const SHOPWAVE_RESOURCES = Object.freeze({
|
|
363
|
+
merchant: {
|
|
364
|
+
route: "merchant",
|
|
365
|
+
key: "merchant",
|
|
366
|
+
upstream: "merchant",
|
|
367
|
+
writable: true
|
|
368
|
+
},
|
|
369
|
+
user: {
|
|
370
|
+
route: "user",
|
|
371
|
+
key: "user",
|
|
372
|
+
upstream: "user",
|
|
373
|
+
writable: false
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
/** `UploadKind` → the API's `contentType` value. */
|
|
377
|
+
const UPLOAD_KINDS = Object.freeze({
|
|
378
|
+
merchant: "merchant",
|
|
379
|
+
merchantFeature: "merchantFeature",
|
|
380
|
+
product: "product",
|
|
381
|
+
user: "user",
|
|
382
|
+
applicationLogo: "applicaionLogo",
|
|
383
|
+
applicationImages: "applicaionImages"
|
|
384
|
+
});
|
|
385
|
+
/** How uploads are addressed (app route and Shopwave uploader). */
|
|
386
|
+
const SHOPWAVE_UPLOAD = Object.freeze({
|
|
387
|
+
route: "upload",
|
|
388
|
+
upstream: "uploader",
|
|
389
|
+
kindHeader: "contentType",
|
|
390
|
+
fileField: "file",
|
|
391
|
+
kindField: "kind"
|
|
392
|
+
});
|
|
393
|
+
function isUploadKind(value) {
|
|
394
|
+
return typeof value === "string" && Object.prototype.hasOwnProperty.call(UPLOAD_KINDS, value);
|
|
395
|
+
}
|
|
396
|
+
const SHOPWAVE_LOOKUPS = Object.freeze({
|
|
397
|
+
productTags: {
|
|
398
|
+
route: "products/tags",
|
|
399
|
+
key: "tags",
|
|
400
|
+
upstream: "product/tags"
|
|
401
|
+
},
|
|
402
|
+
productInstanceTags: {
|
|
403
|
+
route: "products/instance-tags",
|
|
404
|
+
key: "tags",
|
|
405
|
+
upstream: "product/instance/tags"
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
//#endregion
|
|
410
|
+
//#region src/core/productTags.ts
|
|
411
|
+
/**
|
|
412
|
+
* Turns a tag list from the API into clean strings: trims each tag, drops
|
|
413
|
+
* blanks and anything that isn't text, and removes exact duplicates (first one
|
|
414
|
+
* wins). The API's own order is kept. A map is accepted too (its values are
|
|
415
|
+
* used), in case the API ever answers keyed by id.
|
|
416
|
+
*/
|
|
417
|
+
function normalizeTagList(value) {
|
|
418
|
+
const list = Array.isArray(value) ? value : value && typeof value === "object" ? Object.values(value) : [];
|
|
419
|
+
const seen = /* @__PURE__ */ new Set();
|
|
420
|
+
const out = [];
|
|
421
|
+
for (const item of list) {
|
|
422
|
+
if (typeof item !== "string" && typeof item !== "number") continue;
|
|
423
|
+
const tag = String(item).trim();
|
|
424
|
+
if (!tag || seen.has(tag)) continue;
|
|
425
|
+
seen.add(tag);
|
|
426
|
+
out.push(tag);
|
|
427
|
+
}
|
|
428
|
+
return out;
|
|
429
|
+
}
|
|
430
|
+
async function fetchLookup(kind, options) {
|
|
431
|
+
const def = SHOPWAVE_LOOKUPS[kind];
|
|
432
|
+
if (!def) throw new Error(`Unknown Shopwave lookup "${kind}"`);
|
|
433
|
+
const json = await apiGet(`/api/${def.route}`, {}, options);
|
|
434
|
+
assertNoApiErrors(json, 200);
|
|
435
|
+
return normalizeTagList(json?.[def.key]);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Every distinct tag used on the merchant's products (`GET /product/tags`),
|
|
439
|
+
* for a tag field's suggestions. Returns `[]` when there are none.
|
|
440
|
+
*/
|
|
441
|
+
function fetchProductTags(options = {}) {
|
|
442
|
+
return fetchLookup("productTags", options);
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Every distinct tag used on the merchant's price instances
|
|
446
|
+
* (`GET /product/instance/tags`): the channel names (Eat In, Takeout, …).
|
|
447
|
+
* Returns `[]` when there are none.
|
|
448
|
+
*/
|
|
449
|
+
function fetchProductInstanceTags(options = {}) {
|
|
450
|
+
return fetchLookup("productInstanceTags", options);
|
|
451
|
+
}
|
|
452
|
+
|
|
353
453
|
//#endregion
|
|
354
454
|
//#region src/core/promotion.ts
|
|
355
455
|
/** Promotion rule identifiers as stored by the Shopwave API. */
|
|
@@ -811,50 +911,6 @@ function buildConsumerMap(consumers) {
|
|
|
811
911
|
return map;
|
|
812
912
|
}
|
|
813
913
|
|
|
814
|
-
//#endregion
|
|
815
|
-
//#region src/core/resources.ts
|
|
816
|
-
/**
|
|
817
|
-
* - merchant: `GET /merchant`; `POST /merchant` with `{ merchant: { id, … } }`
|
|
818
|
-
* updates it (without an `id` the API would create a second merchant, so the
|
|
819
|
-
* route requires one).
|
|
820
|
-
* - user: `GET /user` (read-only here; the API's `POST /user` invites users and
|
|
821
|
-
* needs the admin scope).
|
|
822
|
-
*/
|
|
823
|
-
const SHOPWAVE_RESOURCES = Object.freeze({
|
|
824
|
-
merchant: {
|
|
825
|
-
route: "merchant",
|
|
826
|
-
key: "merchant",
|
|
827
|
-
upstream: "merchant",
|
|
828
|
-
writable: true
|
|
829
|
-
},
|
|
830
|
-
user: {
|
|
831
|
-
route: "user",
|
|
832
|
-
key: "user",
|
|
833
|
-
upstream: "user",
|
|
834
|
-
writable: false
|
|
835
|
-
}
|
|
836
|
-
});
|
|
837
|
-
/** `UploadKind` → the API's `contentType` value. */
|
|
838
|
-
const UPLOAD_KINDS = Object.freeze({
|
|
839
|
-
merchant: "merchant",
|
|
840
|
-
merchantFeature: "merchantFeature",
|
|
841
|
-
product: "product",
|
|
842
|
-
user: "user",
|
|
843
|
-
applicationLogo: "applicaionLogo",
|
|
844
|
-
applicationImages: "applicaionImages"
|
|
845
|
-
});
|
|
846
|
-
/** How uploads are addressed (app route and Shopwave uploader). */
|
|
847
|
-
const SHOPWAVE_UPLOAD = Object.freeze({
|
|
848
|
-
route: "upload",
|
|
849
|
-
upstream: "uploader",
|
|
850
|
-
kindHeader: "contentType",
|
|
851
|
-
fileField: "file",
|
|
852
|
-
kindField: "kind"
|
|
853
|
-
});
|
|
854
|
-
function isUploadKind(value) {
|
|
855
|
-
return typeof value === "string" && Object.prototype.hasOwnProperty.call(UPLOAD_KINDS, value);
|
|
856
|
-
}
|
|
857
|
-
|
|
858
914
|
//#endregion
|
|
859
915
|
//#region src/core/imageFit.ts
|
|
860
916
|
/**
|
|
@@ -1305,6 +1361,7 @@ exports.MERCHANT_READ_ONLY_FIELDS = MERCHANT_READ_ONLY_FIELDS;
|
|
|
1305
1361
|
exports.PromotionRuleSet = PromotionRuleSet;
|
|
1306
1362
|
exports.SHOPWAVE_ENTITIES = SHOPWAVE_ENTITIES;
|
|
1307
1363
|
exports.SHOPWAVE_IMAGE_BASE_URL = SHOPWAVE_IMAGE_BASE_URL;
|
|
1364
|
+
exports.SHOPWAVE_LOOKUPS = SHOPWAVE_LOOKUPS;
|
|
1308
1365
|
exports.SHOPWAVE_RESOURCES = SHOPWAVE_RESOURCES;
|
|
1309
1366
|
exports.SHOPWAVE_UPLOAD = SHOPWAVE_UPLOAD;
|
|
1310
1367
|
exports.ShopwaveApiError = ShopwaveApiError;
|
|
@@ -1338,6 +1395,8 @@ exports.fetchEmployees = fetchEmployees;
|
|
|
1338
1395
|
exports.fetchEntityById = fetchEntityById;
|
|
1339
1396
|
exports.fetchMerchant = fetchMerchant;
|
|
1340
1397
|
exports.fetchProduct = fetchProduct;
|
|
1398
|
+
exports.fetchProductInstanceTags = fetchProductInstanceTags;
|
|
1399
|
+
exports.fetchProductTags = fetchProductTags;
|
|
1341
1400
|
exports.fetchProducts = fetchProducts;
|
|
1342
1401
|
exports.fetchProductsMap = fetchProductsMap;
|
|
1343
1402
|
exports.fetchPromotion = fetchPromotion;
|
|
@@ -1364,6 +1423,7 @@ exports.logoutPath = logoutPath;
|
|
|
1364
1423
|
exports.merchantLinksFromForm = merchantLinksFromForm;
|
|
1365
1424
|
exports.merchantLinksToForm = merchantLinksToForm;
|
|
1366
1425
|
exports.normalizeMerchantLinks = normalizeMerchantLinks;
|
|
1426
|
+
exports.normalizeTagList = normalizeTagList;
|
|
1367
1427
|
exports.parseBasketReportData = parseBasketReportData;
|
|
1368
1428
|
exports.readImageSize = readImageSize;
|
|
1369
1429
|
exports.rolesById = rolesById;
|