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/server/index.d.cts
CHANGED
|
@@ -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/
|
|
180
|
-
*
|
|
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
|
|
199
|
-
* `
|
|
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;
|
|
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/
|
|
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
|
package/dist/server/index.d.ts
CHANGED
|
@@ -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/
|
|
180
|
-
*
|
|
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
|
|
199
|
-
* `
|
|
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;
|
|
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/
|
|
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
|
package/dist/server/index.js
CHANGED
|
@@ -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;
|
|
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
|