react-shopwave-connect 0.4.6 → 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/dist/index.cjs CHANGED
@@ -211,12 +211,15 @@ async function apiSend(path, method, payload, options = {}) {
211
211
  /**
212
212
  * DELETE helper. Shopwave answers deletes with 205 and an empty body; if a body
213
213
  * does come back and carries API errors, they are thrown. Returns the parsed
214
- * body (`null` when empty).
214
+ * body (`null` when empty). `extras`, when given, travel in the `extras` header
215
+ * (the server forwards only keys its delete route allows).
215
216
  */
216
- async function apiDelete(path, options = {}) {
217
+ async function apiDelete(path, options = {}, extras) {
218
+ const headers = baseHeaders(options);
219
+ if (extras) headers.extras = JSON.stringify(buildExtras(extras));
217
220
  const { status, body } = await send(path, {
218
221
  method: "DELETE",
219
- headers: baseHeaders(options)
222
+ headers
220
223
  }, options);
221
224
  if (getApiErrorMap(body)) throw toShopwaveApiError(status, body);
222
225
  return body;
@@ -487,133 +490,6 @@ function fetchProductInstanceTags(options = {}) {
487
490
  return fetchLookup("productInstanceTags", options);
488
491
  }
489
492
 
490
- //#endregion
491
- //#region src/core/promotion.ts
492
- /** Promotion rule identifiers as stored by the Shopwave API. */
493
- let PromotionRuleSet = /* @__PURE__ */ function(PromotionRuleSet$1) {
494
- PromotionRuleSet$1["Percentage"] = "|%^/";
495
- PromotionRuleSet$1["Combo"] = "&%</";
496
- PromotionRuleSet$1["MealDeal"] = "&=^/";
497
- return PromotionRuleSet$1;
498
- }({});
499
- /**
500
- * Fetches promotions. Returns the flattened `Promotion[]`.
501
- */
502
- async function fetchPromotions(params = {}, options = {}) {
503
- const extras = { deleted: params.deleted ?? false };
504
- if (params.promotionIds) extras.promotionIds = params.promotionIds;
505
- if (params.imminent !== void 0) extras.imminent = params.imminent;
506
- if (params.active !== void 0) extras.active = params.active;
507
- if (params.expired !== void 0) extras.expired = params.expired;
508
- const json = await apiGet("/api/promotions", extras, withToken(options, params.token));
509
- assertNoApiErrors(json, 200);
510
- return Object.values(json?.promotions ?? {});
511
- }
512
-
513
- //#endregion
514
- //#region src/core/store.ts
515
- /**
516
- * Fetches stores. Returns the flattened `Store[]`.
517
- */
518
- async function fetchStores(params = {}, options = {}) {
519
- const extras = { deleted: params.deleted ?? false };
520
- if (params.storeIds) extras.storeIds = params.storeIds;
521
- const json = await apiGet("/api/stores", extras, withToken(options, params.token));
522
- assertNoApiErrors(json, 200);
523
- return Object.values(json?.stores ?? {});
524
- }
525
-
526
- //#endregion
527
- //#region src/core/report.ts
528
- /**
529
- * Runs one or more report queries and returns the `reports` map keyed by query
530
- * name. The query is JSON-encoded into the `extras.query` header, matching the
531
- * original endpoint contract.
532
- */
533
- async function fetchReport(query, options = {}) {
534
- const json = await apiGet("/api/report", { query: JSON.stringify(query) }, options);
535
- assertNoApiErrors(json, 200);
536
- return json?.reports ?? {};
537
- }
538
-
539
- //#endregion
540
- //#region src/core/session.ts
541
- /**
542
- * Reads the current login status from `GET /api/session`.
543
- *
544
- * Works with the new `react-shopwave-connect/next` session route
545
- * (`{ loggedIn }`) and with older routes that returned the raw session
546
- * (`{ token: {...} }`) — in that case only a boolean is surfaced.
547
- */
548
- async function fetchSession(options = {}) {
549
- const doFetch = options.fetch ?? (typeof fetch !== "undefined" ? fetch : void 0);
550
- if (!doFetch) throw new Error("No fetch implementation available. Pass `options.fetch`.");
551
- const response = await doFetch(`${options.baseUrl ?? ""}/api/session`, {
552
- method: "GET",
553
- headers: { Accept: "application/json" },
554
- cache: "no-store",
555
- credentials: "same-origin",
556
- signal: options.signal
557
- });
558
- if (response.status === 401) return { loggedIn: false };
559
- if (!response.ok) throw new Error(`Session request failed (${response.status})`);
560
- const json = await response.json();
561
- if (json && typeof json.loggedIn === "boolean") return {
562
- loggedIn: json.loggedIn,
563
- expiresAt: typeof json.expiresAt === "number" ? json.expiresAt : void 0
564
- };
565
- return { loggedIn: Boolean(json && json.token) };
566
- }
567
- /**
568
- * Path that starts a login and returns to `returnTo` afterwards.
569
- * Navigate the browser to it (`window.location.href = loginPath(...)`).
570
- */
571
- function loginPath(returnTo, authPath = "/auth") {
572
- return returnTo ? `${authPath}?returnTo=${encodeURIComponent(returnTo)}` : authPath;
573
- }
574
- /** Path that logs out of the app and the Shopwave auth server. */
575
- function logoutPath(authPath = "/auth") {
576
- return `${authPath}/logout`;
577
- }
578
- /**
579
- * Ends the current server session (no redirect).
580
- *
581
- * Prefer navigating to {@link logoutPath} when using
582
- * `react-shopwave-connect/next`: it also signs the user out of the auth server.
583
- */
584
- async function logout(options = {}) {
585
- await apiDelete("/api/session?action=logout", options);
586
- }
587
-
588
- //#endregion
589
- //#region src/core/entity.ts
590
- /**
591
- * Low-level, untyped mutations: the caller supplies the route and payload.
592
- *
593
- * Prefer the typed functions in `./entities` (`saveProduct`, `deleteCategory`,
594
- * …): they know each route, build the request envelope, check the response and
595
- * return the saved entity with its id. Keep these for routes that have no typed
596
- * function yet (e.g. `/api/merchant`).
597
- */
598
- /**
599
- * Deletes `/api/{resourcePath}/{entityId}`.
600
- */
601
- async function deleteEntity(resourcePath, entityId, options = {}) {
602
- if (entityId == null || entityId === "") throw new Error("entityId is missing");
603
- await apiDelete(`/api/${resourcePath}/${entityId}`, options);
604
- }
605
- /**
606
- * Sends a create/update mutation. Returns the response's `result` field when it
607
- * has one (older app routes wrapped their answer that way), otherwise the whole
608
- * parsed response body. Throws `ShopwaveApiError` on HTTP or API errors.
609
- */
610
- async function submitEntity({ endpoint, method = "POST", payload }, options = {}) {
611
- const json = await apiSend(endpoint, method, payload, options);
612
- assertNoApiErrors(json, 200);
613
- if (json && typeof json === "object" && "result" in json) return json.result;
614
- return json ?? void 0;
615
- }
616
-
617
493
  //#endregion
618
494
  //#region src/core/imageFit.ts
619
495
  /**
@@ -1109,6 +985,27 @@ function toStoredProductImages(images) {
1109
985
  * - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
1110
986
  * changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
1111
987
  * - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
988
+ * - productComponent: modifier groups, `/product/component` (live, staging, 11 Nov 2026). The
989
+ * API reference lists no read filters; these are what the live API does:
990
+ * - Reads filter by `componentIds` (group ids) or `productIds` (the parent product; option
991
+ * products don't match). `productComponentIds` is ignored on reads: every group comes back.
992
+ * `deleted: false` gives live groups only, `true` deleted groups only, no header both.
993
+ * No match → 204 with an empty body. No order, sort, priority or position field anywhere.
994
+ * - Groups are keyed by id; `products` (option products) by product id; `productInstances`
995
+ * (option rows) by row id. Reads reformat values: price `"100"` → `"100.0"`,
996
+ * vatPercentage `"0.2000"` → `"0.200"`. `tags` and `name` are stored as sent (no trimming;
997
+ * a `tags` array is joined with commas; `tags` null or left out → null).
998
+ * - POST upserts (201, echoed under the ref). For each option product in `products`, its rows
999
+ * are replaced: every row sent is created afresh with a new id (a row `id` is ignored, also
1000
+ * on a price change) and rows left out are removed. Option products left out of the save
1001
+ * are untouched, and an empty `productInstances` removes nothing. A save without `products`
1002
+ * updates only the group's own fields. The echo isn't proof of what was stored: renames of
1003
+ * groups created moments earlier by a test run echoed the new name but never stuck (re-read
1004
+ * for 90 s), while older groups, and groups created in Shopwave's own admin, renamed at
1005
+ * once. Cause not yet known.
1006
+ * - DELETE with `productComponentIds` (plural, comma list) → 205, soft delete. Adding
1007
+ * `productComponentInstanceIds` deletes just those rows (205); they stay in live reads with
1008
+ * `deleteDate` set. The singular `productComponentId` hangs (504 after 60 s).
1112
1009
  */
1113
1010
  const SHOPWAVE_ENTITIES = Object.freeze({
1114
1011
  product: {
@@ -1168,6 +1065,16 @@ const SHOPWAVE_ENTITIES = Object.freeze({
1168
1065
  idHeader: "consumerId",
1169
1066
  writable: false,
1170
1067
  deleteMode: "none"
1068
+ },
1069
+ productComponent: {
1070
+ route: "product-components",
1071
+ collection: "productComponents",
1072
+ upstream: "product/component",
1073
+ idsHeader: "componentIds",
1074
+ idHeader: "productComponentIds",
1075
+ writable: true,
1076
+ deleteMode: "delete",
1077
+ childIdsHeader: "productComponentInstanceIds"
1171
1078
  }
1172
1079
  });
1173
1080
  function definition(kind) {
@@ -1293,6 +1200,237 @@ const deleteEmployee = (id, options) => deleteEntityById("employee", id, options
1293
1200
  const fetchEmployee = (id, params, options) => fetchEntityById("employee", id, params, options);
1294
1201
  const fetchConsumer = (id, params, options) => fetchEntityById("consumer", id, params, options);
1295
1202
 
1203
+ //#endregion
1204
+ //#region src/core/productComponent.ts
1205
+ /** Row fields the server sets; never sent back. */
1206
+ const SERVER_ROW_FIELDS = [
1207
+ "id",
1208
+ "activeDate",
1209
+ "deleteDate",
1210
+ "timestamp"
1211
+ ];
1212
+ const isObject = (v) => !!v && typeof v === "object" && !Array.isArray(v);
1213
+ function listOf(value) {
1214
+ if (!value) return [];
1215
+ if (Array.isArray(value)) return value.map((v) => [null, v]);
1216
+ return Object.entries(value);
1217
+ }
1218
+ /**
1219
+ * Builds the save body for a product component from a group as read (or as a form holds it):
1220
+ * - `products` keyed by product id (a product without `id` takes its key);
1221
+ * - each product's rows keyed `"0"`, `"1"`, …, like every other ref in a save;
1222
+ * - rows that have a `deleteDate` are left out: the API recreates every row it's sent, so
1223
+ * sending one back would bring a deleted option back as a new row;
1224
+ * - server row fields (`id`, `activeDate`, `deleteDate`, `timestamp`) are removed, since the
1225
+ * API gives every row it's sent a new id anyway;
1226
+ * - a `tags` list becomes a comma string.
1227
+ *
1228
+ * The API replaces the rows of every option product in the save and leaves the others alone,
1229
+ * so send all the rows to keep for each product included. To remove an option product
1230
+ * entirely, delete its rows with {@link deleteProductComponentInstances}.
1231
+ */
1232
+ function toProductComponentForSave(input) {
1233
+ const { products,...group } = input;
1234
+ if (products == null) return { ...group };
1235
+ const out = {};
1236
+ for (const [key, product] of listOf(products)) {
1237
+ if (!isObject(product)) continue;
1238
+ const id = product.id ?? key;
1239
+ if (id == null || String(id).trim() === "") continue;
1240
+ const { productInstances,...rest } = product;
1241
+ const rows = {};
1242
+ let index = 0;
1243
+ for (const [, row] of listOf(productInstances)) {
1244
+ if (!isObject(row) || row.deleteDate) continue;
1245
+ const copy = { ...row };
1246
+ for (const field of SERVER_ROW_FIELDS) delete copy[field];
1247
+ if (Array.isArray(copy.tags)) copy.tags = copy.tags.map(String).join(",");
1248
+ rows[String(index++)] = copy;
1249
+ }
1250
+ out[String(id)] = {
1251
+ ...rest,
1252
+ id,
1253
+ productInstances: rows
1254
+ };
1255
+ }
1256
+ return {
1257
+ ...group,
1258
+ products: out
1259
+ };
1260
+ }
1261
+ /**
1262
+ * Reads product components, optionally filtered by parent product and/or group id.
1263
+ * With no filter it returns every group on the merchant. Returns `[]` when nothing matches
1264
+ * (the API answers 204), and when a filter list is empty (no request is made).
1265
+ *
1266
+ * Rows deleted with {@link deleteProductComponentInstances} are still returned, with
1267
+ * `deleteDate` set; skip them when listing options.
1268
+ */
1269
+ async function fetchProductComponents(params = {}, options = {}) {
1270
+ const def = SHOPWAVE_ENTITIES.productComponent;
1271
+ if (params.productIds?.length === 0 || params.productComponentIds?.length === 0) return [];
1272
+ const extras = { deleted: params.deleted ?? false };
1273
+ if (params.productIds) extras.productIds = params.productIds;
1274
+ if (params.productComponentIds) extras[def.idsHeader] = params.productComponentIds;
1275
+ const json = await apiGet(`/api/${def.route}`, extras, options);
1276
+ assertNoApiErrors(json, 200);
1277
+ const map = json?.[def.collection];
1278
+ return isObject(map) ? Object.values(map) : [];
1279
+ }
1280
+ /**
1281
+ * Reads one product component by id, or `null` when there's no such live group.
1282
+ * Note that `{ deleted: true }` returns the group only if it *has* been deleted.
1283
+ */
1284
+ const fetchProductComponent = (id, params, options) => fetchEntityById("productComponent", id, params, options);
1285
+ /**
1286
+ * Creates (no `id`) or updates (with `id`) a product component and returns it with its id.
1287
+ * The body is built with {@link toProductComponentForSave}. Every option row sent is stored
1288
+ * under a new id, so re-read with {@link fetchProductComponent} for the stored rows.
1289
+ */
1290
+ const saveProductComponent = (input, options) => saveEntity("productComponent", toProductComponentForSave(input), options);
1291
+ /** Deletes a product component (soft delete: `deleted: true` reads still return it). */
1292
+ const deleteProductComponent = (id, options) => deleteEntityById("productComponent", id, options);
1293
+ /**
1294
+ * Deletes some option rows of a product component, leaving the group and its other rows.
1295
+ * The rows stay in reads with `deleteDate` set. Throws without calling the API when
1296
+ * `instanceIds` is empty, because a delete without them would delete the whole group.
1297
+ */
1298
+ async function deleteProductComponentInstances(id, instanceIds, options = {}) {
1299
+ const def = SHOPWAVE_ENTITIES.productComponent;
1300
+ const groupId = id == null ? "" : String(id).trim();
1301
+ if (!groupId) throw new Error("productComponent id is missing");
1302
+ const ids = (instanceIds ?? []).map((v) => String(v).trim()).filter(Boolean);
1303
+ if (ids.length === 0) throw new Error("productComponent instance ids are missing");
1304
+ await apiDelete(`/api/${def.route}/${encodeURIComponent(groupId)}`, options, { [def.childIdsHeader]: ids.join(",") });
1305
+ }
1306
+
1307
+ //#endregion
1308
+ //#region src/core/promotion.ts
1309
+ /** Promotion rule identifiers as stored by the Shopwave API. */
1310
+ let PromotionRuleSet = /* @__PURE__ */ function(PromotionRuleSet$1) {
1311
+ PromotionRuleSet$1["Percentage"] = "|%^/";
1312
+ PromotionRuleSet$1["Combo"] = "&%</";
1313
+ PromotionRuleSet$1["MealDeal"] = "&=^/";
1314
+ return PromotionRuleSet$1;
1315
+ }({});
1316
+ /**
1317
+ * Fetches promotions. Returns the flattened `Promotion[]`.
1318
+ */
1319
+ async function fetchPromotions(params = {}, options = {}) {
1320
+ const extras = { deleted: params.deleted ?? false };
1321
+ if (params.promotionIds) extras.promotionIds = params.promotionIds;
1322
+ if (params.imminent !== void 0) extras.imminent = params.imminent;
1323
+ if (params.active !== void 0) extras.active = params.active;
1324
+ if (params.expired !== void 0) extras.expired = params.expired;
1325
+ const json = await apiGet("/api/promotions", extras, withToken(options, params.token));
1326
+ assertNoApiErrors(json, 200);
1327
+ return Object.values(json?.promotions ?? {});
1328
+ }
1329
+
1330
+ //#endregion
1331
+ //#region src/core/store.ts
1332
+ /**
1333
+ * Fetches stores. Returns the flattened `Store[]`.
1334
+ */
1335
+ async function fetchStores(params = {}, options = {}) {
1336
+ const extras = { deleted: params.deleted ?? false };
1337
+ if (params.storeIds) extras.storeIds = params.storeIds;
1338
+ const json = await apiGet("/api/stores", extras, withToken(options, params.token));
1339
+ assertNoApiErrors(json, 200);
1340
+ return Object.values(json?.stores ?? {});
1341
+ }
1342
+
1343
+ //#endregion
1344
+ //#region src/core/report.ts
1345
+ /**
1346
+ * Runs one or more report queries and returns the `reports` map keyed by query
1347
+ * name. The query is JSON-encoded into the `extras.query` header, matching the
1348
+ * original endpoint contract.
1349
+ */
1350
+ async function fetchReport(query, options = {}) {
1351
+ const json = await apiGet("/api/report", { query: JSON.stringify(query) }, options);
1352
+ assertNoApiErrors(json, 200);
1353
+ return json?.reports ?? {};
1354
+ }
1355
+
1356
+ //#endregion
1357
+ //#region src/core/session.ts
1358
+ /**
1359
+ * Reads the current login status from `GET /api/session`.
1360
+ *
1361
+ * Works with the new `react-shopwave-connect/next` session route
1362
+ * (`{ loggedIn }`) and with older routes that returned the raw session
1363
+ * (`{ token: {...} }`) — in that case only a boolean is surfaced.
1364
+ */
1365
+ async function fetchSession(options = {}) {
1366
+ const doFetch = options.fetch ?? (typeof fetch !== "undefined" ? fetch : void 0);
1367
+ if (!doFetch) throw new Error("No fetch implementation available. Pass `options.fetch`.");
1368
+ const response = await doFetch(`${options.baseUrl ?? ""}/api/session`, {
1369
+ method: "GET",
1370
+ headers: { Accept: "application/json" },
1371
+ cache: "no-store",
1372
+ credentials: "same-origin",
1373
+ signal: options.signal
1374
+ });
1375
+ if (response.status === 401) return { loggedIn: false };
1376
+ if (!response.ok) throw new Error(`Session request failed (${response.status})`);
1377
+ const json = await response.json();
1378
+ if (json && typeof json.loggedIn === "boolean") return {
1379
+ loggedIn: json.loggedIn,
1380
+ expiresAt: typeof json.expiresAt === "number" ? json.expiresAt : void 0
1381
+ };
1382
+ return { loggedIn: Boolean(json && json.token) };
1383
+ }
1384
+ /**
1385
+ * Path that starts a login and returns to `returnTo` afterwards.
1386
+ * Navigate the browser to it (`window.location.href = loginPath(...)`).
1387
+ */
1388
+ function loginPath(returnTo, authPath = "/auth") {
1389
+ return returnTo ? `${authPath}?returnTo=${encodeURIComponent(returnTo)}` : authPath;
1390
+ }
1391
+ /** Path that logs out of the app and the Shopwave auth server. */
1392
+ function logoutPath(authPath = "/auth") {
1393
+ return `${authPath}/logout`;
1394
+ }
1395
+ /**
1396
+ * Ends the current server session (no redirect).
1397
+ *
1398
+ * Prefer navigating to {@link logoutPath} when using
1399
+ * `react-shopwave-connect/next`: it also signs the user out of the auth server.
1400
+ */
1401
+ async function logout(options = {}) {
1402
+ await apiDelete("/api/session?action=logout", options);
1403
+ }
1404
+
1405
+ //#endregion
1406
+ //#region src/core/entity.ts
1407
+ /**
1408
+ * Low-level, untyped mutations: the caller supplies the route and payload.
1409
+ *
1410
+ * Prefer the typed functions in `./entities` (`saveProduct`, `deleteCategory`,
1411
+ * …): they know each route, build the request envelope, check the response and
1412
+ * return the saved entity with its id. Keep these for routes that have no typed
1413
+ * function yet (e.g. `/api/merchant`).
1414
+ */
1415
+ /**
1416
+ * Deletes `/api/{resourcePath}/{entityId}`.
1417
+ */
1418
+ async function deleteEntity(resourcePath, entityId, options = {}) {
1419
+ if (entityId == null || entityId === "") throw new Error("entityId is missing");
1420
+ await apiDelete(`/api/${resourcePath}/${entityId}`, options);
1421
+ }
1422
+ /**
1423
+ * Sends a create/update mutation. Returns the response's `result` field when it
1424
+ * has one (older app routes wrapped their answer that way), otherwise the whole
1425
+ * parsed response body. Throws `ShopwaveApiError` on HTTP or API errors.
1426
+ */
1427
+ async function submitEntity({ endpoint, method = "POST", payload }, options = {}) {
1428
+ const json = await apiSend(endpoint, method, payload, options);
1429
+ assertNoApiErrors(json, 200);
1430
+ if (json && typeof json === "object" && "result" in json) return json.result;
1431
+ return json ?? void 0;
1432
+ }
1433
+
1296
1434
  //#endregion
1297
1435
  //#region src/core/basket.ts
1298
1436
  const DEFAULT_BASKET_FILTERS = {
@@ -1869,6 +2007,25 @@ function useProductInstanceTags(options) {
1869
2007
  }), [options?.baseUrl, options?.token], options?.enabled ?? true);
1870
2008
  }
1871
2009
 
2010
+ //#endregion
2011
+ //#region src/hooks/useProductComponent.ts
2012
+ /**
2013
+ * Auto-fetches product components (modifier groups) on mount and whenever the params change:
2014
+ * a product's groups with `{ productIds: [id] }`, one group with `{ productComponentIds: [id] }`,
2015
+ * or every group with no filter. Option rows with a `deleteDate` are included; skip them when
2016
+ * listing options.
2017
+ */
2018
+ function useProductComponent(params = {}, options) {
2019
+ return useQuery((signal) => fetchProductComponents(params, {
2020
+ ...options,
2021
+ signal
2022
+ }), [
2023
+ JSON.stringify(params),
2024
+ options?.baseUrl,
2025
+ options?.token
2026
+ ]);
2027
+ }
2028
+
1872
2029
  //#endregion
1873
2030
  //#region src/hooks/usePromotion.ts
1874
2031
  /**
@@ -2219,6 +2376,8 @@ exports.deleteEmployee = deleteEmployee;
2219
2376
  exports.deleteEntity = deleteEntity;
2220
2377
  exports.deleteEntityById = deleteEntityById;
2221
2378
  exports.deleteProduct = deleteProduct;
2379
+ exports.deleteProductComponent = deleteProductComponent;
2380
+ exports.deleteProductComponentInstances = deleteProductComponentInstances;
2222
2381
  exports.deletePromotion = deletePromotion;
2223
2382
  exports.deleteStore = deleteStore;
2224
2383
  exports.fetchCategories = fetchCategories;
@@ -2230,6 +2389,8 @@ exports.fetchEmployees = fetchEmployees;
2230
2389
  exports.fetchEntityById = fetchEntityById;
2231
2390
  exports.fetchMerchant = fetchMerchant;
2232
2391
  exports.fetchProduct = fetchProduct;
2392
+ exports.fetchProductComponent = fetchProductComponent;
2393
+ exports.fetchProductComponents = fetchProductComponents;
2233
2394
  exports.fetchProductInstanceTags = fetchProductInstanceTags;
2234
2395
  exports.fetchProductTags = fetchProductTags;
2235
2396
  exports.fetchProducts = fetchProducts;
@@ -2272,10 +2433,12 @@ exports.saveEmployee = saveEmployee;
2272
2433
  exports.saveEntities = saveEntities;
2273
2434
  exports.saveEntity = saveEntity;
2274
2435
  exports.saveProduct = saveProduct;
2436
+ exports.saveProductComponent = saveProductComponent;
2275
2437
  exports.savePromotion = savePromotion;
2276
2438
  exports.saveStore = saveStore;
2277
2439
  exports.setMerchantImage = setMerchantImage;
2278
2440
  exports.submitEntity = submitEntity;
2441
+ exports.toProductComponentForSave = toProductComponentForSave;
2279
2442
  exports.toProductInstancesForSave = toProductInstancesForSave;
2280
2443
  exports.toShopwaveApiError = toShopwaveApiError;
2281
2444
  exports.toStoredImageIds = toStoredImageIds;
@@ -2291,6 +2454,7 @@ exports.useEmployee = useEmployee;
2291
2454
  exports.useLogout = useLogout;
2292
2455
  exports.useMerchant = useMerchant;
2293
2456
  exports.useProduct = useProduct;
2457
+ exports.useProductComponent = useProductComponent;
2294
2458
  exports.useProductInstanceTags = useProductInstanceTags;
2295
2459
  exports.useProductTags = useProductTags;
2296
2460
  exports.usePromotion = usePromotion;