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.
@@ -87,8 +87,9 @@ interface ShopwaveOAuthClient {
87
87
  //#region src/core/entities.d.ts
88
88
  /**
89
89
  * How each Shopwave entity is addressed, shared by the client functions below
90
- * and the server route handlers in `react-shopwave-connect/next`, so the two
91
- * halves of the contract can't drift apart.
90
+ * and the server route handlers in `react-shopwave-connect/server` (wrapped with
91
+ * the session by `react-shopwave-connect/next`), so the two halves of the
92
+ * contract can't drift apart.
92
93
  */
93
94
  interface EntityDefinition {
94
95
  /** App route segment: `/api/<route>` and `/api/<route>/<id>`. */
@@ -106,13 +107,25 @@ interface EntityDefinition {
106
107
  /**
107
108
  * How a delete is done upstream:
108
109
  * - `"delete"`: `DELETE {apiUrl}/<upstream>` with the `idHeader` (205, empty body)
109
- * - `"retire"`: the API has no DELETE; the record is retired by POSTing
110
- * `{ id, [retireField]: <now> }` (employees: `exitDate`, promotions: `endDate`)
110
+ * - `"retire"`: the API has no DELETE; the record is retired by setting
111
+ * `retireField` to now (employees: `exitDate`, promotions: `endDate`)
111
112
  * - `"none"`: can't be deleted
112
113
  */
113
114
  deleteMode: "delete" | "retire" | "none";
114
115
  /** Date field set to "now" when `deleteMode` is `"retire"`. */
115
116
  retireField?: string;
117
+ /**
118
+ * Retire by reading the record and POSTing all of it back with the date(s)
119
+ * changed, instead of a partial `{ id, [retireField]: <now> }`. Needed where
120
+ * the API rejects a partial save (promotions: error 899, live 14 Oct 2026).
121
+ */
122
+ retireWithRecord?: boolean;
123
+ /**
124
+ * Start-date field of a record retired with `retireWithRecord`. If the record
125
+ * hasn't started yet (start after now), the start moves to now as well, so
126
+ * start = end and the record is expired without ever running.
127
+ */
128
+ retireStartField?: string;
116
129
  }
117
130
  type EntityKind = "product" | "category" | "store" | "promotion" | "employee" | "consumer";
118
131
  /**
@@ -120,7 +133,10 @@ type EntityKind = "product" | "category" | "store" | "promotion" | "employee" |
120
133
  * (https://developer.merchantstack.com/api-reference.html) and checked against
121
134
  * the live API where noted:
122
135
  * - category/product/store: GET filtered by `<x>Ids`, POST upsert, DELETE with `<x>Id` → 205.
123
- * - promotion: no DELETE; "deleting" a promotion ends it by setting `endDate` to now.
136
+ * - promotion: no DELETE; promotions are never deleted, only expired. Expiring
137
+ * reads the promotion and saves the whole record back with `endDate` = now (and
138
+ * `startDate` = now too if it hadn't started); the live API rejects a partial
139
+ * `{ id, endDate }` save with error 899. Dates are UTC `YYYY-MM-DD HH:mm:ss`.
124
140
  * - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
125
141
  * changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
126
142
  * - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
@@ -133,7 +149,7 @@ declare const SHOPWAVE_ENTITIES: Readonly<Record<EntityKind, EntityDefinition>>;
133
149
  *
134
150
  * Like `SHOPWAVE_ENTITIES`, this table is shared by the client functions
135
151
  * (`fetchMerchant`, `fetchUser`, `uploadImage`, …) and the server route
136
- * handlers in `react-shopwave-connect/next`, so the two halves of the contract
152
+ * handlers in `react-shopwave-connect/server`, so the two halves of the contract
137
153
  * can't drift apart. These aren't in `SHOPWAVE_ENTITIES` because they aren't
138
154
  * keyed collections: `GET /merchant` answers `{ merchant: {…} }`, not a map by id.
139
155
  */
@@ -166,6 +182,35 @@ declare const SHOPWAVE_RESOURCES: Readonly<Record<ResourceKind, ResourceDefiniti
166
182
  type UploadKind = "merchant" | "merchantFeature" | "product" | "user" | "applicationLogo" | "applicationImages";
167
183
  /** `UploadKind` → the API's `contentType` value. */
168
184
  declare const UPLOAD_KINDS: Readonly<Record<UploadKind, string>>;
185
+ /**
186
+ * Read-only lists that aren't records: the merchant's distinct tags.
187
+ *
188
+ * Like `SHOPWAVE_RESOURCES`, shared by the client functions
189
+ * (`fetchProductTags`, `fetchProductInstanceTags`) and the server route
190
+ * handlers (`shopwave.lookup(kind)`), so the two halves can't drift apart.
191
+ *
192
+ * - productTags: `GET /product/tags` → `{ tags: string[] }`, every distinct tag
193
+ * on the merchant's products. No parameters. The API reference says
194
+ * "internal SKU tags are excluded", but the live API includes system tags
195
+ * written by imports and syncs (`PID-133`, `VID-137`, `#Hash:…`, `upc-…`;
196
+ * test merchant 5644, 29 Oct 2026). Apps that offer the list as suggestions
197
+ * filter those themselves. Tags of deleted products drop out of the list.
198
+ * - productInstanceTags: `GET /product/instance/tags` → `{ tags: string[] }`,
199
+ * the same for price instances (channels such as "Eat In", "Takeout").
200
+ *
201
+ * The app route for instance tags is `products/instance-tags`, one segment
202
+ * under `products`, so it sits beside `products/[id]` without nesting deeper.
203
+ */
204
+ interface LookupDefinition {
205
+ /** App route: `/api/<route>`. */
206
+ route: string;
207
+ /** Key of the list in the response (`{ tags: [...] }`). */
208
+ key: string;
209
+ /** Shopwave API path. */
210
+ upstream: string;
211
+ }
212
+ type LookupKind = "productTags" | "productInstanceTags";
213
+ declare const SHOPWAVE_LOOKUPS: Readonly<Record<LookupKind, LookupDefinition>>;
169
214
  //#endregion
170
215
  //#region src/server/api.d.ts
171
216
  /**
@@ -288,6 +333,14 @@ interface ShopwaveApiHandlers {
288
333
  passthrough(path: string): {
289
334
  GET: RouteHandler;
290
335
  };
336
+ /**
337
+ * Handler for a read-only list (see `SHOPWAVE_LOOKUPS`), e.g.
338
+ * `app/api/products/tags/route.ts`: `export const { GET } = shopwave.lookup("productTags")`.
339
+ * Takes no parameters, so the caller's `extras` aren't forwarded.
340
+ */
341
+ lookup(kind: LookupKind): {
342
+ GET: RouteHandler;
343
+ };
291
344
  /** Handlers for a singleton resource: `app/api/merchant/route.ts`, `app/api/user/route.ts`. */
292
345
  resource(kind: ResourceKind): ResourceHandlers;
293
346
  /** Handler for `app/api/upload/route.ts`. */
@@ -495,9 +548,12 @@ interface ShopwaveNextApiConfig extends Omit<ShopwaveApiConfig, "getAuthorizatio
495
548
  * export const { GET } = shopwave.resource("user");
496
549
  * // app/api/upload/route.ts
497
550
  * export const { POST } = shopwave.upload();
551
+ * // app/api/products/tags/route.ts, app/api/products/instance-tags/route.ts
552
+ * export const { GET } = shopwave.lookup("productTags");
553
+ * export const { GET } = shopwave.lookup("productInstanceTags");
498
554
  * ```
499
555
  */
500
556
  declare function createShopwaveApi(config: ShopwaveNextApiConfig): ShopwaveApiHandlers;
501
557
  //#endregion
502
- export { AuthContext, type CollectionHandlers, type EntityDefinition, type EntityKind, type ForwardInit, type ItemHandlers, ProtectOptions, type ResourceHandlers, type ResourceKind, type RouteContext, type RouteHandler, SHOPWAVE_ENTITIES, SHOPWAVE_RESOURCES, type SessionStatus, type ShopwaveApiHandlers, ShopwaveAuth, ShopwaveAuthConfig, ShopwaveAuthError, ShopwaveNextApiConfig, ShopwaveSessionConfig, ShopwaveSessionData, type ShopwaveToken, UPLOAD_KINDS, type UploadHandlers, type UploadKind, type UploadOptions, authorizationHeader, createShopwaveApi, createShopwaveAuth, isExpiredTokenResponse, readRequestToken };
558
+ export { AuthContext, type CollectionHandlers, type EntityDefinition, type EntityKind, type ForwardInit, type ItemHandlers, type LookupKind, ProtectOptions, type ResourceHandlers, type ResourceKind, type RouteContext, type RouteHandler, SHOPWAVE_ENTITIES, SHOPWAVE_LOOKUPS, SHOPWAVE_RESOURCES, type SessionStatus, type ShopwaveApiHandlers, ShopwaveAuth, ShopwaveAuthConfig, ShopwaveAuthError, ShopwaveNextApiConfig, ShopwaveSessionConfig, ShopwaveSessionData, type ShopwaveToken, UPLOAD_KINDS, type UploadHandlers, type UploadKind, type UploadOptions, authorizationHeader, createShopwaveApi, createShopwaveAuth, isExpiredTokenResponse, readRequestToken };
503
559
  //# sourceMappingURL=index.d.cts.map
@@ -87,8 +87,9 @@ interface ShopwaveOAuthClient {
87
87
  //#region src/core/entities.d.ts
88
88
  /**
89
89
  * How each Shopwave entity is addressed, shared by the client functions below
90
- * and the server route handlers in `react-shopwave-connect/next`, so the two
91
- * halves of the contract can't drift apart.
90
+ * and the server route handlers in `react-shopwave-connect/server` (wrapped with
91
+ * the session by `react-shopwave-connect/next`), so the two halves of the
92
+ * contract can't drift apart.
92
93
  */
93
94
  interface EntityDefinition {
94
95
  /** App route segment: `/api/<route>` and `/api/<route>/<id>`. */
@@ -106,13 +107,25 @@ interface EntityDefinition {
106
107
  /**
107
108
  * How a delete is done upstream:
108
109
  * - `"delete"`: `DELETE {apiUrl}/<upstream>` with the `idHeader` (205, empty body)
109
- * - `"retire"`: the API has no DELETE; the record is retired by POSTing
110
- * `{ id, [retireField]: <now> }` (employees: `exitDate`, promotions: `endDate`)
110
+ * - `"retire"`: the API has no DELETE; the record is retired by setting
111
+ * `retireField` to now (employees: `exitDate`, promotions: `endDate`)
111
112
  * - `"none"`: can't be deleted
112
113
  */
113
114
  deleteMode: "delete" | "retire" | "none";
114
115
  /** Date field set to "now" when `deleteMode` is `"retire"`. */
115
116
  retireField?: string;
117
+ /**
118
+ * Retire by reading the record and POSTing all of it back with the date(s)
119
+ * changed, instead of a partial `{ id, [retireField]: <now> }`. Needed where
120
+ * the API rejects a partial save (promotions: error 899, live 14 Oct 2026).
121
+ */
122
+ retireWithRecord?: boolean;
123
+ /**
124
+ * Start-date field of a record retired with `retireWithRecord`. If the record
125
+ * hasn't started yet (start after now), the start moves to now as well, so
126
+ * start = end and the record is expired without ever running.
127
+ */
128
+ retireStartField?: string;
116
129
  }
117
130
  type EntityKind = "product" | "category" | "store" | "promotion" | "employee" | "consumer";
118
131
  /**
@@ -120,7 +133,10 @@ type EntityKind = "product" | "category" | "store" | "promotion" | "employee" |
120
133
  * (https://developer.merchantstack.com/api-reference.html) and checked against
121
134
  * the live API where noted:
122
135
  * - category/product/store: GET filtered by `<x>Ids`, POST upsert, DELETE with `<x>Id` → 205.
123
- * - promotion: no DELETE; "deleting" a promotion ends it by setting `endDate` to now.
136
+ * - promotion: no DELETE; promotions are never deleted, only expired. Expiring
137
+ * reads the promotion and saves the whole record back with `endDate` = now (and
138
+ * `startDate` = now too if it hadn't started); the live API rejects a partial
139
+ * `{ id, endDate }` save with error 899. Dates are UTC `YYYY-MM-DD HH:mm:ss`.
124
140
  * - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
125
141
  * changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
126
142
  * - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
@@ -133,7 +149,7 @@ declare const SHOPWAVE_ENTITIES: Readonly<Record<EntityKind, EntityDefinition>>;
133
149
  *
134
150
  * Like `SHOPWAVE_ENTITIES`, this table is shared by the client functions
135
151
  * (`fetchMerchant`, `fetchUser`, `uploadImage`, …) and the server route
136
- * handlers in `react-shopwave-connect/next`, so the two halves of the contract
152
+ * handlers in `react-shopwave-connect/server`, so the two halves of the contract
137
153
  * can't drift apart. These aren't in `SHOPWAVE_ENTITIES` because they aren't
138
154
  * keyed collections: `GET /merchant` answers `{ merchant: {…} }`, not a map by id.
139
155
  */
@@ -166,6 +182,35 @@ declare const SHOPWAVE_RESOURCES: Readonly<Record<ResourceKind, ResourceDefiniti
166
182
  type UploadKind = "merchant" | "merchantFeature" | "product" | "user" | "applicationLogo" | "applicationImages";
167
183
  /** `UploadKind` → the API's `contentType` value. */
168
184
  declare const UPLOAD_KINDS: Readonly<Record<UploadKind, string>>;
185
+ /**
186
+ * Read-only lists that aren't records: the merchant's distinct tags.
187
+ *
188
+ * Like `SHOPWAVE_RESOURCES`, shared by the client functions
189
+ * (`fetchProductTags`, `fetchProductInstanceTags`) and the server route
190
+ * handlers (`shopwave.lookup(kind)`), so the two halves can't drift apart.
191
+ *
192
+ * - productTags: `GET /product/tags` → `{ tags: string[] }`, every distinct tag
193
+ * on the merchant's products. No parameters. The API reference says
194
+ * "internal SKU tags are excluded", but the live API includes system tags
195
+ * written by imports and syncs (`PID-133`, `VID-137`, `#Hash:…`, `upc-…`;
196
+ * test merchant 5644, 29 Oct 2026). Apps that offer the list as suggestions
197
+ * filter those themselves. Tags of deleted products drop out of the list.
198
+ * - productInstanceTags: `GET /product/instance/tags` → `{ tags: string[] }`,
199
+ * the same for price instances (channels such as "Eat In", "Takeout").
200
+ *
201
+ * The app route for instance tags is `products/instance-tags`, one segment
202
+ * under `products`, so it sits beside `products/[id]` without nesting deeper.
203
+ */
204
+ interface LookupDefinition {
205
+ /** App route: `/api/<route>`. */
206
+ route: string;
207
+ /** Key of the list in the response (`{ tags: [...] }`). */
208
+ key: string;
209
+ /** Shopwave API path. */
210
+ upstream: string;
211
+ }
212
+ type LookupKind = "productTags" | "productInstanceTags";
213
+ declare const SHOPWAVE_LOOKUPS: Readonly<Record<LookupKind, LookupDefinition>>;
169
214
  //#endregion
170
215
  //#region src/server/api.d.ts
171
216
  /**
@@ -288,6 +333,14 @@ interface ShopwaveApiHandlers {
288
333
  passthrough(path: string): {
289
334
  GET: RouteHandler;
290
335
  };
336
+ /**
337
+ * Handler for a read-only list (see `SHOPWAVE_LOOKUPS`), e.g.
338
+ * `app/api/products/tags/route.ts`: `export const { GET } = shopwave.lookup("productTags")`.
339
+ * Takes no parameters, so the caller's `extras` aren't forwarded.
340
+ */
341
+ lookup(kind: LookupKind): {
342
+ GET: RouteHandler;
343
+ };
291
344
  /** Handlers for a singleton resource: `app/api/merchant/route.ts`, `app/api/user/route.ts`. */
292
345
  resource(kind: ResourceKind): ResourceHandlers;
293
346
  /** Handler for `app/api/upload/route.ts`. */
@@ -495,9 +548,12 @@ interface ShopwaveNextApiConfig extends Omit<ShopwaveApiConfig, "getAuthorizatio
495
548
  * export const { GET } = shopwave.resource("user");
496
549
  * // app/api/upload/route.ts
497
550
  * export const { POST } = shopwave.upload();
551
+ * // app/api/products/tags/route.ts, app/api/products/instance-tags/route.ts
552
+ * export const { GET } = shopwave.lookup("productTags");
553
+ * export const { GET } = shopwave.lookup("productInstanceTags");
498
554
  * ```
499
555
  */
500
556
  declare function createShopwaveApi(config: ShopwaveNextApiConfig): ShopwaveApiHandlers;
501
557
  //#endregion
502
- export { AuthContext, type CollectionHandlers, type EntityDefinition, type EntityKind, type ForwardInit, type ItemHandlers, ProtectOptions, type ResourceHandlers, type ResourceKind, type RouteContext, type RouteHandler, SHOPWAVE_ENTITIES, SHOPWAVE_RESOURCES, type SessionStatus, type ShopwaveApiHandlers, ShopwaveAuth, ShopwaveAuthConfig, ShopwaveAuthError, ShopwaveNextApiConfig, ShopwaveSessionConfig, ShopwaveSessionData, type ShopwaveToken, UPLOAD_KINDS, type UploadHandlers, type UploadKind, type UploadOptions, authorizationHeader, createShopwaveApi, createShopwaveAuth, isExpiredTokenResponse, readRequestToken };
558
+ export { AuthContext, type CollectionHandlers, type EntityDefinition, type EntityKind, type ForwardInit, type ItemHandlers, type LookupKind, ProtectOptions, type ResourceHandlers, type ResourceKind, type RouteContext, type RouteHandler, SHOPWAVE_ENTITIES, SHOPWAVE_LOOKUPS, SHOPWAVE_RESOURCES, type SessionStatus, type ShopwaveApiHandlers, ShopwaveAuth, ShopwaveAuthConfig, ShopwaveAuthError, ShopwaveNextApiConfig, ShopwaveSessionConfig, ShopwaveSessionData, type ShopwaveToken, UPLOAD_KINDS, type UploadHandlers, type UploadKind, type UploadOptions, authorizationHeader, createShopwaveApi, createShopwaveAuth, isExpiredTokenResponse, readRequestToken };
503
559
  //# sourceMappingURL=index.d.ts.map
@@ -255,7 +255,10 @@ function safeEqual(a, b) {
255
255
  * (https://developer.merchantstack.com/api-reference.html) and checked against
256
256
  * the live API where noted:
257
257
  * - category/product/store: GET filtered by `<x>Ids`, POST upsert, DELETE with `<x>Id` → 205.
258
- * - promotion: no DELETE; "deleting" a promotion ends it by setting `endDate` to now.
258
+ * - promotion: no DELETE; promotions are never deleted, only expired. Expiring
259
+ * reads the promotion and saves the whole record back with `endDate` = now (and
260
+ * `startDate` = now too if it hadn't started); the live API rejects a partial
261
+ * `{ id, endDate }` save with error 899. Dates are UTC `YYYY-MM-DD HH:mm:ss`.
259
262
  * - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
260
263
  * changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
261
264
  * - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
@@ -296,7 +299,9 @@ const SHOPWAVE_ENTITIES = Object.freeze({
296
299
  idHeader: "promotionId",
297
300
  writable: true,
298
301
  deleteMode: "retire",
299
- retireField: "endDate"
302
+ retireField: "endDate",
303
+ retireWithRecord: true,
304
+ retireStartField: "startDate"
300
305
  },
301
306
  employee: {
302
307
  route: "employees",
@@ -362,6 +367,18 @@ const SHOPWAVE_UPLOAD = Object.freeze({
362
367
  function isUploadKind(value) {
363
368
  return typeof value === "string" && Object.prototype.hasOwnProperty.call(UPLOAD_KINDS, value);
364
369
  }
370
+ const SHOPWAVE_LOOKUPS = Object.freeze({
371
+ productTags: {
372
+ route: "products/tags",
373
+ key: "tags",
374
+ upstream: "product/tags"
375
+ },
376
+ productInstanceTags: {
377
+ route: "products/instance-tags",
378
+ key: "tags",
379
+ upstream: "product/instance/tags"
380
+ }
381
+ });
365
382
 
366
383
  //#endregion
367
384
  //#region src/server/api.ts
@@ -495,6 +512,18 @@ function jsonError(status, error, message) {
495
512
  function shopwaveDateTime(date = /* @__PURE__ */ new Date()) {
496
513
  return date.toISOString().slice(0, 19).replace("T", " ");
497
514
  }
515
+ /**
516
+ * Reads a Shopwave datetime: ISO with a zone (what reads return, e.g.
517
+ * `2026-10-20T16:00:00.000Z`) or a zoneless `YYYY-MM-DD HH:mm:ss`, which is UTC.
518
+ * `null` when the value isn't a date.
519
+ */
520
+ function parseShopwaveDateTime(value) {
521
+ if (typeof value !== "string" || !value.trim()) return null;
522
+ const text = value.trim();
523
+ const zoneless = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/.test(text);
524
+ const date = new Date(zoneless ? `${text.replace(" ", "T")}Z` : text);
525
+ return Number.isNaN(date.getTime()) ? null : date;
526
+ }
498
527
  const NULL_BODY_STATUS = new Set([
499
528
  101,
500
529
  103,
@@ -694,6 +723,45 @@ function createShopwaveApiHandlers(config) {
694
723
  function badId() {
695
724
  return jsonError(400, "invalid_id", "Missing or invalid id");
696
725
  }
726
+ /**
727
+ * Retires a record the API won't take a partial save for (promotions): reads it,
728
+ * sets `field` to now (and `def.retireStartField` to now too when the record
729
+ * hasn't started yet), and POSTs the whole record back. Both dates are sent as
730
+ * UTC `YYYY-MM-DD HH:mm:ss`; reads return them as ISO with `Z`.
731
+ */
732
+ async function retireWithRecord(request, def, id, field) {
733
+ const read = await forward(request, {
734
+ path: def.upstream,
735
+ headers: { [def.idsHeader]: id },
736
+ forwardExtras: false
737
+ });
738
+ if (!read.ok) return read;
739
+ let body;
740
+ try {
741
+ body = await read.json();
742
+ } catch {
743
+ return jsonError(502, "invalid_upstream", `The Shopwave API's ${def.upstream} read wasn't JSON`);
744
+ }
745
+ const map = isPlainObject(body) && isPlainObject(body[def.collection]) ? body[def.collection] : {};
746
+ const record = Object.values(map).find((entry) => isPlainObject(entry) && String(entry.id) === id);
747
+ if (!record) return jsonError(404, "not_found", `No ${def.upstream} with id ${id}`);
748
+ const now = /* @__PURE__ */ new Date();
749
+ const retired = {
750
+ ...record,
751
+ [field]: shopwaveDateTime(now)
752
+ };
753
+ const startField = def.retireStartField;
754
+ if (startField) {
755
+ const start = parseShopwaveDateTime(record[startField]);
756
+ if (start) retired[startField] = shopwaveDateTime(start > now ? now : start);
757
+ }
758
+ return forward(request, {
759
+ method: "POST",
760
+ path: def.upstream,
761
+ postBody: { [def.collection]: { 0: retired } },
762
+ forwardExtras: false
763
+ });
764
+ }
697
765
  return {
698
766
  forward,
699
767
  entity,
@@ -729,6 +797,7 @@ function createShopwaveApiHandlers(config) {
729
797
  if (def.deleteMode === "none") return notSupported(def, "deletes");
730
798
  if (def.deleteMode === "retire") {
731
799
  const field = def.retireField ?? "exitDate";
800
+ if (def.retireWithRecord) return retireWithRecord(request, def, id, field);
732
801
  return forward(request, {
733
802
  method: "POST",
734
803
  path: def.upstream,
@@ -751,6 +820,14 @@ function createShopwaveApiHandlers(config) {
751
820
  passthrough(path) {
752
821
  return { GET: (request) => forward(request, { path }) };
753
822
  },
823
+ lookup(kind) {
824
+ const def = SHOPWAVE_LOOKUPS[kind];
825
+ if (!def) throw new Error(`Unknown Shopwave lookup "${kind}"`);
826
+ return { GET: (request) => forward(request, {
827
+ path: def.upstream,
828
+ forwardExtras: false
829
+ }) };
830
+ },
754
831
  resource(kind) {
755
832
  const def = resourceDef(kind);
756
833
  const save = (request) => writeResource(request, def);
@@ -1093,6 +1170,9 @@ function createShopwaveAuth(config) {
1093
1170
  * export const { GET } = shopwave.resource("user");
1094
1171
  * // app/api/upload/route.ts
1095
1172
  * export const { POST } = shopwave.upload();
1173
+ * // app/api/products/tags/route.ts, app/api/products/instance-tags/route.ts
1174
+ * export const { GET } = shopwave.lookup("productTags");
1175
+ * export const { GET } = shopwave.lookup("productInstanceTags");
1096
1176
  * ```
1097
1177
  */
1098
1178
  function createShopwaveApi(config) {
@@ -1105,5 +1185,5 @@ function createShopwaveApi(config) {
1105
1185
  }
1106
1186
 
1107
1187
  //#endregion
1108
- export { SHOPWAVE_ENTITIES, SHOPWAVE_RESOURCES, ShopwaveAuthError, UPLOAD_KINDS, authorizationHeader, createShopwaveApi, createShopwaveAuth, isExpiredTokenResponse, readRequestToken };
1188
+ export { SHOPWAVE_ENTITIES, SHOPWAVE_LOOKUPS, SHOPWAVE_RESOURCES, ShopwaveAuthError, UPLOAD_KINDS, authorizationHeader, createShopwaveApi, createShopwaveAuth, isExpiredTokenResponse, readRequestToken };
1109
1189
  //# sourceMappingURL=index.js.map