react-shopwave-connect 0.4.1 → 0.4.2
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 +3 -3
- package/dist/core/index.cjs +15 -4
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +26 -5
- package/dist/core/index.d.ts +26 -5
- package/dist/core/index.js +15 -4
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +15 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -5
- package/dist/index.d.ts +26 -5
- package/dist/index.js +15 -4
- package/dist/index.js.map +1 -1
- package/dist/next/index.cjs +59 -2
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +18 -3
- package/dist/next/index.d.ts +18 -3
- package/dist/next/index.js +59 -2
- package/dist/next/index.js.map +1 -1
- package/dist/server/index.cjs +59 -2
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +18 -3
- package/dist/server/index.d.ts +18 -3
- package/dist/server/index.js +59 -2
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/next/index.d.ts
CHANGED
|
@@ -106,13 +106,25 @@ interface EntityDefinition {
|
|
|
106
106
|
/**
|
|
107
107
|
* How a delete is done upstream:
|
|
108
108
|
* - `"delete"`: `DELETE {apiUrl}/<upstream>` with the `idHeader` (205, empty body)
|
|
109
|
-
* - `"retire"`: the API has no DELETE; the record is retired by
|
|
110
|
-
* `
|
|
109
|
+
* - `"retire"`: the API has no DELETE; the record is retired by setting
|
|
110
|
+
* `retireField` to now (employees: `exitDate`, promotions: `endDate`)
|
|
111
111
|
* - `"none"`: can't be deleted
|
|
112
112
|
*/
|
|
113
113
|
deleteMode: "delete" | "retire" | "none";
|
|
114
114
|
/** Date field set to "now" when `deleteMode` is `"retire"`. */
|
|
115
115
|
retireField?: string;
|
|
116
|
+
/**
|
|
117
|
+
* Retire by reading the record and POSTing all of it back with the date(s)
|
|
118
|
+
* changed, instead of a partial `{ id, [retireField]: <now> }`. Needed where
|
|
119
|
+
* the API rejects a partial save (promotions: error 899, live 14 Oct 2026).
|
|
120
|
+
*/
|
|
121
|
+
retireWithRecord?: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Start-date field of a record retired with `retireWithRecord`. If the record
|
|
124
|
+
* hasn't started yet (start after now), the start moves to now as well, so
|
|
125
|
+
* start = end and the record is expired without ever running.
|
|
126
|
+
*/
|
|
127
|
+
retireStartField?: string;
|
|
116
128
|
}
|
|
117
129
|
type EntityKind = "product" | "category" | "store" | "promotion" | "employee" | "consumer";
|
|
118
130
|
/**
|
|
@@ -120,7 +132,10 @@ type EntityKind = "product" | "category" | "store" | "promotion" | "employee" |
|
|
|
120
132
|
* (https://developer.merchantstack.com/api-reference.html) and checked against
|
|
121
133
|
* the live API where noted:
|
|
122
134
|
* - category/product/store: GET filtered by `<x>Ids`, POST upsert, DELETE with `<x>Id` → 205.
|
|
123
|
-
* - promotion: no DELETE;
|
|
135
|
+
* - promotion: no DELETE; promotions are never deleted, only expired. Expiring
|
|
136
|
+
* reads the promotion and saves the whole record back with `endDate` = now (and
|
|
137
|
+
* `startDate` = now too if it hadn't started); the live API rejects a partial
|
|
138
|
+
* `{ id, endDate }` save with error 899. Dates are UTC `YYYY-MM-DD HH:mm:ss`.
|
|
124
139
|
* - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
|
|
125
140
|
* changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
|
|
126
141
|
* - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
|
package/dist/next/index.js
CHANGED
|
@@ -255,7 +255,10 @@ function safeEqual(a, b) {
|
|
|
255
255
|
* (https://developer.merchantstack.com/api-reference.html) and checked against
|
|
256
256
|
* the live API where noted:
|
|
257
257
|
* - category/product/store: GET filtered by `<x>Ids`, POST upsert, DELETE with `<x>Id` → 205.
|
|
258
|
-
* - promotion: no DELETE;
|
|
258
|
+
* - promotion: no DELETE; promotions are never deleted, only expired. Expiring
|
|
259
|
+
* reads the promotion and saves the whole record back with `endDate` = now (and
|
|
260
|
+
* `startDate` = now too if it hadn't started); the live API rejects a partial
|
|
261
|
+
* `{ id, endDate }` save with error 899. Dates are UTC `YYYY-MM-DD HH:mm:ss`.
|
|
259
262
|
* - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
|
|
260
263
|
* changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
|
|
261
264
|
* - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
|
|
@@ -296,7 +299,9 @@ const SHOPWAVE_ENTITIES = Object.freeze({
|
|
|
296
299
|
idHeader: "promotionId",
|
|
297
300
|
writable: true,
|
|
298
301
|
deleteMode: "retire",
|
|
299
|
-
retireField: "endDate"
|
|
302
|
+
retireField: "endDate",
|
|
303
|
+
retireWithRecord: true,
|
|
304
|
+
retireStartField: "startDate"
|
|
300
305
|
},
|
|
301
306
|
employee: {
|
|
302
307
|
route: "employees",
|
|
@@ -495,6 +500,18 @@ function jsonError(status, error, message) {
|
|
|
495
500
|
function shopwaveDateTime(date = /* @__PURE__ */ new Date()) {
|
|
496
501
|
return date.toISOString().slice(0, 19).replace("T", " ");
|
|
497
502
|
}
|
|
503
|
+
/**
|
|
504
|
+
* Reads a Shopwave datetime: ISO with a zone (what reads return, e.g.
|
|
505
|
+
* `2026-10-20T16:00:00.000Z`) or a zoneless `YYYY-MM-DD HH:mm:ss`, which is UTC.
|
|
506
|
+
* `null` when the value isn't a date.
|
|
507
|
+
*/
|
|
508
|
+
function parseShopwaveDateTime(value) {
|
|
509
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
510
|
+
const text = value.trim();
|
|
511
|
+
const zoneless = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/.test(text);
|
|
512
|
+
const date = new Date(zoneless ? `${text.replace(" ", "T")}Z` : text);
|
|
513
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
514
|
+
}
|
|
498
515
|
const NULL_BODY_STATUS = new Set([
|
|
499
516
|
101,
|
|
500
517
|
103,
|
|
@@ -694,6 +711,45 @@ function createShopwaveApiHandlers(config) {
|
|
|
694
711
|
function badId() {
|
|
695
712
|
return jsonError(400, "invalid_id", "Missing or invalid id");
|
|
696
713
|
}
|
|
714
|
+
/**
|
|
715
|
+
* Retires a record the API won't take a partial save for (promotions): reads it,
|
|
716
|
+
* sets `field` to now (and `def.retireStartField` to now too when the record
|
|
717
|
+
* hasn't started yet), and POSTs the whole record back. Both dates are sent as
|
|
718
|
+
* UTC `YYYY-MM-DD HH:mm:ss`; reads return them as ISO with `Z`.
|
|
719
|
+
*/
|
|
720
|
+
async function retireWithRecord(request, def, id, field) {
|
|
721
|
+
const read = await forward(request, {
|
|
722
|
+
path: def.upstream,
|
|
723
|
+
headers: { [def.idsHeader]: id },
|
|
724
|
+
forwardExtras: false
|
|
725
|
+
});
|
|
726
|
+
if (!read.ok) return read;
|
|
727
|
+
let body;
|
|
728
|
+
try {
|
|
729
|
+
body = await read.json();
|
|
730
|
+
} catch {
|
|
731
|
+
return jsonError(502, "invalid_upstream", `The Shopwave API's ${def.upstream} read wasn't JSON`);
|
|
732
|
+
}
|
|
733
|
+
const map = isPlainObject(body) && isPlainObject(body[def.collection]) ? body[def.collection] : {};
|
|
734
|
+
const record = Object.values(map).find((entry) => isPlainObject(entry) && String(entry.id) === id);
|
|
735
|
+
if (!record) return jsonError(404, "not_found", `No ${def.upstream} with id ${id}`);
|
|
736
|
+
const now = /* @__PURE__ */ new Date();
|
|
737
|
+
const retired = {
|
|
738
|
+
...record,
|
|
739
|
+
[field]: shopwaveDateTime(now)
|
|
740
|
+
};
|
|
741
|
+
const startField = def.retireStartField;
|
|
742
|
+
if (startField) {
|
|
743
|
+
const start = parseShopwaveDateTime(record[startField]);
|
|
744
|
+
if (start) retired[startField] = shopwaveDateTime(start > now ? now : start);
|
|
745
|
+
}
|
|
746
|
+
return forward(request, {
|
|
747
|
+
method: "POST",
|
|
748
|
+
path: def.upstream,
|
|
749
|
+
postBody: { [def.collection]: { 0: retired } },
|
|
750
|
+
forwardExtras: false
|
|
751
|
+
});
|
|
752
|
+
}
|
|
697
753
|
return {
|
|
698
754
|
forward,
|
|
699
755
|
entity,
|
|
@@ -729,6 +785,7 @@ function createShopwaveApiHandlers(config) {
|
|
|
729
785
|
if (def.deleteMode === "none") return notSupported(def, "deletes");
|
|
730
786
|
if (def.deleteMode === "retire") {
|
|
731
787
|
const field = def.retireField ?? "exitDate";
|
|
788
|
+
if (def.retireWithRecord) return retireWithRecord(request, def, id, field);
|
|
732
789
|
return forward(request, {
|
|
733
790
|
method: "POST",
|
|
734
791
|
path: def.upstream,
|