react-shopwave-connect 0.4.5 → 0.4.7
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 +19 -11
- package/dist/core/index.cjs +281 -137
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +307 -160
- package/dist/core/index.d.ts +307 -160
- package/dist/core/index.js +276 -138
- package/dist/core/index.js.map +1 -1
- package/dist/hooks/index.cjs +268 -115
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.cts +96 -29
- package/dist/hooks/index.d.ts +96 -29
- package/dist/hooks/index.js +268 -116
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.cjs +301 -137
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +316 -160
- package/dist/index.d.ts +316 -160
- package/dist/index.js +295 -138
- package/dist/index.js.map +1 -1
- package/dist/next/index.cjs +69 -2
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +36 -5
- package/dist/next/index.d.ts +36 -5
- package/dist/next/index.js +69 -2
- package/dist/next/index.js.map +1 -1
- package/dist/server/index.cjs +70 -2
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +45 -8
- package/dist/server/index.d.ts +45 -8
- package/dist/server/index.js +70 -3
- package/dist/server/index.js.map +1 -1
- package/package.json +5 -1
package/dist/next/index.d.cts
CHANGED
|
@@ -126,8 +126,15 @@ interface EntityDefinition {
|
|
|
126
126
|
* start = end and the record is expired without ever running.
|
|
127
127
|
*/
|
|
128
128
|
retireStartField?: string;
|
|
129
|
+
/**
|
|
130
|
+
* Header naming child records to delete instead of the whole record
|
|
131
|
+
* (`productComponentInstanceIds: 1,2`). Only `item()` DELETE uses it, and only when
|
|
132
|
+
* the caller's `extras` carry this one key (a non-empty list of ids); every other
|
|
133
|
+
* `extras` key is still dropped. Only for `deleteMode: "delete"`.
|
|
134
|
+
*/
|
|
135
|
+
childIdsHeader?: string;
|
|
129
136
|
}
|
|
130
|
-
type EntityKind = "product" | "category" | "store" | "promotion" | "employee" | "consumer";
|
|
137
|
+
type EntityKind = "product" | "category" | "store" | "promotion" | "employee" | "consumer" | "productComponent";
|
|
131
138
|
/**
|
|
132
139
|
* Addressing per entity, following the Shopwave API reference
|
|
133
140
|
* (https://developer.merchantstack.com/api-reference.html) and checked against
|
|
@@ -140,6 +147,27 @@ type EntityKind = "product" | "category" | "store" | "promotion" | "employee" |
|
|
|
140
147
|
* - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
|
|
141
148
|
* changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
|
|
142
149
|
* - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
|
|
150
|
+
* - productComponent: modifier groups, `/product/component` (live, staging, 11 Nov 2026). The
|
|
151
|
+
* API reference lists no read filters; these are what the live API does:
|
|
152
|
+
* - Reads filter by `componentIds` (group ids) or `productIds` (the parent product; option
|
|
153
|
+
* products don't match). `productComponentIds` is ignored on reads: every group comes back.
|
|
154
|
+
* `deleted: false` gives live groups only, `true` deleted groups only, no header both.
|
|
155
|
+
* No match → 204 with an empty body. No order, sort, priority or position field anywhere.
|
|
156
|
+
* - Groups are keyed by id; `products` (option products) by product id; `productInstances`
|
|
157
|
+
* (option rows) by row id. Reads reformat values: price `"100"` → `"100.0"`,
|
|
158
|
+
* vatPercentage `"0.2000"` → `"0.200"`. `tags` and `name` are stored as sent (no trimming;
|
|
159
|
+
* a `tags` array is joined with commas; `tags` null or left out → null).
|
|
160
|
+
* - POST upserts (201, echoed under the ref). For each option product in `products`, its rows
|
|
161
|
+
* are replaced: every row sent is created afresh with a new id (a row `id` is ignored, also
|
|
162
|
+
* on a price change) and rows left out are removed. Option products left out of the save
|
|
163
|
+
* are untouched, and an empty `productInstances` removes nothing. A save without `products`
|
|
164
|
+
* updates only the group's own fields. The echo isn't proof of what was stored: renames of
|
|
165
|
+
* groups created moments earlier by a test run echoed the new name but never stuck (re-read
|
|
166
|
+
* for 90 s), while older groups, and groups created in Shopwave's own admin, renamed at
|
|
167
|
+
* once. Cause not yet known.
|
|
168
|
+
* - DELETE with `productComponentIds` (plural, comma list) → 205, soft delete. Adding
|
|
169
|
+
* `productComponentInstanceIds` deletes just those rows (205); they stay in live reads with
|
|
170
|
+
* `deleteDate` set. The singular `productComponentId` hangs (504 after 60 s).
|
|
143
171
|
*/
|
|
144
172
|
declare const SHOPWAVE_ENTITIES: Readonly<Record<EntityKind, EntityDefinition>>;
|
|
145
173
|
//#endregion
|
|
@@ -185,8 +213,8 @@ declare const UPLOAD_KINDS: Readonly<Record<UploadKind, string>>;
|
|
|
185
213
|
/**
|
|
186
214
|
* The role an image plays, sent to Shopwave as the uploader's `prefix` header.
|
|
187
215
|
* The uploader names the file `<prefix>_<hash>.<ext>`, so the role travels with
|
|
188
|
-
* the file wherever a record stores it (a product's `images`). Live,
|
|
189
|
-
*
|
|
216
|
+
* the file wherever a record stores it (a product's `images`). Live, test
|
|
217
|
+
* merchant, 4 Nov 2026: all four values answered 201.
|
|
190
218
|
* - `product`: a product gallery image;
|
|
191
219
|
* - `product_thumbnail`: the product's lead image, shown in lists;
|
|
192
220
|
* - `variant` / `variant_thumbnail`: a variant's own image and its lead image
|
|
@@ -208,7 +236,7 @@ declare const UPLOAD_PREFIXES: ReadonlyArray<UploadPrefix>;
|
|
|
208
236
|
* on the merchant's products. No parameters. The API reference says
|
|
209
237
|
* "internal SKU tags are excluded", but the live API includes system tags
|
|
210
238
|
* written by imports and syncs (`PID-133`, `VID-137`, `#Hash:…`, `upc-…`;
|
|
211
|
-
* test merchant
|
|
239
|
+
* a test merchant, 29 Oct 2026). Apps that offer the list as suggestions
|
|
212
240
|
* filter those themselves. Tags of deleted products drop out of the list.
|
|
213
241
|
* - productInstanceTags: `GET /product/instance/tags` → `{ tags: string[] }`,
|
|
214
242
|
* the same for price instances (channels such as "Eat In", "Takeout").
|
|
@@ -303,7 +331,10 @@ interface ItemHandlers {
|
|
|
303
331
|
GET: RouteHandler;
|
|
304
332
|
/** Update the record at `[id]` (the id in the URL wins). */
|
|
305
333
|
PUT: RouteHandler;
|
|
306
|
-
/**
|
|
334
|
+
/**
|
|
335
|
+
* Delete the record at `[id]`. Shopwave answers 205 with an empty body. For an entity with
|
|
336
|
+
* `childIdsHeader`, `extras` carrying that key deletes just those child rows instead.
|
|
337
|
+
*/
|
|
307
338
|
DELETE: RouteHandler;
|
|
308
339
|
}
|
|
309
340
|
interface ResourceHandlers {
|
package/dist/next/index.d.ts
CHANGED
|
@@ -126,8 +126,15 @@ interface EntityDefinition {
|
|
|
126
126
|
* start = end and the record is expired without ever running.
|
|
127
127
|
*/
|
|
128
128
|
retireStartField?: string;
|
|
129
|
+
/**
|
|
130
|
+
* Header naming child records to delete instead of the whole record
|
|
131
|
+
* (`productComponentInstanceIds: 1,2`). Only `item()` DELETE uses it, and only when
|
|
132
|
+
* the caller's `extras` carry this one key (a non-empty list of ids); every other
|
|
133
|
+
* `extras` key is still dropped. Only for `deleteMode: "delete"`.
|
|
134
|
+
*/
|
|
135
|
+
childIdsHeader?: string;
|
|
129
136
|
}
|
|
130
|
-
type EntityKind = "product" | "category" | "store" | "promotion" | "employee" | "consumer";
|
|
137
|
+
type EntityKind = "product" | "category" | "store" | "promotion" | "employee" | "consumer" | "productComponent";
|
|
131
138
|
/**
|
|
132
139
|
* Addressing per entity, following the Shopwave API reference
|
|
133
140
|
* (https://developer.merchantstack.com/api-reference.html) and checked against
|
|
@@ -140,6 +147,27 @@ type EntityKind = "product" | "category" | "store" | "promotion" | "employee" |
|
|
|
140
147
|
* - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
|
|
141
148
|
* changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
|
|
142
149
|
* - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
|
|
150
|
+
* - productComponent: modifier groups, `/product/component` (live, staging, 11 Nov 2026). The
|
|
151
|
+
* API reference lists no read filters; these are what the live API does:
|
|
152
|
+
* - Reads filter by `componentIds` (group ids) or `productIds` (the parent product; option
|
|
153
|
+
* products don't match). `productComponentIds` is ignored on reads: every group comes back.
|
|
154
|
+
* `deleted: false` gives live groups only, `true` deleted groups only, no header both.
|
|
155
|
+
* No match → 204 with an empty body. No order, sort, priority or position field anywhere.
|
|
156
|
+
* - Groups are keyed by id; `products` (option products) by product id; `productInstances`
|
|
157
|
+
* (option rows) by row id. Reads reformat values: price `"100"` → `"100.0"`,
|
|
158
|
+
* vatPercentage `"0.2000"` → `"0.200"`. `tags` and `name` are stored as sent (no trimming;
|
|
159
|
+
* a `tags` array is joined with commas; `tags` null or left out → null).
|
|
160
|
+
* - POST upserts (201, echoed under the ref). For each option product in `products`, its rows
|
|
161
|
+
* are replaced: every row sent is created afresh with a new id (a row `id` is ignored, also
|
|
162
|
+
* on a price change) and rows left out are removed. Option products left out of the save
|
|
163
|
+
* are untouched, and an empty `productInstances` removes nothing. A save without `products`
|
|
164
|
+
* updates only the group's own fields. The echo isn't proof of what was stored: renames of
|
|
165
|
+
* groups created moments earlier by a test run echoed the new name but never stuck (re-read
|
|
166
|
+
* for 90 s), while older groups, and groups created in Shopwave's own admin, renamed at
|
|
167
|
+
* once. Cause not yet known.
|
|
168
|
+
* - DELETE with `productComponentIds` (plural, comma list) → 205, soft delete. Adding
|
|
169
|
+
* `productComponentInstanceIds` deletes just those rows (205); they stay in live reads with
|
|
170
|
+
* `deleteDate` set. The singular `productComponentId` hangs (504 after 60 s).
|
|
143
171
|
*/
|
|
144
172
|
declare const SHOPWAVE_ENTITIES: Readonly<Record<EntityKind, EntityDefinition>>;
|
|
145
173
|
//#endregion
|
|
@@ -185,8 +213,8 @@ declare const UPLOAD_KINDS: Readonly<Record<UploadKind, string>>;
|
|
|
185
213
|
/**
|
|
186
214
|
* The role an image plays, sent to Shopwave as the uploader's `prefix` header.
|
|
187
215
|
* The uploader names the file `<prefix>_<hash>.<ext>`, so the role travels with
|
|
188
|
-
* the file wherever a record stores it (a product's `images`). Live,
|
|
189
|
-
*
|
|
216
|
+
* the file wherever a record stores it (a product's `images`). Live, test
|
|
217
|
+
* merchant, 4 Nov 2026: all four values answered 201.
|
|
190
218
|
* - `product`: a product gallery image;
|
|
191
219
|
* - `product_thumbnail`: the product's lead image, shown in lists;
|
|
192
220
|
* - `variant` / `variant_thumbnail`: a variant's own image and its lead image
|
|
@@ -208,7 +236,7 @@ declare const UPLOAD_PREFIXES: ReadonlyArray<UploadPrefix>;
|
|
|
208
236
|
* on the merchant's products. No parameters. The API reference says
|
|
209
237
|
* "internal SKU tags are excluded", but the live API includes system tags
|
|
210
238
|
* written by imports and syncs (`PID-133`, `VID-137`, `#Hash:…`, `upc-…`;
|
|
211
|
-
* test merchant
|
|
239
|
+
* a test merchant, 29 Oct 2026). Apps that offer the list as suggestions
|
|
212
240
|
* filter those themselves. Tags of deleted products drop out of the list.
|
|
213
241
|
* - productInstanceTags: `GET /product/instance/tags` → `{ tags: string[] }`,
|
|
214
242
|
* the same for price instances (channels such as "Eat In", "Takeout").
|
|
@@ -303,7 +331,10 @@ interface ItemHandlers {
|
|
|
303
331
|
GET: RouteHandler;
|
|
304
332
|
/** Update the record at `[id]` (the id in the URL wins). */
|
|
305
333
|
PUT: RouteHandler;
|
|
306
|
-
/**
|
|
334
|
+
/**
|
|
335
|
+
* Delete the record at `[id]`. Shopwave answers 205 with an empty body. For an entity with
|
|
336
|
+
* `childIdsHeader`, `extras` carrying that key deletes just those child rows instead.
|
|
337
|
+
*/
|
|
307
338
|
DELETE: RouteHandler;
|
|
308
339
|
}
|
|
309
340
|
interface ResourceHandlers {
|
package/dist/next/index.js
CHANGED
|
@@ -48,7 +48,7 @@ function tokenFromResponse(response, previous, now = Date.now()) {
|
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
50
|
* Reads a token from session storage. Accepts both the current shape and the
|
|
51
|
-
* legacy raw response that older apps
|
|
51
|
+
* legacy raw response that older apps stored directly in
|
|
52
52
|
* the session, so existing sessions survive an upgrade.
|
|
53
53
|
*/
|
|
54
54
|
function normalizeStoredToken(raw) {
|
|
@@ -330,6 +330,27 @@ const SHOPWAVE_LOOKUPS = Object.freeze({
|
|
|
330
330
|
* - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
|
|
331
331
|
* changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
|
|
332
332
|
* - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
|
|
333
|
+
* - productComponent: modifier groups, `/product/component` (live, staging, 11 Nov 2026). The
|
|
334
|
+
* API reference lists no read filters; these are what the live API does:
|
|
335
|
+
* - Reads filter by `componentIds` (group ids) or `productIds` (the parent product; option
|
|
336
|
+
* products don't match). `productComponentIds` is ignored on reads: every group comes back.
|
|
337
|
+
* `deleted: false` gives live groups only, `true` deleted groups only, no header both.
|
|
338
|
+
* No match → 204 with an empty body. No order, sort, priority or position field anywhere.
|
|
339
|
+
* - Groups are keyed by id; `products` (option products) by product id; `productInstances`
|
|
340
|
+
* (option rows) by row id. Reads reformat values: price `"100"` → `"100.0"`,
|
|
341
|
+
* vatPercentage `"0.2000"` → `"0.200"`. `tags` and `name` are stored as sent (no trimming;
|
|
342
|
+
* a `tags` array is joined with commas; `tags` null or left out → null).
|
|
343
|
+
* - POST upserts (201, echoed under the ref). For each option product in `products`, its rows
|
|
344
|
+
* are replaced: every row sent is created afresh with a new id (a row `id` is ignored, also
|
|
345
|
+
* on a price change) and rows left out are removed. Option products left out of the save
|
|
346
|
+
* are untouched, and an empty `productInstances` removes nothing. A save without `products`
|
|
347
|
+
* updates only the group's own fields. The echo isn't proof of what was stored: renames of
|
|
348
|
+
* groups created moments earlier by a test run echoed the new name but never stuck (re-read
|
|
349
|
+
* for 90 s), while older groups, and groups created in Shopwave's own admin, renamed at
|
|
350
|
+
* once. Cause not yet known.
|
|
351
|
+
* - DELETE with `productComponentIds` (plural, comma list) → 205, soft delete. Adding
|
|
352
|
+
* `productComponentInstanceIds` deletes just those rows (205); they stay in live reads with
|
|
353
|
+
* `deleteDate` set. The singular `productComponentId` hangs (504 after 60 s).
|
|
333
354
|
*/
|
|
334
355
|
const SHOPWAVE_ENTITIES = Object.freeze({
|
|
335
356
|
product: {
|
|
@@ -389,6 +410,16 @@ const SHOPWAVE_ENTITIES = Object.freeze({
|
|
|
389
410
|
idHeader: "consumerId",
|
|
390
411
|
writable: false,
|
|
391
412
|
deleteMode: "none"
|
|
413
|
+
},
|
|
414
|
+
productComponent: {
|
|
415
|
+
route: "product-components",
|
|
416
|
+
collection: "productComponents",
|
|
417
|
+
upstream: "product/component",
|
|
418
|
+
idsHeader: "componentIds",
|
|
419
|
+
idHeader: "productComponentIds",
|
|
420
|
+
writable: true,
|
|
421
|
+
deleteMode: "delete",
|
|
422
|
+
childIdsHeader: "productComponentInstanceIds"
|
|
392
423
|
}
|
|
393
424
|
});
|
|
394
425
|
|
|
@@ -478,7 +509,7 @@ function isPlainObject(value) {
|
|
|
478
509
|
*
|
|
479
510
|
* Accepts:
|
|
480
511
|
* - the SDK ≥ 0.3 shape `{ <collection>: { "0": {...}, "1": {...} } }` (refs kept),
|
|
481
|
-
* - the older
|
|
512
|
+
* - the older app shapes `{ <collection>: { new: {...} } }` / `{ updated: {...} }`,
|
|
482
513
|
* - a bare entity object `{ title: … }`.
|
|
483
514
|
*
|
|
484
515
|
* With `forcedId` (item routes) exactly one entity is allowed and its `id` is
|
|
@@ -562,6 +593,29 @@ async function readUpstream(response) {
|
|
|
562
593
|
};
|
|
563
594
|
}
|
|
564
595
|
}
|
|
596
|
+
/**
|
|
597
|
+
* The child ids a DELETE names in `extras` under `header` (case-insensitive), as one comma
|
|
598
|
+
* list: `undefined` when the key is absent, `null` when `extras` or the value is invalid (not a
|
|
599
|
+
* JSON object, or not a non-empty list of ids). The key may hold an array or a comma string.
|
|
600
|
+
*/
|
|
601
|
+
function readChildIds(raw, header) {
|
|
602
|
+
if (!raw) return void 0;
|
|
603
|
+
let parsed;
|
|
604
|
+
try {
|
|
605
|
+
parsed = JSON.parse(raw);
|
|
606
|
+
} catch {
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
if (!isPlainObject(parsed)) return null;
|
|
610
|
+
const key = Object.keys(parsed).find((k) => k.toLowerCase() === header.toLowerCase());
|
|
611
|
+
if (key === void 0) return void 0;
|
|
612
|
+
const value = parsed[key];
|
|
613
|
+
const list = Array.isArray(value) ? value : typeof value === "string" || typeof value === "number" ? String(value).split(",") : null;
|
|
614
|
+
if (!list) return null;
|
|
615
|
+
const ids = list.map((v) => typeof v === "string" || typeof v === "number" ? String(v).trim() : "");
|
|
616
|
+
if (ids.length === 0 || ids.some((id) => !ID_PATTERN.test(id))) return null;
|
|
617
|
+
return ids.join(",");
|
|
618
|
+
}
|
|
565
619
|
async function routeParam(context, name) {
|
|
566
620
|
const value = (await context?.params)?.[name];
|
|
567
621
|
const id = Array.isArray(value) ? value[0] : value;
|
|
@@ -812,6 +866,19 @@ function createShopwaveApiHandlers(config) {
|
|
|
812
866
|
const id = await routeParam(context, param);
|
|
813
867
|
if (!id) return badId();
|
|
814
868
|
if (def.deleteMode === "none") return notSupported(def, "deletes");
|
|
869
|
+
if (def.deleteMode === "delete" && def.childIdsHeader) {
|
|
870
|
+
const childIds = readChildIds(request.headers.get("extras"), def.childIdsHeader);
|
|
871
|
+
if (childIds === null) return jsonError(400, "invalid_extras", `"${def.childIdsHeader}" must be a non-empty list of ids`);
|
|
872
|
+
if (childIds !== void 0) return forward(request, {
|
|
873
|
+
method: "DELETE",
|
|
874
|
+
path: def.upstream,
|
|
875
|
+
headers: {
|
|
876
|
+
[def.idHeader]: id,
|
|
877
|
+
[def.childIdsHeader]: childIds
|
|
878
|
+
},
|
|
879
|
+
forwardExtras: false
|
|
880
|
+
});
|
|
881
|
+
}
|
|
815
882
|
if (def.deleteMode === "retire") {
|
|
816
883
|
const field = def.retireField ?? "exitDate";
|
|
817
884
|
if (def.retireWithRecord) return retireWithRecord(request, def, id, field);
|