react-shopwave-connect 0.4.1 → 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.
@@ -281,7 +281,10 @@ function safeEqual(a, b) {
281
281
  * (https://developer.merchantstack.com/api-reference.html) and checked against
282
282
  * the live API where noted:
283
283
  * - category/product/store: GET filtered by `<x>Ids`, POST upsert, DELETE with `<x>Id` → 205.
284
- * - promotion: no DELETE; "deleting" a promotion ends it by setting `endDate` to now.
284
+ * - promotion: no DELETE; promotions are never deleted, only expired. Expiring
285
+ * reads the promotion and saves the whole record back with `endDate` = now (and
286
+ * `startDate` = now too if it hadn't started); the live API rejects a partial
287
+ * `{ id, endDate }` save with error 899. Dates are UTC `YYYY-MM-DD HH:mm:ss`.
285
288
  * - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
286
289
  * changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
287
290
  * - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
@@ -322,7 +325,9 @@ const SHOPWAVE_ENTITIES = Object.freeze({
322
325
  idHeader: "promotionId",
323
326
  writable: true,
324
327
  deleteMode: "retire",
325
- retireField: "endDate"
328
+ retireField: "endDate",
329
+ retireWithRecord: true,
330
+ retireStartField: "startDate"
326
331
  },
327
332
  employee: {
328
333
  route: "employees",
@@ -388,6 +393,18 @@ const SHOPWAVE_UPLOAD = Object.freeze({
388
393
  function isUploadKind(value) {
389
394
  return typeof value === "string" && Object.prototype.hasOwnProperty.call(UPLOAD_KINDS, value);
390
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
+ });
391
408
 
392
409
  //#endregion
393
410
  //#region src/server/api.ts
@@ -521,6 +538,18 @@ function jsonError(status, error, message) {
521
538
  function shopwaveDateTime(date = /* @__PURE__ */ new Date()) {
522
539
  return date.toISOString().slice(0, 19).replace("T", " ");
523
540
  }
541
+ /**
542
+ * Reads a Shopwave datetime: ISO with a zone (what reads return, e.g.
543
+ * `2026-10-20T16:00:00.000Z`) or a zoneless `YYYY-MM-DD HH:mm:ss`, which is UTC.
544
+ * `null` when the value isn't a date.
545
+ */
546
+ function parseShopwaveDateTime(value) {
547
+ if (typeof value !== "string" || !value.trim()) return null;
548
+ const text = value.trim();
549
+ const zoneless = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/.test(text);
550
+ const date = new Date(zoneless ? `${text.replace(" ", "T")}Z` : text);
551
+ return Number.isNaN(date.getTime()) ? null : date;
552
+ }
524
553
  const NULL_BODY_STATUS = new Set([
525
554
  101,
526
555
  103,
@@ -720,6 +749,45 @@ function createShopwaveApiHandlers(config) {
720
749
  function badId() {
721
750
  return jsonError(400, "invalid_id", "Missing or invalid id");
722
751
  }
752
+ /**
753
+ * Retires a record the API won't take a partial save for (promotions): reads it,
754
+ * sets `field` to now (and `def.retireStartField` to now too when the record
755
+ * hasn't started yet), and POSTs the whole record back. Both dates are sent as
756
+ * UTC `YYYY-MM-DD HH:mm:ss`; reads return them as ISO with `Z`.
757
+ */
758
+ async function retireWithRecord(request, def, id, field) {
759
+ const read = await forward(request, {
760
+ path: def.upstream,
761
+ headers: { [def.idsHeader]: id },
762
+ forwardExtras: false
763
+ });
764
+ if (!read.ok) return read;
765
+ let body;
766
+ try {
767
+ body = await read.json();
768
+ } catch {
769
+ return jsonError(502, "invalid_upstream", `The Shopwave API's ${def.upstream} read wasn't JSON`);
770
+ }
771
+ const map = isPlainObject(body) && isPlainObject(body[def.collection]) ? body[def.collection] : {};
772
+ const record = Object.values(map).find((entry) => isPlainObject(entry) && String(entry.id) === id);
773
+ if (!record) return jsonError(404, "not_found", `No ${def.upstream} with id ${id}`);
774
+ const now = /* @__PURE__ */ new Date();
775
+ const retired = {
776
+ ...record,
777
+ [field]: shopwaveDateTime(now)
778
+ };
779
+ const startField = def.retireStartField;
780
+ if (startField) {
781
+ const start = parseShopwaveDateTime(record[startField]);
782
+ if (start) retired[startField] = shopwaveDateTime(start > now ? now : start);
783
+ }
784
+ return forward(request, {
785
+ method: "POST",
786
+ path: def.upstream,
787
+ postBody: { [def.collection]: { 0: retired } },
788
+ forwardExtras: false
789
+ });
790
+ }
723
791
  return {
724
792
  forward,
725
793
  entity,
@@ -755,6 +823,7 @@ function createShopwaveApiHandlers(config) {
755
823
  if (def.deleteMode === "none") return notSupported(def, "deletes");
756
824
  if (def.deleteMode === "retire") {
757
825
  const field = def.retireField ?? "exitDate";
826
+ if (def.retireWithRecord) return retireWithRecord(request, def, id, field);
758
827
  return forward(request, {
759
828
  method: "POST",
760
829
  path: def.upstream,
@@ -777,6 +846,14 @@ function createShopwaveApiHandlers(config) {
777
846
  passthrough(path) {
778
847
  return { GET: (request) => forward(request, { path }) };
779
848
  },
849
+ lookup(kind) {
850
+ const def = SHOPWAVE_LOOKUPS[kind];
851
+ if (!def) throw new Error(`Unknown Shopwave lookup "${kind}"`);
852
+ return { GET: (request) => forward(request, {
853
+ path: def.upstream,
854
+ forwardExtras: false
855
+ }) };
856
+ },
780
857
  resource(kind) {
781
858
  const def = resourceDef(kind);
782
859
  const save = (request) => writeResource(request, def);
@@ -1119,6 +1196,9 @@ function createShopwaveAuth(config) {
1119
1196
  * export const { GET } = shopwave.resource("user");
1120
1197
  * // app/api/upload/route.ts
1121
1198
  * export const { POST } = shopwave.upload();
1199
+ * // app/api/products/tags/route.ts, app/api/products/instance-tags/route.ts
1200
+ * export const { GET } = shopwave.lookup("productTags");
1201
+ * export const { GET } = shopwave.lookup("productInstanceTags");
1122
1202
  * ```
1123
1203
  */
1124
1204
  function createShopwaveApi(config) {
@@ -1132,6 +1212,7 @@ function createShopwaveApi(config) {
1132
1212
 
1133
1213
  //#endregion
1134
1214
  exports.SHOPWAVE_ENTITIES = SHOPWAVE_ENTITIES;
1215
+ exports.SHOPWAVE_LOOKUPS = SHOPWAVE_LOOKUPS;
1135
1216
  exports.SHOPWAVE_RESOURCES = SHOPWAVE_RESOURCES;
1136
1217
  exports.ShopwaveAuthError = ShopwaveAuthError;
1137
1218
  exports.UPLOAD_KINDS = UPLOAD_KINDS;