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.
- package/README.md +17 -5
- package/dist/core/index.cjs +119 -48
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +80 -9
- package/dist/core/index.d.ts +80 -9
- package/dist/core/index.js +116 -49
- package/dist/core/index.js.map +1 -1
- package/dist/hooks/index.cjs +129 -44
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.cts +21 -1
- package/dist/hooks/index.d.ts +21 -1
- package/dist/hooks/index.js +128 -45
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.cjs +148 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +100 -9
- package/dist/index.d.ts +100 -9
- package/dist/index.js +143 -49
- package/dist/index.js.map +1 -1
- package/dist/next/index.cjs +83 -2
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +63 -7
- package/dist/next/index.d.ts +63 -7
- package/dist/next/index.js +83 -3
- package/dist/next/index.js.map +1 -1
- package/dist/server/index.cjs +80 -2
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +60 -7
- package/dist/server/index.d.ts +60 -7
- package/dist/server/index.js +80 -3
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/next/index.d.cts
CHANGED
|
@@ -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/
|
|
91
|
-
*
|
|
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
|
|
110
|
-
* `
|
|
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;
|
|
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/
|
|
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
|
package/dist/next/index.d.ts
CHANGED
|
@@ -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/
|
|
91
|
-
*
|
|
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
|
|
110
|
-
* `
|
|
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;
|
|
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/
|
|
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
|
package/dist/next/index.js
CHANGED
|
@@ -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;
|
|
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
|