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.
@@ -37,7 +37,7 @@ declare const SHOPWAVE_TOKEN_EXPIRED_ERROR_ID = 908;
37
37
  declare function tokenFromResponse(response: ShopwaveTokenResponse, previous?: ShopwaveToken, now?: number): ShopwaveToken | null;
38
38
  /**
39
39
  * Reads a token from session storage. Accepts both the current shape and the
40
- * legacy raw response that older apps (e.g. AdminUI ≤ 0.1) stored directly in
40
+ * legacy raw response that older apps stored directly in
41
41
  * the session, so existing sessions survive an upgrade.
42
42
  */
43
43
  declare function normalizeStoredToken(raw: unknown): ShopwaveToken | null;
@@ -215,8 +215,15 @@ interface EntityDefinition {
215
215
  * start = end and the record is expired without ever running.
216
216
  */
217
217
  retireStartField?: string;
218
+ /**
219
+ * Header naming child records to delete instead of the whole record
220
+ * (`productComponentInstanceIds: 1,2`). Only `item()` DELETE uses it, and only when
221
+ * the caller's `extras` carry this one key (a non-empty list of ids); every other
222
+ * `extras` key is still dropped. Only for `deleteMode: "delete"`.
223
+ */
224
+ childIdsHeader?: string;
218
225
  }
219
- type EntityKind = "product" | "category" | "store" | "promotion" | "employee" | "consumer";
226
+ type EntityKind = "product" | "category" | "store" | "promotion" | "employee" | "consumer" | "productComponent";
220
227
  /** Entities that can be created/updated through the API. */
221
228
  type WritableEntityKind = Exclude<EntityKind, "consumer">;
222
229
  /** Entities that can be deleted (or retired) through the API. */
@@ -233,6 +240,27 @@ type DeletableEntityKind = Exclude<EntityKind, "consumer">;
233
240
  * - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
234
241
  * changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
235
242
  * - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
243
+ * - productComponent: modifier groups, `/product/component` (live, staging, 11 Nov 2026). The
244
+ * API reference lists no read filters; these are what the live API does:
245
+ * - Reads filter by `componentIds` (group ids) or `productIds` (the parent product; option
246
+ * products don't match). `productComponentIds` is ignored on reads: every group comes back.
247
+ * `deleted: false` gives live groups only, `true` deleted groups only, no header both.
248
+ * No match → 204 with an empty body. No order, sort, priority or position field anywhere.
249
+ * - Groups are keyed by id; `products` (option products) by product id; `productInstances`
250
+ * (option rows) by row id. Reads reformat values: price `"100"` → `"100.0"`,
251
+ * vatPercentage `"0.2000"` → `"0.200"`. `tags` and `name` are stored as sent (no trimming;
252
+ * a `tags` array is joined with commas; `tags` null or left out → null).
253
+ * - POST upserts (201, echoed under the ref). For each option product in `products`, its rows
254
+ * are replaced: every row sent is created afresh with a new id (a row `id` is ignored, also
255
+ * on a price change) and rows left out are removed. Option products left out of the save
256
+ * are untouched, and an empty `productInstances` removes nothing. A save without `products`
257
+ * updates only the group's own fields. The echo isn't proof of what was stored: renames of
258
+ * groups created moments earlier by a test run echoed the new name but never stuck (re-read
259
+ * for 90 s), while older groups, and groups created in Shopwave's own admin, renamed at
260
+ * once. Cause not yet known.
261
+ * - DELETE with `productComponentIds` (plural, comma list) → 205, soft delete. Adding
262
+ * `productComponentInstanceIds` deletes just those rows (205); they stay in live reads with
263
+ * `deleteDate` set. The singular `productComponentId` hangs (504 after 60 s).
236
264
  */
237
265
  declare const SHOPWAVE_ENTITIES: Readonly<Record<EntityKind, EntityDefinition>>;
238
266
  //#endregion
@@ -278,8 +306,8 @@ declare const UPLOAD_KINDS: Readonly<Record<UploadKind, string>>;
278
306
  /**
279
307
  * The role an image plays, sent to Shopwave as the uploader's `prefix` header.
280
308
  * The uploader names the file `<prefix>_<hash>.<ext>`, so the role travels with
281
- * the file wherever a record stores it (a product's `images`). Live, merchant
282
- * 5644, 4 Nov 2026: all four values answered 201.
309
+ * the file wherever a record stores it (a product's `images`). Live, test
310
+ * merchant, 4 Nov 2026: all four values answered 201.
283
311
  * - `product`: a product gallery image;
284
312
  * - `product_thumbnail`: the product's lead image, shown in lists;
285
313
  * - `variant` / `variant_thumbnail`: a variant's own image and its lead image
@@ -318,7 +346,7 @@ declare const SHOPWAVE_UPLOAD: Readonly<{
318
346
  * on the merchant's products. No parameters. The API reference says
319
347
  * "internal SKU tags are excluded", but the live API includes system tags
320
348
  * written by imports and syncs (`PID-133`, `VID-137`, `#Hash:…`, `upc-…`;
321
- * test merchant 5644, 29 Oct 2026). Apps that offer the list as suggestions
349
+ * a test merchant, 29 Oct 2026). Apps that offer the list as suggestions
322
350
  * filter those themselves. Tags of deleted products drop out of the list.
323
351
  * - productInstanceTags: `GET /product/instance/tags` → `{ tags: string[] }`,
324
352
  * the same for price instances (channels such as "Eat In", "Takeout").
@@ -364,7 +392,7 @@ declare class RequestBodyError extends Error {}
364
392
  *
365
393
  * Accepts:
366
394
  * - the SDK ≥ 0.3 shape `{ <collection>: { "0": {...}, "1": {...} } }` (refs kept),
367
- * - the older AdminUI shapes `{ <collection>: { new: {...} } }` / `{ updated: {...} }`,
395
+ * - the older app shapes `{ <collection>: { new: {...} } }` / `{ updated: {...} }`,
368
396
  * - a bare entity object `{ title: … }`.
369
397
  *
370
398
  * With `forcedId` (item routes) exactly one entity is allowed and its `id` is
@@ -438,7 +466,10 @@ interface ItemHandlers {
438
466
  GET: RouteHandler;
439
467
  /** Update the record at `[id]` (the id in the URL wins). */
440
468
  PUT: RouteHandler;
441
- /** Delete the record at `[id]`. Shopwave answers 205 with an empty body. */
469
+ /**
470
+ * Delete the record at `[id]`. Shopwave answers 205 with an empty body. For an entity with
471
+ * `childIdsHeader`, `extras` carrying that key deletes just those child rows instead.
472
+ */
442
473
  DELETE: RouteHandler;
443
474
  }
444
475
  interface ResourceHandlers {
@@ -502,7 +533,13 @@ interface ShopwaveApiHandlers {
502
533
  }
503
534
  /** `YYYY-MM-DD HH:mm:ss` in UTC, the datetime format Shopwave writes use (e.g. promotions). */
504
535
  declare function shopwaveDateTime(date?: Date): string;
536
+ /**
537
+ * The child ids a DELETE names in `extras` under `header` (case-insensitive), as one comma
538
+ * list: `undefined` when the key is absent, `null` when `extras` or the value is invalid (not a
539
+ * JSON object, or not a non-empty list of ids). The key may hold an array or a comma string.
540
+ */
541
+ declare function readChildIds(raw: string | null, header: string): string | undefined | null;
505
542
  declare function createShopwaveApiHandlers(config: ShopwaveApiConfig): ShopwaveApiHandlers;
506
543
  //#endregion
507
- export { BLOCKED_EXTRAS, type CollectionHandlers, type DeletableEntityKind, type EntityDefinition, type EntityKind, type ForwardInit, type ItemHandlers, type LookupDefinition, type LookupKind, RequestBodyError, type ResourceDefinition, type ResourceHandlers, type ResourceKind, type RouteContext, type RouteHandler, SHOPWAVE_ENTITIES, SHOPWAVE_LOOKUPS, SHOPWAVE_RESOURCES, SHOPWAVE_TOKEN_EXPIRED_ERROR_ID, SHOPWAVE_UPLOAD, type ShopwaveApiConfig, type ShopwaveApiHandlers, ShopwaveAuthError, type ShopwaveAuthErrorCode, type ShopwaveOAuthClient, type ShopwaveOAuthConfig, type ShopwaveToken, type ShopwaveTokenResponse, UPLOAD_KINDS, UPLOAD_PREFIXES, type UploadHandlers, type UploadKind, type UploadOptions, type UploadPrefix, type WritableEntityKind, assertOAuthConfig, authorizationHeader, createShopwaveApiHandlers, createShopwaveOAuth, createState, isExpiredTokenResponse, isTokenExpired, normalizeStoredToken, normalizeWriteBody, parseExtras, readRequestToken, safeEqual, sanitizeReturnTo, shopwaveDateTime, tokenFromResponse };
544
+ export { BLOCKED_EXTRAS, type CollectionHandlers, type DeletableEntityKind, type EntityDefinition, type EntityKind, type ForwardInit, type ItemHandlers, type LookupDefinition, type LookupKind, RequestBodyError, type ResourceDefinition, type ResourceHandlers, type ResourceKind, type RouteContext, type RouteHandler, SHOPWAVE_ENTITIES, SHOPWAVE_LOOKUPS, SHOPWAVE_RESOURCES, SHOPWAVE_TOKEN_EXPIRED_ERROR_ID, SHOPWAVE_UPLOAD, type ShopwaveApiConfig, type ShopwaveApiHandlers, ShopwaveAuthError, type ShopwaveAuthErrorCode, type ShopwaveOAuthClient, type ShopwaveOAuthConfig, type ShopwaveToken, type ShopwaveTokenResponse, UPLOAD_KINDS, UPLOAD_PREFIXES, type UploadHandlers, type UploadKind, type UploadOptions, type UploadPrefix, type WritableEntityKind, assertOAuthConfig, authorizationHeader, createShopwaveApiHandlers, createShopwaveOAuth, createState, isExpiredTokenResponse, isTokenExpired, normalizeStoredToken, normalizeWriteBody, parseExtras, readChildIds, readRequestToken, safeEqual, sanitizeReturnTo, shopwaveDateTime, tokenFromResponse };
508
545
  //# sourceMappingURL=index.d.cts.map
@@ -37,7 +37,7 @@ declare const SHOPWAVE_TOKEN_EXPIRED_ERROR_ID = 908;
37
37
  declare function tokenFromResponse(response: ShopwaveTokenResponse, previous?: ShopwaveToken, now?: number): ShopwaveToken | null;
38
38
  /**
39
39
  * Reads a token from session storage. Accepts both the current shape and the
40
- * legacy raw response that older apps (e.g. AdminUI ≤ 0.1) stored directly in
40
+ * legacy raw response that older apps stored directly in
41
41
  * the session, so existing sessions survive an upgrade.
42
42
  */
43
43
  declare function normalizeStoredToken(raw: unknown): ShopwaveToken | null;
@@ -215,8 +215,15 @@ interface EntityDefinition {
215
215
  * start = end and the record is expired without ever running.
216
216
  */
217
217
  retireStartField?: string;
218
+ /**
219
+ * Header naming child records to delete instead of the whole record
220
+ * (`productComponentInstanceIds: 1,2`). Only `item()` DELETE uses it, and only when
221
+ * the caller's `extras` carry this one key (a non-empty list of ids); every other
222
+ * `extras` key is still dropped. Only for `deleteMode: "delete"`.
223
+ */
224
+ childIdsHeader?: string;
218
225
  }
219
- type EntityKind = "product" | "category" | "store" | "promotion" | "employee" | "consumer";
226
+ type EntityKind = "product" | "category" | "store" | "promotion" | "employee" | "consumer" | "productComponent";
220
227
  /** Entities that can be created/updated through the API. */
221
228
  type WritableEntityKind = Exclude<EntityKind, "consumer">;
222
229
  /** Entities that can be deleted (or retired) through the API. */
@@ -233,6 +240,27 @@ type DeletableEntityKind = Exclude<EntityKind, "consumer">;
233
240
  * - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
234
241
  * changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
235
242
  * - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
243
+ * - productComponent: modifier groups, `/product/component` (live, staging, 11 Nov 2026). The
244
+ * API reference lists no read filters; these are what the live API does:
245
+ * - Reads filter by `componentIds` (group ids) or `productIds` (the parent product; option
246
+ * products don't match). `productComponentIds` is ignored on reads: every group comes back.
247
+ * `deleted: false` gives live groups only, `true` deleted groups only, no header both.
248
+ * No match → 204 with an empty body. No order, sort, priority or position field anywhere.
249
+ * - Groups are keyed by id; `products` (option products) by product id; `productInstances`
250
+ * (option rows) by row id. Reads reformat values: price `"100"` → `"100.0"`,
251
+ * vatPercentage `"0.2000"` → `"0.200"`. `tags` and `name` are stored as sent (no trimming;
252
+ * a `tags` array is joined with commas; `tags` null or left out → null).
253
+ * - POST upserts (201, echoed under the ref). For each option product in `products`, its rows
254
+ * are replaced: every row sent is created afresh with a new id (a row `id` is ignored, also
255
+ * on a price change) and rows left out are removed. Option products left out of the save
256
+ * are untouched, and an empty `productInstances` removes nothing. A save without `products`
257
+ * updates only the group's own fields. The echo isn't proof of what was stored: renames of
258
+ * groups created moments earlier by a test run echoed the new name but never stuck (re-read
259
+ * for 90 s), while older groups, and groups created in Shopwave's own admin, renamed at
260
+ * once. Cause not yet known.
261
+ * - DELETE with `productComponentIds` (plural, comma list) → 205, soft delete. Adding
262
+ * `productComponentInstanceIds` deletes just those rows (205); they stay in live reads with
263
+ * `deleteDate` set. The singular `productComponentId` hangs (504 after 60 s).
236
264
  */
237
265
  declare const SHOPWAVE_ENTITIES: Readonly<Record<EntityKind, EntityDefinition>>;
238
266
  //#endregion
@@ -278,8 +306,8 @@ declare const UPLOAD_KINDS: Readonly<Record<UploadKind, string>>;
278
306
  /**
279
307
  * The role an image plays, sent to Shopwave as the uploader's `prefix` header.
280
308
  * The uploader names the file `<prefix>_<hash>.<ext>`, so the role travels with
281
- * the file wherever a record stores it (a product's `images`). Live, merchant
282
- * 5644, 4 Nov 2026: all four values answered 201.
309
+ * the file wherever a record stores it (a product's `images`). Live, test
310
+ * merchant, 4 Nov 2026: all four values answered 201.
283
311
  * - `product`: a product gallery image;
284
312
  * - `product_thumbnail`: the product's lead image, shown in lists;
285
313
  * - `variant` / `variant_thumbnail`: a variant's own image and its lead image
@@ -318,7 +346,7 @@ declare const SHOPWAVE_UPLOAD: Readonly<{
318
346
  * on the merchant's products. No parameters. The API reference says
319
347
  * "internal SKU tags are excluded", but the live API includes system tags
320
348
  * written by imports and syncs (`PID-133`, `VID-137`, `#Hash:…`, `upc-…`;
321
- * test merchant 5644, 29 Oct 2026). Apps that offer the list as suggestions
349
+ * a test merchant, 29 Oct 2026). Apps that offer the list as suggestions
322
350
  * filter those themselves. Tags of deleted products drop out of the list.
323
351
  * - productInstanceTags: `GET /product/instance/tags` → `{ tags: string[] }`,
324
352
  * the same for price instances (channels such as "Eat In", "Takeout").
@@ -364,7 +392,7 @@ declare class RequestBodyError extends Error {}
364
392
  *
365
393
  * Accepts:
366
394
  * - the SDK ≥ 0.3 shape `{ <collection>: { "0": {...}, "1": {...} } }` (refs kept),
367
- * - the older AdminUI shapes `{ <collection>: { new: {...} } }` / `{ updated: {...} }`,
395
+ * - the older app shapes `{ <collection>: { new: {...} } }` / `{ updated: {...} }`,
368
396
  * - a bare entity object `{ title: … }`.
369
397
  *
370
398
  * With `forcedId` (item routes) exactly one entity is allowed and its `id` is
@@ -438,7 +466,10 @@ interface ItemHandlers {
438
466
  GET: RouteHandler;
439
467
  /** Update the record at `[id]` (the id in the URL wins). */
440
468
  PUT: RouteHandler;
441
- /** Delete the record at `[id]`. Shopwave answers 205 with an empty body. */
469
+ /**
470
+ * Delete the record at `[id]`. Shopwave answers 205 with an empty body. For an entity with
471
+ * `childIdsHeader`, `extras` carrying that key deletes just those child rows instead.
472
+ */
442
473
  DELETE: RouteHandler;
443
474
  }
444
475
  interface ResourceHandlers {
@@ -502,7 +533,13 @@ interface ShopwaveApiHandlers {
502
533
  }
503
534
  /** `YYYY-MM-DD HH:mm:ss` in UTC, the datetime format Shopwave writes use (e.g. promotions). */
504
535
  declare function shopwaveDateTime(date?: Date): string;
536
+ /**
537
+ * The child ids a DELETE names in `extras` under `header` (case-insensitive), as one comma
538
+ * list: `undefined` when the key is absent, `null` when `extras` or the value is invalid (not a
539
+ * JSON object, or not a non-empty list of ids). The key may hold an array or a comma string.
540
+ */
541
+ declare function readChildIds(raw: string | null, header: string): string | undefined | null;
505
542
  declare function createShopwaveApiHandlers(config: ShopwaveApiConfig): ShopwaveApiHandlers;
506
543
  //#endregion
507
- export { BLOCKED_EXTRAS, type CollectionHandlers, type DeletableEntityKind, type EntityDefinition, type EntityKind, type ForwardInit, type ItemHandlers, type LookupDefinition, type LookupKind, RequestBodyError, type ResourceDefinition, type ResourceHandlers, type ResourceKind, type RouteContext, type RouteHandler, SHOPWAVE_ENTITIES, SHOPWAVE_LOOKUPS, SHOPWAVE_RESOURCES, SHOPWAVE_TOKEN_EXPIRED_ERROR_ID, SHOPWAVE_UPLOAD, type ShopwaveApiConfig, type ShopwaveApiHandlers, ShopwaveAuthError, type ShopwaveAuthErrorCode, type ShopwaveOAuthClient, type ShopwaveOAuthConfig, type ShopwaveToken, type ShopwaveTokenResponse, UPLOAD_KINDS, UPLOAD_PREFIXES, type UploadHandlers, type UploadKind, type UploadOptions, type UploadPrefix, type WritableEntityKind, assertOAuthConfig, authorizationHeader, createShopwaveApiHandlers, createShopwaveOAuth, createState, isExpiredTokenResponse, isTokenExpired, normalizeStoredToken, normalizeWriteBody, parseExtras, readRequestToken, safeEqual, sanitizeReturnTo, shopwaveDateTime, tokenFromResponse };
544
+ export { BLOCKED_EXTRAS, type CollectionHandlers, type DeletableEntityKind, type EntityDefinition, type EntityKind, type ForwardInit, type ItemHandlers, type LookupDefinition, type LookupKind, RequestBodyError, type ResourceDefinition, type ResourceHandlers, type ResourceKind, type RouteContext, type RouteHandler, SHOPWAVE_ENTITIES, SHOPWAVE_LOOKUPS, SHOPWAVE_RESOURCES, SHOPWAVE_TOKEN_EXPIRED_ERROR_ID, SHOPWAVE_UPLOAD, type ShopwaveApiConfig, type ShopwaveApiHandlers, ShopwaveAuthError, type ShopwaveAuthErrorCode, type ShopwaveOAuthClient, type ShopwaveOAuthConfig, type ShopwaveToken, type ShopwaveTokenResponse, UPLOAD_KINDS, UPLOAD_PREFIXES, type UploadHandlers, type UploadKind, type UploadOptions, type UploadPrefix, type WritableEntityKind, assertOAuthConfig, authorizationHeader, createShopwaveApiHandlers, createShopwaveOAuth, createState, isExpiredTokenResponse, isTokenExpired, normalizeStoredToken, normalizeWriteBody, parseExtras, readChildIds, readRequestToken, safeEqual, sanitizeReturnTo, shopwaveDateTime, tokenFromResponse };
508
545
  //# sourceMappingURL=index.d.ts.map
@@ -44,7 +44,7 @@ function tokenFromResponse(response, previous, now = Date.now()) {
44
44
  }
45
45
  /**
46
46
  * Reads a token from session storage. Accepts both the current shape and the
47
- * legacy raw response that older apps (e.g. AdminUI ≤ 0.1) stored directly in
47
+ * legacy raw response that older apps stored directly in
48
48
  * the session, so existing sessions survive an upgrade.
49
49
  */
50
50
  function normalizeStoredToken(raw) {
@@ -326,6 +326,27 @@ const SHOPWAVE_LOOKUPS = Object.freeze({
326
326
  * - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
327
327
  * changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
328
328
  * - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
329
+ * - productComponent: modifier groups, `/product/component` (live, staging, 11 Nov 2026). The
330
+ * API reference lists no read filters; these are what the live API does:
331
+ * - Reads filter by `componentIds` (group ids) or `productIds` (the parent product; option
332
+ * products don't match). `productComponentIds` is ignored on reads: every group comes back.
333
+ * `deleted: false` gives live groups only, `true` deleted groups only, no header both.
334
+ * No match → 204 with an empty body. No order, sort, priority or position field anywhere.
335
+ * - Groups are keyed by id; `products` (option products) by product id; `productInstances`
336
+ * (option rows) by row id. Reads reformat values: price `"100"` → `"100.0"`,
337
+ * vatPercentage `"0.2000"` → `"0.200"`. `tags` and `name` are stored as sent (no trimming;
338
+ * a `tags` array is joined with commas; `tags` null or left out → null).
339
+ * - POST upserts (201, echoed under the ref). For each option product in `products`, its rows
340
+ * are replaced: every row sent is created afresh with a new id (a row `id` is ignored, also
341
+ * on a price change) and rows left out are removed. Option products left out of the save
342
+ * are untouched, and an empty `productInstances` removes nothing. A save without `products`
343
+ * updates only the group's own fields. The echo isn't proof of what was stored: renames of
344
+ * groups created moments earlier by a test run echoed the new name but never stuck (re-read
345
+ * for 90 s), while older groups, and groups created in Shopwave's own admin, renamed at
346
+ * once. Cause not yet known.
347
+ * - DELETE with `productComponentIds` (plural, comma list) → 205, soft delete. Adding
348
+ * `productComponentInstanceIds` deletes just those rows (205); they stay in live reads with
349
+ * `deleteDate` set. The singular `productComponentId` hangs (504 after 60 s).
329
350
  */
330
351
  const SHOPWAVE_ENTITIES = Object.freeze({
331
352
  product: {
@@ -385,6 +406,16 @@ const SHOPWAVE_ENTITIES = Object.freeze({
385
406
  idHeader: "consumerId",
386
407
  writable: false,
387
408
  deleteMode: "none"
409
+ },
410
+ productComponent: {
411
+ route: "product-components",
412
+ collection: "productComponents",
413
+ upstream: "product/component",
414
+ idsHeader: "componentIds",
415
+ idHeader: "productComponentIds",
416
+ writable: true,
417
+ deleteMode: "delete",
418
+ childIdsHeader: "productComponentInstanceIds"
388
419
  }
389
420
  });
390
421
 
@@ -474,7 +505,7 @@ function isPlainObject(value) {
474
505
  *
475
506
  * Accepts:
476
507
  * - the SDK ≥ 0.3 shape `{ <collection>: { "0": {...}, "1": {...} } }` (refs kept),
477
- * - the older AdminUI shapes `{ <collection>: { new: {...} } }` / `{ updated: {...} }`,
508
+ * - the older app shapes `{ <collection>: { new: {...} } }` / `{ updated: {...} }`,
478
509
  * - a bare entity object `{ title: … }`.
479
510
  *
480
511
  * With `forcedId` (item routes) exactly one entity is allowed and its `id` is
@@ -558,6 +589,29 @@ async function readUpstream(response) {
558
589
  };
559
590
  }
560
591
  }
592
+ /**
593
+ * The child ids a DELETE names in `extras` under `header` (case-insensitive), as one comma
594
+ * list: `undefined` when the key is absent, `null` when `extras` or the value is invalid (not a
595
+ * JSON object, or not a non-empty list of ids). The key may hold an array or a comma string.
596
+ */
597
+ function readChildIds(raw, header) {
598
+ if (!raw) return void 0;
599
+ let parsed;
600
+ try {
601
+ parsed = JSON.parse(raw);
602
+ } catch {
603
+ return null;
604
+ }
605
+ if (!isPlainObject(parsed)) return null;
606
+ const key = Object.keys(parsed).find((k) => k.toLowerCase() === header.toLowerCase());
607
+ if (key === void 0) return void 0;
608
+ const value = parsed[key];
609
+ const list = Array.isArray(value) ? value : typeof value === "string" || typeof value === "number" ? String(value).split(",") : null;
610
+ if (!list) return null;
611
+ const ids = list.map((v) => typeof v === "string" || typeof v === "number" ? String(v).trim() : "");
612
+ if (ids.length === 0 || ids.some((id) => !ID_PATTERN.test(id))) return null;
613
+ return ids.join(",");
614
+ }
561
615
  async function routeParam(context, name) {
562
616
  const value = (await context?.params)?.[name];
563
617
  const id = Array.isArray(value) ? value[0] : value;
@@ -808,6 +862,19 @@ function createShopwaveApiHandlers(config) {
808
862
  const id = await routeParam(context, param);
809
863
  if (!id) return badId();
810
864
  if (def.deleteMode === "none") return notSupported(def, "deletes");
865
+ if (def.deleteMode === "delete" && def.childIdsHeader) {
866
+ const childIds = readChildIds(request.headers.get("extras"), def.childIdsHeader);
867
+ if (childIds === null) return jsonError(400, "invalid_extras", `"${def.childIdsHeader}" must be a non-empty list of ids`);
868
+ if (childIds !== void 0) return forward(request, {
869
+ method: "DELETE",
870
+ path: def.upstream,
871
+ headers: {
872
+ [def.idHeader]: id,
873
+ [def.childIdsHeader]: childIds
874
+ },
875
+ forwardExtras: false
876
+ });
877
+ }
811
878
  if (def.deleteMode === "retire") {
812
879
  const field = def.retireField ?? "exitDate";
813
880
  if (def.retireWithRecord) return retireWithRecord(request, def, id, field);
@@ -857,5 +924,5 @@ function createShopwaveApiHandlers(config) {
857
924
  }
858
925
 
859
926
  //#endregion
860
- export { BLOCKED_EXTRAS, RequestBodyError, SHOPWAVE_ENTITIES, SHOPWAVE_LOOKUPS, SHOPWAVE_RESOURCES, SHOPWAVE_TOKEN_EXPIRED_ERROR_ID, SHOPWAVE_UPLOAD, ShopwaveAuthError, UPLOAD_KINDS, UPLOAD_PREFIXES, assertOAuthConfig, authorizationHeader, createShopwaveApiHandlers, createShopwaveOAuth, createState, isExpiredTokenResponse, isTokenExpired, normalizeStoredToken, normalizeWriteBody, parseExtras, readRequestToken, safeEqual, sanitizeReturnTo, shopwaveDateTime, tokenFromResponse };
927
+ export { BLOCKED_EXTRAS, RequestBodyError, SHOPWAVE_ENTITIES, SHOPWAVE_LOOKUPS, SHOPWAVE_RESOURCES, SHOPWAVE_TOKEN_EXPIRED_ERROR_ID, SHOPWAVE_UPLOAD, ShopwaveAuthError, UPLOAD_KINDS, UPLOAD_PREFIXES, assertOAuthConfig, authorizationHeader, createShopwaveApiHandlers, createShopwaveOAuth, createState, isExpiredTokenResponse, isTokenExpired, normalizeStoredToken, normalizeWriteBody, parseExtras, readChildIds, readRequestToken, safeEqual, sanitizeReturnTo, shopwaveDateTime, tokenFromResponse };
861
928
  //# sourceMappingURL=index.js.map