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.
@@ -176,8 +176,9 @@ declare class ShopwaveAuthError extends Error {
176
176
  //#region src/core/entities.d.ts
177
177
  /**
178
178
  * How each Shopwave entity is addressed, shared by the client functions below
179
- * and the server route handlers in `react-shopwave-connect/next`, so the two
180
- * halves of the contract can't drift apart.
179
+ * and the server route handlers in `react-shopwave-connect/server` (wrapped with
180
+ * the session by `react-shopwave-connect/next`), so the two halves of the
181
+ * contract can't drift apart.
181
182
  */
182
183
  interface EntityDefinition {
183
184
  /** App route segment: `/api/<route>` and `/api/<route>/<id>`. */
@@ -195,13 +196,25 @@ interface EntityDefinition {
195
196
  /**
196
197
  * How a delete is done upstream:
197
198
  * - `"delete"`: `DELETE {apiUrl}/<upstream>` with the `idHeader` (205, empty body)
198
- * - `"retire"`: the API has no DELETE; the record is retired by POSTing
199
- * `{ id, [retireField]: <now> }` (employees: `exitDate`, promotions: `endDate`)
199
+ * - `"retire"`: the API has no DELETE; the record is retired by setting
200
+ * `retireField` to now (employees: `exitDate`, promotions: `endDate`)
200
201
  * - `"none"`: can't be deleted
201
202
  */
202
203
  deleteMode: "delete" | "retire" | "none";
203
204
  /** Date field set to "now" when `deleteMode` is `"retire"`. */
204
205
  retireField?: string;
206
+ /**
207
+ * Retire by reading the record and POSTing all of it back with the date(s)
208
+ * changed, instead of a partial `{ id, [retireField]: <now> }`. Needed where
209
+ * the API rejects a partial save (promotions: error 899, live 14 Oct 2026).
210
+ */
211
+ retireWithRecord?: boolean;
212
+ /**
213
+ * Start-date field of a record retired with `retireWithRecord`. If the record
214
+ * hasn't started yet (start after now), the start moves to now as well, so
215
+ * start = end and the record is expired without ever running.
216
+ */
217
+ retireStartField?: string;
205
218
  }
206
219
  type EntityKind = "product" | "category" | "store" | "promotion" | "employee" | "consumer";
207
220
  /** Entities that can be created/updated through the API. */
@@ -213,7 +226,10 @@ type DeletableEntityKind = Exclude<EntityKind, "consumer">;
213
226
  * (https://developer.merchantstack.com/api-reference.html) and checked against
214
227
  * the live API where noted:
215
228
  * - category/product/store: GET filtered by `<x>Ids`, POST upsert, DELETE with `<x>Id` → 205.
216
- * - promotion: no DELETE; "deleting" a promotion ends it by setting `endDate` to now.
229
+ * - promotion: no DELETE; promotions are never deleted, only expired. Expiring
230
+ * reads the promotion and saves the whole record back with `endDate` = now (and
231
+ * `startDate` = now too if it hadn't started); the live API rejects a partial
232
+ * `{ id, endDate }` save with error 899. Dates are UTC `YYYY-MM-DD HH:mm:ss`.
217
233
  * - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
218
234
  * changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
219
235
  * - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
@@ -226,7 +242,7 @@ declare const SHOPWAVE_ENTITIES: Readonly<Record<EntityKind, EntityDefinition>>;
226
242
  *
227
243
  * Like `SHOPWAVE_ENTITIES`, this table is shared by the client functions
228
244
  * (`fetchMerchant`, `fetchUser`, `uploadImage`, …) and the server route
229
- * handlers in `react-shopwave-connect/next`, so the two halves of the contract
245
+ * handlers in `react-shopwave-connect/server`, so the two halves of the contract
230
246
  * can't drift apart. These aren't in `SHOPWAVE_ENTITIES` because they aren't
231
247
  * keyed collections: `GET /merchant` answers `{ merchant: {…} }`, not a map by id.
232
248
  */
@@ -272,6 +288,35 @@ declare const SHOPWAVE_UPLOAD: Readonly<{
272
288
  /** Multipart field name for the kind in the app request. */
273
289
  kindField: "kind";
274
290
  }>;
291
+ /**
292
+ * Read-only lists that aren't records: the merchant's distinct tags.
293
+ *
294
+ * Like `SHOPWAVE_RESOURCES`, shared by the client functions
295
+ * (`fetchProductTags`, `fetchProductInstanceTags`) and the server route
296
+ * handlers (`shopwave.lookup(kind)`), so the two halves can't drift apart.
297
+ *
298
+ * - productTags: `GET /product/tags` → `{ tags: string[] }`, every distinct tag
299
+ * on the merchant's products. No parameters. The API reference says
300
+ * "internal SKU tags are excluded", but the live API includes system tags
301
+ * written by imports and syncs (`PID-133`, `VID-137`, `#Hash:…`, `upc-…`;
302
+ * test merchant 5644, 29 Oct 2026). Apps that offer the list as suggestions
303
+ * filter those themselves. Tags of deleted products drop out of the list.
304
+ * - productInstanceTags: `GET /product/instance/tags` → `{ tags: string[] }`,
305
+ * the same for price instances (channels such as "Eat In", "Takeout").
306
+ *
307
+ * The app route for instance tags is `products/instance-tags`, one segment
308
+ * under `products`, so it sits beside `products/[id]` without nesting deeper.
309
+ */
310
+ interface LookupDefinition {
311
+ /** App route: `/api/<route>`. */
312
+ route: string;
313
+ /** Key of the list in the response (`{ tags: [...] }`). */
314
+ key: string;
315
+ /** Shopwave API path. */
316
+ upstream: string;
317
+ }
318
+ type LookupKind = "productTags" | "productInstanceTags";
319
+ declare const SHOPWAVE_LOOKUPS: Readonly<Record<LookupKind, LookupDefinition>>;
275
320
  //#endregion
276
321
  //#region src/server/api.d.ts
277
322
  /**
@@ -419,6 +464,14 @@ interface ShopwaveApiHandlers {
419
464
  passthrough(path: string): {
420
465
  GET: RouteHandler;
421
466
  };
467
+ /**
468
+ * Handler for a read-only list (see `SHOPWAVE_LOOKUPS`), e.g.
469
+ * `app/api/products/tags/route.ts`: `export const { GET } = shopwave.lookup("productTags")`.
470
+ * Takes no parameters, so the caller's `extras` aren't forwarded.
471
+ */
472
+ lookup(kind: LookupKind): {
473
+ GET: RouteHandler;
474
+ };
422
475
  /** Handlers for a singleton resource: `app/api/merchant/route.ts`, `app/api/user/route.ts`. */
423
476
  resource(kind: ResourceKind): ResourceHandlers;
424
477
  /** Handler for `app/api/upload/route.ts`. */
@@ -430,5 +483,5 @@ interface ShopwaveApiHandlers {
430
483
  declare function shopwaveDateTime(date?: Date): string;
431
484
  declare function createShopwaveApiHandlers(config: ShopwaveApiConfig): ShopwaveApiHandlers;
432
485
  //#endregion
433
- export { BLOCKED_EXTRAS, type CollectionHandlers, type DeletableEntityKind, type EntityDefinition, type EntityKind, type ForwardInit, type ItemHandlers, RequestBodyError, type ResourceDefinition, type ResourceHandlers, type ResourceKind, type RouteContext, type RouteHandler, SHOPWAVE_ENTITIES, 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, type UploadHandlers, type UploadKind, type UploadOptions, type WritableEntityKind, assertOAuthConfig, authorizationHeader, createShopwaveApiHandlers, createShopwaveOAuth, createState, isExpiredTokenResponse, isTokenExpired, normalizeStoredToken, normalizeWriteBody, parseExtras, readRequestToken, safeEqual, sanitizeReturnTo, shopwaveDateTime, tokenFromResponse };
486
+ 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, type UploadHandlers, type UploadKind, type UploadOptions, type WritableEntityKind, assertOAuthConfig, authorizationHeader, createShopwaveApiHandlers, createShopwaveOAuth, createState, isExpiredTokenResponse, isTokenExpired, normalizeStoredToken, normalizeWriteBody, parseExtras, readRequestToken, safeEqual, sanitizeReturnTo, shopwaveDateTime, tokenFromResponse };
434
487
  //# sourceMappingURL=index.d.cts.map
@@ -176,8 +176,9 @@ declare class ShopwaveAuthError extends Error {
176
176
  //#region src/core/entities.d.ts
177
177
  /**
178
178
  * How each Shopwave entity is addressed, shared by the client functions below
179
- * and the server route handlers in `react-shopwave-connect/next`, so the two
180
- * halves of the contract can't drift apart.
179
+ * and the server route handlers in `react-shopwave-connect/server` (wrapped with
180
+ * the session by `react-shopwave-connect/next`), so the two halves of the
181
+ * contract can't drift apart.
181
182
  */
182
183
  interface EntityDefinition {
183
184
  /** App route segment: `/api/<route>` and `/api/<route>/<id>`. */
@@ -195,13 +196,25 @@ interface EntityDefinition {
195
196
  /**
196
197
  * How a delete is done upstream:
197
198
  * - `"delete"`: `DELETE {apiUrl}/<upstream>` with the `idHeader` (205, empty body)
198
- * - `"retire"`: the API has no DELETE; the record is retired by POSTing
199
- * `{ id, [retireField]: <now> }` (employees: `exitDate`, promotions: `endDate`)
199
+ * - `"retire"`: the API has no DELETE; the record is retired by setting
200
+ * `retireField` to now (employees: `exitDate`, promotions: `endDate`)
200
201
  * - `"none"`: can't be deleted
201
202
  */
202
203
  deleteMode: "delete" | "retire" | "none";
203
204
  /** Date field set to "now" when `deleteMode` is `"retire"`. */
204
205
  retireField?: string;
206
+ /**
207
+ * Retire by reading the record and POSTing all of it back with the date(s)
208
+ * changed, instead of a partial `{ id, [retireField]: <now> }`. Needed where
209
+ * the API rejects a partial save (promotions: error 899, live 14 Oct 2026).
210
+ */
211
+ retireWithRecord?: boolean;
212
+ /**
213
+ * Start-date field of a record retired with `retireWithRecord`. If the record
214
+ * hasn't started yet (start after now), the start moves to now as well, so
215
+ * start = end and the record is expired without ever running.
216
+ */
217
+ retireStartField?: string;
205
218
  }
206
219
  type EntityKind = "product" | "category" | "store" | "promotion" | "employee" | "consumer";
207
220
  /** Entities that can be created/updated through the API. */
@@ -213,7 +226,10 @@ type DeletableEntityKind = Exclude<EntityKind, "consumer">;
213
226
  * (https://developer.merchantstack.com/api-reference.html) and checked against
214
227
  * the live API where noted:
215
228
  * - category/product/store: GET filtered by `<x>Ids`, POST upsert, DELETE with `<x>Id` → 205.
216
- * - promotion: no DELETE; "deleting" a promotion ends it by setting `endDate` to now.
229
+ * - promotion: no DELETE; promotions are never deleted, only expired. Expiring
230
+ * reads the promotion and saves the whole record back with `endDate` = now (and
231
+ * `startDate` = now too if it hadn't started); the live API rejects a partial
232
+ * `{ id, endDate }` save with error 899. Dates are UTC `YYYY-MM-DD HH:mm:ss`.
217
233
  * - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
218
234
  * changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
219
235
  * - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
@@ -226,7 +242,7 @@ declare const SHOPWAVE_ENTITIES: Readonly<Record<EntityKind, EntityDefinition>>;
226
242
  *
227
243
  * Like `SHOPWAVE_ENTITIES`, this table is shared by the client functions
228
244
  * (`fetchMerchant`, `fetchUser`, `uploadImage`, …) and the server route
229
- * handlers in `react-shopwave-connect/next`, so the two halves of the contract
245
+ * handlers in `react-shopwave-connect/server`, so the two halves of the contract
230
246
  * can't drift apart. These aren't in `SHOPWAVE_ENTITIES` because they aren't
231
247
  * keyed collections: `GET /merchant` answers `{ merchant: {…} }`, not a map by id.
232
248
  */
@@ -272,6 +288,35 @@ declare const SHOPWAVE_UPLOAD: Readonly<{
272
288
  /** Multipart field name for the kind in the app request. */
273
289
  kindField: "kind";
274
290
  }>;
291
+ /**
292
+ * Read-only lists that aren't records: the merchant's distinct tags.
293
+ *
294
+ * Like `SHOPWAVE_RESOURCES`, shared by the client functions
295
+ * (`fetchProductTags`, `fetchProductInstanceTags`) and the server route
296
+ * handlers (`shopwave.lookup(kind)`), so the two halves can't drift apart.
297
+ *
298
+ * - productTags: `GET /product/tags` → `{ tags: string[] }`, every distinct tag
299
+ * on the merchant's products. No parameters. The API reference says
300
+ * "internal SKU tags are excluded", but the live API includes system tags
301
+ * written by imports and syncs (`PID-133`, `VID-137`, `#Hash:…`, `upc-…`;
302
+ * test merchant 5644, 29 Oct 2026). Apps that offer the list as suggestions
303
+ * filter those themselves. Tags of deleted products drop out of the list.
304
+ * - productInstanceTags: `GET /product/instance/tags` → `{ tags: string[] }`,
305
+ * the same for price instances (channels such as "Eat In", "Takeout").
306
+ *
307
+ * The app route for instance tags is `products/instance-tags`, one segment
308
+ * under `products`, so it sits beside `products/[id]` without nesting deeper.
309
+ */
310
+ interface LookupDefinition {
311
+ /** App route: `/api/<route>`. */
312
+ route: string;
313
+ /** Key of the list in the response (`{ tags: [...] }`). */
314
+ key: string;
315
+ /** Shopwave API path. */
316
+ upstream: string;
317
+ }
318
+ type LookupKind = "productTags" | "productInstanceTags";
319
+ declare const SHOPWAVE_LOOKUPS: Readonly<Record<LookupKind, LookupDefinition>>;
275
320
  //#endregion
276
321
  //#region src/server/api.d.ts
277
322
  /**
@@ -419,6 +464,14 @@ interface ShopwaveApiHandlers {
419
464
  passthrough(path: string): {
420
465
  GET: RouteHandler;
421
466
  };
467
+ /**
468
+ * Handler for a read-only list (see `SHOPWAVE_LOOKUPS`), e.g.
469
+ * `app/api/products/tags/route.ts`: `export const { GET } = shopwave.lookup("productTags")`.
470
+ * Takes no parameters, so the caller's `extras` aren't forwarded.
471
+ */
472
+ lookup(kind: LookupKind): {
473
+ GET: RouteHandler;
474
+ };
422
475
  /** Handlers for a singleton resource: `app/api/merchant/route.ts`, `app/api/user/route.ts`. */
423
476
  resource(kind: ResourceKind): ResourceHandlers;
424
477
  /** Handler for `app/api/upload/route.ts`. */
@@ -430,5 +483,5 @@ interface ShopwaveApiHandlers {
430
483
  declare function shopwaveDateTime(date?: Date): string;
431
484
  declare function createShopwaveApiHandlers(config: ShopwaveApiConfig): ShopwaveApiHandlers;
432
485
  //#endregion
433
- export { BLOCKED_EXTRAS, type CollectionHandlers, type DeletableEntityKind, type EntityDefinition, type EntityKind, type ForwardInit, type ItemHandlers, RequestBodyError, type ResourceDefinition, type ResourceHandlers, type ResourceKind, type RouteContext, type RouteHandler, SHOPWAVE_ENTITIES, 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, type UploadHandlers, type UploadKind, type UploadOptions, type WritableEntityKind, assertOAuthConfig, authorizationHeader, createShopwaveApiHandlers, createShopwaveOAuth, createState, isExpiredTokenResponse, isTokenExpired, normalizeStoredToken, normalizeWriteBody, parseExtras, readRequestToken, safeEqual, sanitizeReturnTo, shopwaveDateTime, tokenFromResponse };
486
+ 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, type UploadHandlers, type UploadKind, type UploadOptions, type WritableEntityKind, assertOAuthConfig, authorizationHeader, createShopwaveApiHandlers, createShopwaveOAuth, createState, isExpiredTokenResponse, isTokenExpired, normalizeStoredToken, normalizeWriteBody, parseExtras, readRequestToken, safeEqual, sanitizeReturnTo, shopwaveDateTime, tokenFromResponse };
434
487
  //# sourceMappingURL=index.d.ts.map
@@ -251,7 +251,10 @@ function safeEqual(a, b) {
251
251
  * (https://developer.merchantstack.com/api-reference.html) and checked against
252
252
  * the live API where noted:
253
253
  * - category/product/store: GET filtered by `<x>Ids`, POST upsert, DELETE with `<x>Id` → 205.
254
- * - promotion: no DELETE; "deleting" a promotion ends it by setting `endDate` to now.
254
+ * - promotion: no DELETE; promotions are never deleted, only expired. Expiring
255
+ * reads the promotion and saves the whole record back with `endDate` = now (and
256
+ * `startDate` = now too if it hadn't started); the live API rejects a partial
257
+ * `{ id, endDate }` save with error 899. Dates are UTC `YYYY-MM-DD HH:mm:ss`.
255
258
  * - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
256
259
  * changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
257
260
  * - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
@@ -292,7 +295,9 @@ const SHOPWAVE_ENTITIES = Object.freeze({
292
295
  idHeader: "promotionId",
293
296
  writable: true,
294
297
  deleteMode: "retire",
295
- retireField: "endDate"
298
+ retireField: "endDate",
299
+ retireWithRecord: true,
300
+ retireStartField: "startDate"
296
301
  },
297
302
  employee: {
298
303
  route: "employees",
@@ -358,6 +363,18 @@ const SHOPWAVE_UPLOAD = Object.freeze({
358
363
  function isUploadKind(value) {
359
364
  return typeof value === "string" && Object.prototype.hasOwnProperty.call(UPLOAD_KINDS, value);
360
365
  }
366
+ const SHOPWAVE_LOOKUPS = Object.freeze({
367
+ productTags: {
368
+ route: "products/tags",
369
+ key: "tags",
370
+ upstream: "product/tags"
371
+ },
372
+ productInstanceTags: {
373
+ route: "products/instance-tags",
374
+ key: "tags",
375
+ upstream: "product/instance/tags"
376
+ }
377
+ });
361
378
 
362
379
  //#endregion
363
380
  //#region src/server/api.ts
@@ -491,6 +508,18 @@ function jsonError(status, error, message) {
491
508
  function shopwaveDateTime(date = /* @__PURE__ */ new Date()) {
492
509
  return date.toISOString().slice(0, 19).replace("T", " ");
493
510
  }
511
+ /**
512
+ * Reads a Shopwave datetime: ISO with a zone (what reads return, e.g.
513
+ * `2026-10-20T16:00:00.000Z`) or a zoneless `YYYY-MM-DD HH:mm:ss`, which is UTC.
514
+ * `null` when the value isn't a date.
515
+ */
516
+ function parseShopwaveDateTime(value) {
517
+ if (typeof value !== "string" || !value.trim()) return null;
518
+ const text = value.trim();
519
+ const zoneless = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/.test(text);
520
+ const date = new Date(zoneless ? `${text.replace(" ", "T")}Z` : text);
521
+ return Number.isNaN(date.getTime()) ? null : date;
522
+ }
494
523
  const NULL_BODY_STATUS = new Set([
495
524
  101,
496
525
  103,
@@ -690,6 +719,45 @@ function createShopwaveApiHandlers(config) {
690
719
  function badId() {
691
720
  return jsonError(400, "invalid_id", "Missing or invalid id");
692
721
  }
722
+ /**
723
+ * Retires a record the API won't take a partial save for (promotions): reads it,
724
+ * sets `field` to now (and `def.retireStartField` to now too when the record
725
+ * hasn't started yet), and POSTs the whole record back. Both dates are sent as
726
+ * UTC `YYYY-MM-DD HH:mm:ss`; reads return them as ISO with `Z`.
727
+ */
728
+ async function retireWithRecord(request, def, id, field) {
729
+ const read = await forward(request, {
730
+ path: def.upstream,
731
+ headers: { [def.idsHeader]: id },
732
+ forwardExtras: false
733
+ });
734
+ if (!read.ok) return read;
735
+ let body;
736
+ try {
737
+ body = await read.json();
738
+ } catch {
739
+ return jsonError(502, "invalid_upstream", `The Shopwave API's ${def.upstream} read wasn't JSON`);
740
+ }
741
+ const map = isPlainObject(body) && isPlainObject(body[def.collection]) ? body[def.collection] : {};
742
+ const record = Object.values(map).find((entry) => isPlainObject(entry) && String(entry.id) === id);
743
+ if (!record) return jsonError(404, "not_found", `No ${def.upstream} with id ${id}`);
744
+ const now = /* @__PURE__ */ new Date();
745
+ const retired = {
746
+ ...record,
747
+ [field]: shopwaveDateTime(now)
748
+ };
749
+ const startField = def.retireStartField;
750
+ if (startField) {
751
+ const start = parseShopwaveDateTime(record[startField]);
752
+ if (start) retired[startField] = shopwaveDateTime(start > now ? now : start);
753
+ }
754
+ return forward(request, {
755
+ method: "POST",
756
+ path: def.upstream,
757
+ postBody: { [def.collection]: { 0: retired } },
758
+ forwardExtras: false
759
+ });
760
+ }
693
761
  return {
694
762
  forward,
695
763
  entity,
@@ -725,6 +793,7 @@ function createShopwaveApiHandlers(config) {
725
793
  if (def.deleteMode === "none") return notSupported(def, "deletes");
726
794
  if (def.deleteMode === "retire") {
727
795
  const field = def.retireField ?? "exitDate";
796
+ if (def.retireWithRecord) return retireWithRecord(request, def, id, field);
728
797
  return forward(request, {
729
798
  method: "POST",
730
799
  path: def.upstream,
@@ -747,6 +816,14 @@ function createShopwaveApiHandlers(config) {
747
816
  passthrough(path) {
748
817
  return { GET: (request) => forward(request, { path }) };
749
818
  },
819
+ lookup(kind) {
820
+ const def = SHOPWAVE_LOOKUPS[kind];
821
+ if (!def) throw new Error(`Unknown Shopwave lookup "${kind}"`);
822
+ return { GET: (request) => forward(request, {
823
+ path: def.upstream,
824
+ forwardExtras: false
825
+ }) };
826
+ },
750
827
  resource(kind) {
751
828
  const def = resourceDef(kind);
752
829
  const save = (request) => writeResource(request, def);
@@ -763,5 +840,5 @@ function createShopwaveApiHandlers(config) {
763
840
  }
764
841
 
765
842
  //#endregion
766
- export { BLOCKED_EXTRAS, RequestBodyError, SHOPWAVE_ENTITIES, SHOPWAVE_RESOURCES, SHOPWAVE_TOKEN_EXPIRED_ERROR_ID, SHOPWAVE_UPLOAD, ShopwaveAuthError, UPLOAD_KINDS, assertOAuthConfig, authorizationHeader, createShopwaveApiHandlers, createShopwaveOAuth, createState, isExpiredTokenResponse, isTokenExpired, normalizeStoredToken, normalizeWriteBody, parseExtras, readRequestToken, safeEqual, sanitizeReturnTo, shopwaveDateTime, tokenFromResponse };
843
+ export { BLOCKED_EXTRAS, RequestBodyError, SHOPWAVE_ENTITIES, SHOPWAVE_LOOKUPS, SHOPWAVE_RESOURCES, SHOPWAVE_TOKEN_EXPIRED_ERROR_ID, SHOPWAVE_UPLOAD, ShopwaveAuthError, UPLOAD_KINDS, assertOAuthConfig, authorizationHeader, createShopwaveApiHandlers, createShopwaveOAuth, createState, isExpiredTokenResponse, isTokenExpired, normalizeStoredToken, normalizeWriteBody, parseExtras, readRequestToken, safeEqual, sanitizeReturnTo, shopwaveDateTime, tokenFromResponse };
767
844
  //# sourceMappingURL=index.js.map