react-shopwave-connect 0.2.0 → 0.4.0

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.
@@ -34,19 +34,24 @@ interface apiResponse {
34
34
  /**
35
35
  * Per-call configuration shared by every core API function.
36
36
  *
37
- * Nothing here is React-specific. By accepting these options per call, the
38
- * core layer works in the browser/Next (relative `/api/...` URLs, the default)
39
- * AND in Node / CLI / Vue / Angular by passing an absolute `baseUrl`, an auth
40
- * `token`, and/or a custom `fetch` implementation.
37
+ * Nothing here is React-specific. In the browser/Next leave `baseUrl` empty to
38
+ * call the app's own `/api/...` routes (which hold the session). From Node, a
39
+ * CLI or tests, pass an absolute `baseUrl` pointing at such an app plus a
40
+ * `token`.
41
41
  */
42
42
  interface RequestOptions {
43
43
  /**
44
44
  * Prefixed in front of every request path. Leave undefined in the browser to
45
- * use relative URLs (e.g. `/api/products`). In Node/CLI pass something like
46
- * `https://api.shopwave.com`.
45
+ * use relative URLs (e.g. `/api/products`). From Node pass the app origin,
46
+ * e.g. `http://localhost:3000`.
47
47
  */
48
48
  baseUrl?: string;
49
- /** OAuth/session token forwarded in the `extras.token` header. */
49
+ /**
50
+ * Shopwave OAuth access token. Sent as `Authorization: OAuth <token>` on every
51
+ * request (GET, POST, PUT and DELETE alike). A value that already starts with
52
+ * a scheme (`OAuth …` / `Bearer …`) is sent as-is. Leave unset in the browser:
53
+ * the app's routes use the session cookie.
54
+ */
50
55
  token?: string;
51
56
  /**
52
57
  * Custom fetch implementation. Defaults to the global `fetch`. Useful for
@@ -58,24 +63,79 @@ interface RequestOptions {
58
63
  }
59
64
  /** The Shopwave API ships request metadata inside a single `extras` header. */
60
65
  type Extras = Record<string, unknown>;
66
+ /** Options with `token` filled in from a legacy per-call `params.token`, unless `options.token` is set. */
67
+ declare function withToken(options: RequestOptions, token: string | undefined): RequestOptions;
68
+ /** `Authorization` header value for a token (`OAuth <token>` unless it already has a scheme). */
69
+ declare function authorizationFor(token: string): string;
70
+ /**
71
+ * Low-level request: resolves `{ status, body }` for 2xx responses and throws
72
+ * {@link ShopwaveApiError} otherwise. `init.headers` are merged over the
73
+ * standard ones (Accept + Authorization).
74
+ */
75
+ declare function apiRequest(path: string, init: RequestInit, options?: RequestOptions): Promise<{
76
+ status: number;
77
+ body: unknown;
78
+ }>;
61
79
  /**
62
80
  * Returns a JSON string of API errors if the response carries any, otherwise
63
- * `null`. Mirrors the original `!responseJson.api.message.errors` checks.
81
+ * `null`. Kept for backwards compatibility; prefer `ShopwaveApiError`.
64
82
  */
65
83
  declare function getApiErrors(api?: apiResponse): string | null;
66
84
  /**
67
- * GET helper. Sends request metadata via the `extras` header exactly like the
68
- * original hooks did, parses JSON, and returns the typed payload.
85
+ * GET helper. Sends request metadata via the `extras` header, parses JSON and
86
+ * returns the payload. Throws {@link ShopwaveApiError} for non-2xx responses.
69
87
  */
70
88
  declare function apiGet<T>(path: string, extras: Extras, options?: RequestOptions): Promise<T>;
71
89
  /**
72
- * JSON body helper for POST/PUT mutations.
90
+ * JSON body helper for POST/PUT mutations. Returns the parsed body (`null` for
91
+ * an empty one). Throws {@link ShopwaveApiError} for non-2xx responses.
73
92
  */
74
93
  declare function apiSend<T>(path: string, method: "POST" | "PUT", payload: unknown, options?: RequestOptions): Promise<T>;
75
94
  /**
76
- * DELETE helper.
95
+ * DELETE helper. Shopwave answers deletes with 205 and an empty body; if a body
96
+ * does come back and carries API errors, they are thrown. Returns the parsed
97
+ * body (`null` when empty).
98
+ */
99
+ declare function apiDelete(path: string, options?: RequestOptions): Promise<unknown>;
100
+ //#endregion
101
+ //#region src/core/errors.d.ts
102
+ /** Map of Shopwave API errors as found in `api.message.errors`, keyed by error code. */
103
+ type ShopwaveErrorMap = Record<string, Partial<ErrorMessage>>;
104
+ /**
105
+ * Thrown by every `core` function when a request fails, either at the HTTP
106
+ * level (`status` is the HTTP status) or because the Shopwave response carried
107
+ * `api.message.errors` (then `status` is the HTTP status of that response,
108
+ * usually 200/201, and `errors` holds the API errors).
109
+ *
110
+ * Branch on `status` (e.g. `isUnauthorized` → send the user to log in) rather
111
+ * than parsing `message`.
112
+ */
113
+ declare class ShopwaveApiError extends Error {
114
+ /** HTTP status of the response (0 when the request never got a response). */
115
+ readonly status: number;
116
+ /** Shopwave API errors, when the response carried any. */
117
+ readonly errors: ShopwaveErrorMap | null;
118
+ /** Parsed response body, when there was one. */
119
+ readonly body?: unknown;
120
+ constructor(status: number, message: string, details?: {
121
+ errors?: ShopwaveErrorMap | null;
122
+ body?: unknown;
123
+ cause?: unknown;
124
+ });
125
+ /** 401, or the Shopwave "token expired / invalid" error 908. */
126
+ get isUnauthorized(): boolean;
127
+ get isNotFound(): boolean;
128
+ }
129
+ /** The `api.message.errors` (or `api.message.error`) map of a Shopwave response, or `null` when there are none. */
130
+ declare function getApiErrorMap(body: unknown): ShopwaveErrorMap | null;
131
+ /**
132
+ * Builds a {@link ShopwaveApiError} for a response. Understands the Shopwave
133
+ * envelope (`api.message.errors`) and the `{ error, message }` bodies returned
134
+ * by the `react-shopwave-connect/next` routes.
77
135
  */
78
- declare function apiDelete(path: string, options?: RequestOptions): Promise<void>;
136
+ declare function toShopwaveApiError(status: number, body: unknown, fallback?: string): ShopwaveApiError;
137
+ /** Throws a {@link ShopwaveApiError} when the response body carries API errors. */
138
+ declare function assertNoApiErrors(body: unknown, status: number): void;
79
139
  //#endregion
80
140
  //#region src/core/category.d.ts
81
141
  interface Category {
@@ -96,7 +156,7 @@ interface FetchCategoriesParams {
96
156
  categoryIds?: Array<number>;
97
157
  /** Whether to include soft-deleted records. Defaults to `false`. */
98
158
  deleted?: boolean;
99
- /** Token override (also accepted via `options.token`). */
159
+ /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */
100
160
  token?: string;
101
161
  }
102
162
  /**
@@ -159,6 +219,8 @@ interface FetchEmployeesParams {
159
219
  employeeIds?: Array<number>;
160
220
  /** Whether to include soft-deleted records. Defaults to `false`. */
161
221
  deleted?: boolean;
222
+ /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */
223
+ token?: string;
162
224
  }
163
225
  /**
164
226
  * Fetches employees. Returns the flattened `Employee[]`.
@@ -203,7 +265,7 @@ interface FetchProductsParams {
203
265
  storeId?: number;
204
266
  /** Whether to include soft-deleted records. Defaults to `false`. */
205
267
  deleted?: boolean;
206
- /** Token override (also accepted via `options.token`). */
268
+ /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */
207
269
  token?: string;
208
270
  }
209
271
  /**
@@ -265,7 +327,7 @@ interface FetchPromotionsParams {
265
327
  active?: boolean;
266
328
  /** Filter for expired promotions. */
267
329
  expired?: boolean;
268
- /** Token override (also accepted via `options.token`). */
330
+ /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */
269
331
  token?: string;
270
332
  }
271
333
  /**
@@ -297,8 +359,12 @@ interface StoreResponse {
297
359
  api: apiResponse;
298
360
  }
299
361
  interface FetchStoresParams {
362
+ /** Optional id filter. */
363
+ storeIds?: Array<number>;
300
364
  /** Whether to include soft-deleted records. Defaults to `false`. */
301
365
  deleted?: boolean;
366
+ /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */
367
+ token?: string;
302
368
  }
303
369
  /**
304
370
  * Fetches stores. Returns the flattened `Store[]`.
@@ -376,10 +442,12 @@ declare function logout(options?: RequestOptions): Promise<void>;
376
442
  //#endregion
377
443
  //#region src/core/entity.d.ts
378
444
  /**
379
- * Generic CRUD-style mutations extracted from `useHandleDelete` /
380
- * `useHandleSubmit`. The original hooks also surfaced notistack snackbars; that
381
- * UI concern is deliberately left to the hooks/host layer so core stays
382
- * dependency-free.
445
+ * Low-level, untyped mutations: the caller supplies the route and payload.
446
+ *
447
+ * Prefer the typed functions in `./entities` (`saveProduct`, `deleteCategory`,
448
+ * …): they know each route, build the request envelope, check the response and
449
+ * return the saved entity with its id. Keep these for routes that have no typed
450
+ * function yet (e.g. `/api/merchant`).
383
451
  */
384
452
  /**
385
453
  * Deletes `/api/{resourcePath}/{entityId}`.
@@ -398,8 +466,9 @@ interface SubmitEntityResponse<R = any> {
398
466
  [key: string]: unknown;
399
467
  }
400
468
  /**
401
- * Sends a create/update mutation and returns the parsed `result` field
402
- * (matching the original `result.result` access).
469
+ * Sends a create/update mutation. Returns the response's `result` field when it
470
+ * has one (older app routes wrapped their answer that way), otherwise the whole
471
+ * parsed response body. Throws `ShopwaveApiError` on HTTP or API errors.
403
472
  */
404
473
  declare function submitEntity<R = any>({
405
474
  endpoint,
@@ -407,6 +476,128 @@ declare function submitEntity<R = any>({
407
476
  payload
408
477
  }: SubmitEntityArgs, options?: RequestOptions): Promise<R | undefined>;
409
478
  //#endregion
479
+ //#region src/core/entities.d.ts
480
+ /**
481
+ * How each Shopwave entity is addressed, shared by the client functions below
482
+ * and the server route handlers in `react-shopwave-connect/next`, so the two
483
+ * halves of the contract can't drift apart.
484
+ */
485
+ interface EntityDefinition {
486
+ /** App route segment: `/api/<route>` and `/api/<route>/<id>`. */
487
+ route: string;
488
+ /** Key of the entity map in requests and responses (`{ products: { … } }`). */
489
+ collection: string;
490
+ /** Shopwave API path (`GET/POST/DELETE {apiUrl}/<upstream>`). */
491
+ upstream: string;
492
+ /** Header that filters reads by id (`productIds: 1,2`). */
493
+ idsHeader: string;
494
+ /** Header that names the record to delete (`productId: 1`). */
495
+ idHeader: string;
496
+ /** False for read-only entities (the API has no POST for them). */
497
+ writable: boolean;
498
+ /**
499
+ * How a delete is done upstream:
500
+ * - `"delete"`: `DELETE {apiUrl}/<upstream>` with the `idHeader` (205, empty body)
501
+ * - `"retire"`: the API has no DELETE; the record is retired by POSTing
502
+ * `{ id, [retireField]: <now> }` (employees: `exitDate`, promotions: `endDate`)
503
+ * - `"none"`: can't be deleted
504
+ */
505
+ deleteMode: "delete" | "retire" | "none";
506
+ /** Date field set to "now" when `deleteMode` is `"retire"`. */
507
+ retireField?: string;
508
+ }
509
+ type EntityKind = "product" | "category" | "store" | "promotion" | "employee" | "consumer";
510
+ /** Entities that can be created/updated through the API. */
511
+ type WritableEntityKind = Exclude<EntityKind, "consumer">;
512
+ /** Entities that can be deleted (or retired) through the API. */
513
+ type DeletableEntityKind = Exclude<EntityKind, "consumer">;
514
+ /**
515
+ * Addressing per entity, following the Shopwave API reference
516
+ * (https://developer.merchantstack.com/api-reference.html) and checked against
517
+ * the live API where noted:
518
+ * - category/product/store: GET filtered by `<x>Ids`, POST upsert, DELETE with `<x>Id` → 205.
519
+ * - promotion: no DELETE; "deleting" a promotion ends it by setting `endDate` to now.
520
+ * - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
521
+ * changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
522
+ * - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
523
+ */
524
+ declare const SHOPWAVE_ENTITIES: Readonly<Record<EntityKind, EntityDefinition>>;
525
+ interface EntityTypes {
526
+ product: Product;
527
+ category: Category;
528
+ store: Store;
529
+ promotion: Promotion;
530
+ employee: Employee;
531
+ consumer: Consumer;
532
+ }
533
+ /**
534
+ * What a save accepts: any subset of the entity's fields (plus extra API
535
+ * fields the type doesn't list). Include `id` to update; leave it out to create.
536
+ */
537
+ type EntityInput<T> = Partial<T> & {
538
+ [field: string]: unknown;
539
+ };
540
+ /** A saved entity: always has its `id`. */
541
+ type Saved<T> = T & {
542
+ id: number;
543
+ };
544
+ type EntityId = number | string;
545
+ /**
546
+ * Creates or updates several entities of one kind in a single request and
547
+ * returns them as saved (with their ids), in the order given.
548
+ *
549
+ * The request is `{ <collection>: { "0": item0, "1": item1, … } }`. Shopwave
550
+ * answers 201 and echoes every entity under the ref it was sent with, so each
551
+ * result is matched back by ref. Throws {@link ShopwaveApiError} when the
552
+ * response carries `api.message.errors` or any ref is missing from it.
553
+ *
554
+ * The API's partial-success semantics for multi-entity saves haven't been
555
+ * confirmed yet; single saves (`saveProduct` etc.) are the tested path.
556
+ */
557
+ declare function saveEntities<K extends WritableEntityKind>(kind: K, items: ReadonlyArray<EntityInput<EntityTypes[K]>>, options?: RequestOptions): Promise<Array<Saved<EntityTypes[K]>>>;
558
+ /** Creates (no `id`) or updates (with `id`) one entity and returns it with its id. */
559
+ declare function saveEntity<K extends WritableEntityKind>(kind: K, item: EntityInput<EntityTypes[K]>, options?: RequestOptions): Promise<Saved<EntityTypes[K]>>;
560
+ /**
561
+ * Deletes one entity (a soft delete: it stays readable with `deleted: true`).
562
+ * Employees and promotions can't be deleted in Shopwave; for them the route
563
+ * sets `exitDate` / `endDate` to now instead (see `SHOPWAVE_ENTITIES`).
564
+ *
565
+ * Shopwave answers 205 with an empty body — also for ids that don't exist — so
566
+ * a resolved promise means "the API accepted the request", not "a record was
567
+ * deleted".
568
+ */
569
+ declare function deleteEntityById(kind: DeletableEntityKind, id: EntityId, options?: RequestOptions): Promise<void>;
570
+ interface FetchByIdParams {
571
+ /** Also return the record when it has been deleted. Defaults to `false`. */
572
+ deleted?: boolean;
573
+ }
574
+ /** Reads one entity by id, or `null` when there's no such (non-deleted) record. */
575
+ declare function fetchEntityById<K extends EntityKind>(kind: K, id: EntityId, params?: FetchByIdParams, options?: RequestOptions): Promise<EntityTypes[K] | null>;
576
+ declare const saveProduct: (product: EntityInput<Product>, options?: RequestOptions) => Promise<Saved<Product>>;
577
+ declare const deleteProduct: (id: EntityId, options?: RequestOptions) => Promise<void>;
578
+ declare const fetchProduct: (id: EntityId, params?: FetchByIdParams, options?: RequestOptions) => Promise<Product | null>;
579
+ declare const saveCategory: (category: EntityInput<Category>, options?: RequestOptions) => Promise<Saved<Category>>;
580
+ declare const deleteCategory: (id: EntityId, options?: RequestOptions) => Promise<void>;
581
+ declare const fetchCategory: (id: EntityId, params?: FetchByIdParams, options?: RequestOptions) => Promise<Category | null>;
582
+ declare const saveStore: (store: EntityInput<Store>, options?: RequestOptions) => Promise<Saved<Store>>;
583
+ declare const deleteStore: (id: EntityId, options?: RequestOptions) => Promise<void>;
584
+ declare const fetchStore: (id: EntityId, params?: FetchByIdParams, options?: RequestOptions) => Promise<Store | null>;
585
+ declare const savePromotion: (promotion: EntityInput<Promotion>, options?: RequestOptions) => Promise<Saved<Promotion>>;
586
+ /** Ends a promotion: Shopwave has no promotion DELETE, so this sets `endDate` to now. */
587
+ declare const deletePromotion: (id: EntityId, options?: RequestOptions) => Promise<void>;
588
+ declare const fetchPromotion: (id: EntityId, params?: FetchByIdParams, options?: RequestOptions) => Promise<Promotion | null>;
589
+ /**
590
+ * Creates an employee, or updates one (with `id`). On update Shopwave only
591
+ * applies `roleId`, `joinedDate` and `exitDate` (and store roles); names and
592
+ * email can't be changed, and the echo only carries the updated fields — re-read
593
+ * with `fetchEmployee` if you need the stored record.
594
+ */
595
+ declare const saveEmployee: (employee: EntityInput<Employee>, options?: RequestOptions) => Promise<Saved<Employee>>;
596
+ /** Retires an employee: Shopwave has no employee DELETE, so this sets `exitDate` to now. */
597
+ declare const deleteEmployee: (id: EntityId, options?: RequestOptions) => Promise<void>;
598
+ declare const fetchEmployee: (id: EntityId, params?: FetchByIdParams, options?: RequestOptions) => Promise<Employee | null>;
599
+ declare const fetchConsumer: (id: EntityId, params?: FetchByIdParams, options?: RequestOptions) => Promise<Consumer | null>;
600
+ //#endregion
410
601
  //#region src/core/basket.d.ts
411
602
  interface BasketItem {
412
603
  productId: number;
@@ -505,5 +696,395 @@ declare function buildConsumerMap(consumers: Consumer[] | null | undefined): {
505
696
  [id: number]: Consumer;
506
697
  };
507
698
  //#endregion
508
- export { Basket, BasketFilters, BasketItem, BasketPagination, BasketReportRow, BasketSummary, Category, CategoryResponse, CombineBasketRowsParams, Consumer, ConsumerResponse, DEFAULT_BASKET_FILTERS, DEFAULT_PAGINATION, Employee, EmployeeResponse, ErrorMessage, Extras, FetchCategoriesParams, FetchEmployeesParams, FetchProductsParams, FetchPromotionsParams, FetchStoresParams, MessageInfo, ParsedBasket, Product, ProductInstance, ProductResponse, Promotion, PromotionResponse, PromotionRuleSet, ReportData, ReportKey, ReportQuery, ReportQueryMap, ReportResponse, RequestOptions, SessionStatus, Store, StoreResponse, SubmitEntityArgs, SubmitEntityResponse, SuccessMessage, apiDelete, apiGet, apiResponse, apiSend, buildBasketReportQuery, buildConsumerMap, collectConsumerIds, collectProductIds, combineBasketRows, computeBasketSummary, deleteEntity, fetchCategories, fetchConsumers, fetchEmployees, fetchProducts, fetchProductsMap, fetchPromotions, fetchReport, fetchSession, fetchStores, formatDateForQuery, getApiErrors, loginPath, logout, logoutPath, parseBasketReportData, rolesById, submitEntity };
699
+ //#region src/core/resources.d.ts
700
+ /**
701
+ * Singleton resources (one per logged-in user) and the uploader.
702
+ *
703
+ * Like `SHOPWAVE_ENTITIES`, this table is shared by the client functions
704
+ * (`fetchMerchant`, `fetchUser`, `uploadImage`, …) and the server route
705
+ * handlers in `react-shopwave-connect/next`, so the two halves of the contract
706
+ * can't drift apart. These aren't in `SHOPWAVE_ENTITIES` because they aren't
707
+ * keyed collections: `GET /merchant` answers `{ merchant: {…} }`, not a map by id.
708
+ */
709
+ interface ResourceDefinition {
710
+ /** App route: `/api/<route>`. */
711
+ route: string;
712
+ /** Key of the object in requests and responses (`{ merchant: {…} }`). */
713
+ key: string;
714
+ /** Shopwave API path. */
715
+ upstream: string;
716
+ /** False for read-only resources. */
717
+ writable: boolean;
718
+ }
719
+ type ResourceKind = "merchant" | "user";
720
+ /**
721
+ * - merchant: `GET /merchant`; `POST /merchant` with `{ merchant: { id, … } }`
722
+ * updates it (without an `id` the API would create a second merchant, so the
723
+ * route requires one).
724
+ * - user: `GET /user` (read-only here; the API's `POST /user` invites users and
725
+ * needs the admin scope).
726
+ */
727
+ declare const SHOPWAVE_RESOURCES: Readonly<Record<ResourceKind, ResourceDefinition>>;
728
+ /**
729
+ * What an upload is for. Sent to Shopwave as the `contentType` header of
730
+ * `PUT /uploader`. `merchant` is for the receipt and square logos (stored
731
+ * under `…/merchant/<id>/logo/`), `merchantFeature` for the featured image.
732
+ * The API spells two of its values `applicaionLogo` / `applicaionImages`; use
733
+ * the correctly spelt names here and the server maps them.
734
+ */
735
+ type UploadKind = "merchant" | "merchantFeature" | "product" | "user" | "applicationLogo" | "applicationImages";
736
+ /** `UploadKind` → the API's `contentType` value. */
737
+ declare const UPLOAD_KINDS: Readonly<Record<UploadKind, string>>;
738
+ /** How uploads are addressed (app route and Shopwave uploader). */
739
+ declare const SHOPWAVE_UPLOAD: Readonly<{
740
+ /** App route: `POST /api/<route>` (multipart `file` + `kind`). */
741
+ route: "upload";
742
+ /** Shopwave path: `PUT {apiUrl}/uploader` (multipart `file`). */
743
+ upstream: "uploader";
744
+ /** Header that carries the upload kind upstream. */
745
+ kindHeader: "contentType";
746
+ /** Multipart field name for the file, on both sides. */
747
+ fileField: "file";
748
+ /** Multipart field name for the kind in the app request. */
749
+ kindField: "kind";
750
+ }>;
751
+ declare function isUploadKind(value: unknown): value is UploadKind;
752
+ //#endregion
753
+ //#region src/core/imageFit.d.ts
754
+ /**
755
+ * Browser helpers for getting an image to an exact pixel size before upload.
756
+ *
757
+ * Shopwave doesn't resize uploads, and the merchant image slots have fixed
758
+ * sizes (adminV1 refuses any other size), so the SDK scales and centre-crops
759
+ * the image in the browser instead.
760
+ */
761
+ /** Source rectangle to draw so the image covers `dstWidth × dstHeight` (centre crop, no stretching). */
762
+ interface CropRect {
763
+ sx: number;
764
+ sy: number;
765
+ sw: number;
766
+ sh: number;
767
+ }
768
+ /**
769
+ * The centred part of a `srcWidth × srcHeight` image with the aspect ratio of
770
+ * `dstWidth × dstHeight` (like CSS `object-fit: cover`).
771
+ */
772
+ declare function coverCrop(srcWidth: number, srcHeight: number, dstWidth: number, dstHeight: number): CropRect;
773
+ interface ImageSize {
774
+ width: number;
775
+ height: number;
776
+ }
777
+ /** Reads an image file's pixel size (browser only). */
778
+ declare function readImageSize(file: Blob): Promise<ImageSize>;
779
+ interface FitImageOptions {
780
+ /** Output type. Default `"image/png"`. */
781
+ type?: "image/png" | "image/jpeg" | "image/webp";
782
+ /** 0–1, for JPEG/WebP. */
783
+ quality?: number;
784
+ /** Return the file untouched when it's already the right size and type. Default true. */
785
+ keepIfExact?: boolean;
786
+ }
787
+ /**
788
+ * Returns the image at exactly `width × height`: scaled and centre-cropped
789
+ * (never stretched). Browser only. The result is a `File` named like the
790
+ * input, with the output type's extension.
791
+ */
792
+ declare function fitImageToSize(file: Blob, width: number, height: number, options?: FitImageOptions): Promise<File>;
793
+ //#endregion
794
+ //#region src/core/upload.d.ts
795
+ interface UploadImageParams {
796
+ /** What the image is for (sent to Shopwave as the uploader's `contentType`). */
797
+ kind: UploadKind;
798
+ /** File name to send when `file` is a Blob without one. Defaults to `File.name`, else `"upload"`. */
799
+ fileName?: string;
800
+ }
801
+ interface UploadedImage {
802
+ /** The generated file name (products store it in `images`). */
803
+ id: string;
804
+ /** Public HTTPS URL of the file (see {@link getImageUrl}). */
805
+ url: string;
806
+ /** The URL exactly as the API returned it (`http://static.merchantstack.com/images/…`). */
807
+ path: string;
808
+ }
809
+ /**
810
+ * HTTPS host that serves the Shopwave CDN's images. The API returns
811
+ * `http://static.merchantstack.com/images/…`, which has no HTTPS endpoint
812
+ * (checked Sep 2026), so pages served over HTTPS can't show it.
813
+ */
814
+ declare const SHOPWAVE_IMAGE_BASE_URL = "https://s3-eu-west-1.amazonaws.com/static.merchantstack.com/images";
815
+ /**
816
+ * Turns an image path from the API into a URL a page can load over HTTPS:
817
+ * `http://static.merchantstack.com/images/x.png` → `<base>/x.png`, and a
818
+ * relative path (`merchant/5644/logo/x.png`) → `<base>/merchant/5644/logo/x.png`.
819
+ * Other absolute URLs are returned unchanged; empty input gives `""`.
820
+ */
821
+ declare function getImageUrl(path: string | null | undefined, base?: string): string;
822
+ /**
823
+ * Uploads one image to the Shopwave CDN through the app's `/api/upload` route
824
+ * (`createShopwaveApi(...).upload()`), and returns its id and URL.
825
+ *
826
+ * The file only lands on the CDN (under `…/images/<kind>/<merchantId>/…`); a
827
+ * record has to point at it to use it. Live API (Sep 2026): the upload answers
828
+ * 201 `{ fileName, path }`.
829
+ */
830
+ declare function uploadImage(file: Blob, params: UploadImageParams, options?: RequestOptions): Promise<UploadedImage>;
831
+ //#endregion
832
+ //#region src/core/merchantImages.d.ts
833
+ /**
834
+ * Merchant images, as adminV1 saves them and adminV2 reads them:
835
+ *
836
+ * ```
837
+ * { logo: { receipt: "<file>", square: "<file>" }, feature: ["<file>"] }
838
+ * ```
839
+ *
840
+ * - **Writes** take the uploader's `fileName` (`<hash>.png`), not a URL.
841
+ * - **Reads** return full URLs (`http://static.merchantstack.com/images/…`),
842
+ * so a value read back must be turned into a file name before it's saved
843
+ * again ({@link toStoredImageIds}; `updateMerchant` does this for you).
844
+ * - Sending `imageIds` replaces the whole stored object (`{}` clears it);
845
+ * leaving it out keeps it.
846
+ * - The API builds each URL from the key, not from where the file is:
847
+ * `logo.*` → `…/merchant/<id>/logo/<file>`, `feature` → `…/feature/<file>`.
848
+ * So a featured image must be uploaded with kind `merchantFeature` (which
849
+ * stores it under `feature/`); a `merchant` upload lands under `logo/`.
850
+ *
851
+ * Confirmed on the live API, merchant 5644, 30 Sep 2026.
852
+ */
853
+ interface MerchantImageIds {
854
+ logo?: {
855
+ receipt?: string | null;
856
+ square?: string | null;
857
+ [key: string]: string | null | undefined;
858
+ } | null;
859
+ feature?: string[] | null;
860
+ [key: string]: unknown;
861
+ }
862
+ /** The three image slots on the merchant settings page. */
863
+ declare const MERCHANT_IMAGE_SLOT_KEYS: readonly ["receipt", "square", "featured"];
864
+ type MerchantImageSlot = (typeof MERCHANT_IMAGE_SLOT_KEYS)[number];
865
+ interface MerchantImageSlotDefinition {
866
+ /** Where it's stored: `logo.receipt`, `logo.square` or `feature[0]`. */
867
+ field: "logo.receipt" | "logo.square" | "feature[0]";
868
+ /** Uploader `contentType` (the featured image has its own). */
869
+ uploadKind: UploadKind;
870
+ /** Exact size in pixels. adminV1 refuses any other size; the API doesn't resize. */
871
+ width: number;
872
+ height: number;
873
+ }
874
+ /**
875
+ * Where each slot lives, its upload kind and its exact size (from adminV1's
876
+ * merchant settings page).
877
+ */
878
+ declare const MERCHANT_IMAGE_SLOTS: Readonly<Record<MerchantImageSlot, MerchantImageSlotDefinition>>;
879
+ /**
880
+ * The file name in an image value: `"http://…/logo/abc.png"` → `"abc.png"`,
881
+ * `"abc.png"` → `"abc.png"`. Query strings and fragments are ignored.
882
+ * Returns `null` for empty or non-string values.
883
+ */
884
+ declare function imageFileName(value: unknown): string | null;
885
+ /** The stored value for a slot (a URL when read from the API; a file name right after an upload). */
886
+ declare function getMerchantImage(imageIds: MerchantImageIds | null | undefined, slot: MerchantImageSlot): string | null;
887
+ /**
888
+ * An HTTPS URL for a slot's stored image, or `""` when the slot is empty or
889
+ * holds only a file name (a fresh upload not yet saved; use the upload's `url`).
890
+ */
891
+ declare function getMerchantImageUrl(imageIds: MerchantImageIds | null | undefined, slot: MerchantImageSlot, base?: string): string;
892
+ /**
893
+ * Returns a copy of `imageIds` with one slot set to `fileName` (or cleared
894
+ * with `null`). Other slots and unknown keys are kept; an empty `logo` or
895
+ * `feature` is removed.
896
+ */
897
+ declare function setMerchantImage(imageIds: MerchantImageIds | null | undefined, slot: MerchantImageSlot, fileName: string | null): MerchantImageIds;
898
+ /**
899
+ * The `imageIds` to send on a save: every URL is turned back into its file
900
+ * name (what adminV1 does with images you didn't change) and empty values
901
+ * are dropped. No images gives `{}`.
902
+ */
903
+ declare function toStoredImageIds(imageIds: unknown): MerchantImageIds;
904
+ interface UploadMerchantImageOptions extends RequestOptions {
905
+ /**
906
+ * Scale and centre-crop the image to the slot's exact size before upload
907
+ * (browser only). Default true. With `false` the file is sent as-is.
908
+ */
909
+ fit?: boolean;
910
+ /** Output type and quality when fitting. Default PNG. */
911
+ fitOptions?: FitImageOptions;
912
+ }
913
+ /**
914
+ * Uploads an image for a merchant slot: fits it to the slot's size, uploads
915
+ * it with the slot's kind, and returns `{ id, url, path }`. Put `id` into
916
+ * `imageIds` with {@link setMerchantImage} and save with `updateMerchant`.
917
+ */
918
+ declare function uploadMerchantImage(file: Blob, slot: MerchantImageSlot, options?: UploadMerchantImageOptions): Promise<UploadedImage>;
919
+ //#endregion
920
+ //#region src/core/merchant.d.ts
921
+ /** A colour set: hex strings (`"#1a2b3c"`). */
922
+ interface MerchantColourSet {
923
+ main?: string | null;
924
+ highlight?: string | null;
925
+ contrast?: string | null;
926
+ [key: string]: string | null | undefined;
927
+ }
928
+ /**
929
+ * Brand colours, as adminV1 saves them and adminV2 reads them
930
+ * (`colours.primary.main` is the logo background): `{ primary: { main,
931
+ * highlight, contrast } }`. `{}` when unset. Confirmed live, 30 Sep 2026.
932
+ */
933
+ interface MerchantColours {
934
+ primary?: MerchantColourSet | null;
935
+ [key: string]: MerchantColourSet | null | undefined;
936
+ }
937
+ /** Colour keys inside `colours.primary`, in display order. */
938
+ declare const MERCHANT_COLOUR_KEYS: readonly ["main", "highlight", "contrast"];
939
+ type MerchantColourKey = (typeof MERCHANT_COLOUR_KEYS)[number];
940
+ /**
941
+ * Web and social links, as adminV1 saves them:
942
+ * `{ website: { home }, social: { twitter, facebook, instagram } }`.
943
+ * `{}` when empty (confirmed live, 30 Sep 2026). Use {@link normalizeMerchantLinks} to read older shapes and
944
+ * {@link merchantLinksToForm} / {@link merchantLinksFromForm} for flat forms.
945
+ */
946
+ interface MerchantLinks {
947
+ website?: {
948
+ home?: string | null;
949
+ [key: string]: string | null | undefined;
950
+ } | null;
951
+ social?: {
952
+ twitter?: string | null;
953
+ facebook?: string | null;
954
+ instagram?: string | null;
955
+ [key: string]: string | null | undefined;
956
+ } | null;
957
+ [key: string]: unknown;
958
+ }
959
+ /** Flat link fields a form edits, in display order. */
960
+ declare const MERCHANT_LINK_KEYS: readonly ["website", "twitter", "facebook", "instagram"];
961
+ type MerchantLinkKey = (typeof MERCHANT_LINK_KEYS)[number];
962
+ /** Where each flat link field lives in `merchant.links`. */
963
+ declare const MERCHANT_LINK_PATHS: Readonly<Record<MerchantLinkKey, readonly [string, string]>>;
964
+ /**
965
+ * Reads `merchant.links` as flat fields (`{ website, twitter, facebook,
966
+ * instagram }`), whatever shape it was saved in:
967
+ * - the nested shape adminV1 saves (`{ website: { home }, social: { … } }`);
968
+ * - flat keys (`{ website: "…" }`, what SDK 0.4.0 pre-releases sent);
969
+ * - an array in {@link MERCHANT_LINK_KEYS} order (older AdminUI versions).
970
+ * Empty values are dropped.
971
+ */
972
+ declare function merchantLinksToForm(links: unknown): Partial<Record<MerchantLinkKey, string>>;
973
+ /**
974
+ * Builds the stored `links` object from flat fields. Empty values are left
975
+ * out, and so is a group with nothing in it; no links gives `{}`.
976
+ */
977
+ declare function merchantLinksFromForm(form: Partial<Record<MerchantLinkKey, string | null | undefined>>): MerchantLinks;
978
+ /** Reads any stored or older `links` value into the nested shape adminV1 saves. */
979
+ declare function normalizeMerchantLinks(links: unknown): MerchantLinks;
980
+ interface Merchant {
981
+ id: number;
982
+ name: string;
983
+ description?: string | null;
984
+ /** Company registration number. */
985
+ companyNumber?: string | null;
986
+ vatNumber?: string | null;
987
+ categoryId?: number | null;
988
+ estAnnualRevenue?: number | string | null;
989
+ note?: string | null;
990
+ links?: MerchantLinks | null;
991
+ colours?: MerchantColours | null;
992
+ imageIds?: MerchantImageIds | null;
993
+ createdDate?: string;
994
+ modifiedDate?: string;
995
+ /** Other fields the API returns are kept as-is. */
996
+ [field: string]: unknown;
997
+ }
998
+ interface MerchantResponse {
999
+ merchant: Merchant;
1000
+ api: apiResponse;
1001
+ }
1002
+ /** What `updateMerchant` accepts: any subset of the merchant's fields. */
1003
+ type MerchantPatch = Partial<Merchant> & {
1004
+ [field: string]: unknown;
1005
+ };
1006
+ /**
1007
+ * Object fields of the merchant. The API keeps one you leave out, and
1008
+ * replaces it as a whole when you send it.
1009
+ */
1010
+ declare const MERCHANT_OBJECT_FIELDS: ReadonlyArray<string>;
1011
+ /** Fields the API sets itself; `updateMerchant` never sends them back. */
1012
+ declare const MERCHANT_READ_ONLY_FIELDS: ReadonlyArray<string>;
1013
+ /**
1014
+ * Longest values the API stores. Longer values are cut off silently
1015
+ * (live API, Sep 2026), so `updateMerchant` rejects them instead.
1016
+ * `vatNumber` fits a UK VAT number without the `GB` prefix.
1017
+ */
1018
+ declare const MERCHANT_FIELD_MAX_LENGTH: Readonly<Record<string, number>>;
1019
+ /** Returns `"<field> is longer than <n> characters"` messages for over-long fields. */
1020
+ declare function validateMerchant(merchant: Record<string, unknown>): string[];
1021
+ /**
1022
+ * Reads the logged-in user's merchant (a user has exactly one), or `null`
1023
+ * when the API returns none.
1024
+ */
1025
+ declare function fetchMerchant(options?: RequestOptions): Promise<Merchant | null>;
1026
+ interface UpdateMerchantOptions extends RequestOptions {
1027
+ /**
1028
+ * Read the merchant first and send the patch merged over it (the default).
1029
+ * Scalar fields are merged over the stored ones. Object fields
1030
+ * (`colours`, `links`, `imageIds`) are sent only when the patch has them,
1031
+ * and then replace the stored one as a whole.
1032
+ *
1033
+ * Set to `false` to send only the patch (it must then include `id`). Only
1034
+ * do that if you know the API keeps the fields you leave out.
1035
+ */
1036
+ merge?: boolean;
1037
+ }
1038
+ /**
1039
+ * Updates the merchant and returns it as stored.
1040
+ *
1041
+ * Shopwave's `POST /merchant` replaces the whole record: a scalar field you
1042
+ * leave out (`companyNumber`, `vatNumber`, `note`, …) is set to null, while
1043
+ * an object field you leave out (`colours`, `links`, `imageIds`) is kept and
1044
+ * one you send replaces the stored one; sending `{}` clears it (live API,
1045
+ * Sep 2026). So by default the
1046
+ * current merchant is read first and the patch is merged over it.
1047
+ *
1048
+ * `imageIds` is always sent as file names ({@link toStoredImageIds}), so you
1049
+ * can pass back what you read. See `MerchantImageIds` for the shape.
1050
+ *
1051
+ * The API answers 205 with an empty body, so the merchant is read again after
1052
+ * the save and that is what's returned (it shows exactly what was kept).
1053
+ * Throws before sending when a value is longer than the API stores
1054
+ * ({@link MERCHANT_FIELD_MAX_LENGTH}).
1055
+ */
1056
+ declare function updateMerchant(patch: MerchantPatch, options?: UpdateMerchantOptions): Promise<Merchant>;
1057
+ //#endregion
1058
+ //#region src/core/user.d.ts
1059
+ /** The logged-in user's employee record (`GET /user` → `user.employee`). */
1060
+ interface UserEmployee {
1061
+ merchantId?: number;
1062
+ /** See `rolesById`. */
1063
+ roleId?: number;
1064
+ /** Store roles, keyed by store id. */
1065
+ stores?: Record<string, unknown>;
1066
+ [field: string]: unknown;
1067
+ }
1068
+ /** The logged-in user (`GET /user`). */
1069
+ interface User {
1070
+ id: number;
1071
+ firstName: string;
1072
+ lastName: string;
1073
+ email: string;
1074
+ employee?: UserEmployee | null;
1075
+ /** Documented in the API reference but not returned by the live API (Sep 2026). */
1076
+ createdDate?: string;
1077
+ /** Not returned by the live API (Sep 2026); use `fetchMerchant()`. */
1078
+ merchant?: Merchant | null;
1079
+ /** Other fields the API returns are kept as-is. */
1080
+ [field: string]: unknown;
1081
+ }
1082
+ interface UserResponse {
1083
+ user: User;
1084
+ api: apiResponse;
1085
+ }
1086
+ /** Reads the logged-in user, or `null` when the API returns none. */
1087
+ declare function fetchUser(options?: RequestOptions): Promise<User | null>;
1088
+ //#endregion
1089
+ export { Basket, BasketFilters, BasketItem, BasketPagination, BasketReportRow, BasketSummary, Category, CategoryResponse, CombineBasketRowsParams, Consumer, ConsumerResponse, CropRect, DEFAULT_BASKET_FILTERS, DEFAULT_PAGINATION, DeletableEntityKind, Employee, EmployeeResponse, EntityDefinition, EntityId, EntityInput, EntityKind, EntityTypes, ErrorMessage, Extras, FetchByIdParams, FetchCategoriesParams, FetchEmployeesParams, FetchProductsParams, FetchPromotionsParams, FetchStoresParams, FitImageOptions, ImageSize, MERCHANT_COLOUR_KEYS, MERCHANT_FIELD_MAX_LENGTH, MERCHANT_IMAGE_SLOTS, MERCHANT_IMAGE_SLOT_KEYS, MERCHANT_LINK_KEYS, MERCHANT_LINK_PATHS, MERCHANT_OBJECT_FIELDS, MERCHANT_READ_ONLY_FIELDS, Merchant, MerchantColourKey, MerchantColourSet, MerchantColours, MerchantImageIds, MerchantImageSlot, MerchantImageSlotDefinition, MerchantLinkKey, MerchantLinks, MerchantPatch, MerchantResponse, MessageInfo, ParsedBasket, Product, ProductInstance, ProductResponse, Promotion, PromotionResponse, PromotionRuleSet, ReportData, ReportKey, ReportQuery, ReportQueryMap, ReportResponse, RequestOptions, ResourceDefinition, ResourceKind, SHOPWAVE_ENTITIES, SHOPWAVE_IMAGE_BASE_URL, SHOPWAVE_RESOURCES, SHOPWAVE_UPLOAD, Saved, SessionStatus, ShopwaveApiError, ShopwaveErrorMap, Store, StoreResponse, SubmitEntityArgs, SubmitEntityResponse, SuccessMessage, UPLOAD_KINDS, UpdateMerchantOptions, UploadImageParams, UploadKind, UploadMerchantImageOptions, UploadedImage, User, UserEmployee, UserResponse, WritableEntityKind, apiDelete, apiGet, apiRequest, apiResponse, apiSend, assertNoApiErrors, authorizationFor, buildBasketReportQuery, buildConsumerMap, collectConsumerIds, collectProductIds, combineBasketRows, computeBasketSummary, coverCrop, deleteCategory, deleteEmployee, deleteEntity, deleteEntityById, deleteProduct, deletePromotion, deleteStore, fetchCategories, fetchCategory, fetchConsumer, fetchConsumers, fetchEmployee, fetchEmployees, fetchEntityById, fetchMerchant, fetchProduct, fetchProducts, fetchProductsMap, fetchPromotion, fetchPromotions, fetchReport, fetchSession, fetchStore, fetchStores, fetchUser, fitImageToSize, formatDateForQuery, getApiErrorMap, getApiErrors, getImageUrl, getMerchantImage, getMerchantImageUrl, imageFileName, isUploadKind, loginPath, logout, logoutPath, merchantLinksFromForm, merchantLinksToForm, normalizeMerchantLinks, parseBasketReportData, readImageSize, rolesById, saveCategory, saveEmployee, saveEntities, saveEntity, saveProduct, savePromotion, saveStore, setMerchantImage, submitEntity, toShopwaveApiError, toStoredImageIds, updateMerchant, uploadImage, uploadMerchantImage, validateMerchant, withToken };
509
1090
  //# sourceMappingURL=index.d.ts.map