react-shopwave-connect 0.1.2 → 0.3.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.
- package/README.md +281 -39
- package/dist/core/index.cjs +435 -93
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +241 -28
- package/dist/core/index.d.ts +241 -28
- package/dist/core/index.js +405 -94
- package/dist/core/index.js.map +1 -1
- package/dist/hooks/index.cjs +328 -116
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.cts +57 -12
- package/dist/hooks/index.d.ts +57 -12
- package/dist/hooks/index.js +329 -118
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.cjs +553 -112
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +265 -29
- package/dist/index.d.ts +265 -29
- package/dist/index.js +523 -114
- package/dist/index.js.map +1 -1
- package/dist/next/index.cjs +1019 -0
- package/dist/next/index.cjs.map +1 -0
- package/dist/next/index.d.cts +423 -0
- package/dist/next/index.d.ts +423 -0
- package/dist/next/index.js +987 -0
- package/dist/next/index.js.map +1 -0
- package/dist/server/index.cjs +670 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.cts +346 -0
- package/dist/server/index.d.ts +346 -0
- package/dist/server/index.js +650 -0
- package/dist/server/index.js.map +1 -0
- package/package.json +41 -8
package/dist/index.cjs
CHANGED
|
@@ -24,89 +24,202 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
let react = require("react");
|
|
25
25
|
react = __toESM(react);
|
|
26
26
|
|
|
27
|
+
//#region src/core/errors.ts
|
|
28
|
+
/**
|
|
29
|
+
* Thrown by every `core` function when a request fails, either at the HTTP
|
|
30
|
+
* level (`status` is the HTTP status) or because the Shopwave response carried
|
|
31
|
+
* `api.message.errors` (then `status` is the HTTP status of that response,
|
|
32
|
+
* usually 200/201, and `errors` holds the API errors).
|
|
33
|
+
*
|
|
34
|
+
* Branch on `status` (e.g. `isUnauthorized` → send the user to log in) rather
|
|
35
|
+
* than parsing `message`.
|
|
36
|
+
*/
|
|
37
|
+
var ShopwaveApiError = class extends Error {
|
|
38
|
+
/** HTTP status of the response (0 when the request never got a response). */
|
|
39
|
+
status;
|
|
40
|
+
/** Shopwave API errors, when the response carried any. */
|
|
41
|
+
errors;
|
|
42
|
+
/** Parsed response body, when there was one. */
|
|
43
|
+
body;
|
|
44
|
+
constructor(status, message, details = {}) {
|
|
45
|
+
super(message, details.cause !== void 0 ? { cause: details.cause } : void 0);
|
|
46
|
+
this.name = "ShopwaveApiError";
|
|
47
|
+
this.status = status;
|
|
48
|
+
this.errors = details.errors ?? null;
|
|
49
|
+
this.body = details.body;
|
|
50
|
+
}
|
|
51
|
+
/** 401, or the Shopwave "token expired / invalid" error 908. */
|
|
52
|
+
get isUnauthorized() {
|
|
53
|
+
if (this.status === 401) return true;
|
|
54
|
+
return !!this.errors && Object.entries(this.errors).some(([k, v]) => k === "908" || Number(v?.id) === 908);
|
|
55
|
+
}
|
|
56
|
+
get isNotFound() {
|
|
57
|
+
return this.status === 404;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
/** The `api.message.errors` (or `api.message.error`) map of a Shopwave response, or `null` when there are none. */
|
|
61
|
+
function getApiErrorMap(body) {
|
|
62
|
+
const message = body?.api?.message;
|
|
63
|
+
const errors = message?.errors ?? message?.error;
|
|
64
|
+
if (errors && typeof errors === "object" && Object.keys(errors).length > 0) return errors;
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
function describeErrors(errors) {
|
|
68
|
+
return Object.entries(errors).map(([code, e]) => {
|
|
69
|
+
const text = [e?.title, e?.details].filter(Boolean).join(": ");
|
|
70
|
+
return text ? `${code} ${text}` : code;
|
|
71
|
+
}).join("; ");
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Builds a {@link ShopwaveApiError} for a response. Understands the Shopwave
|
|
75
|
+
* envelope (`api.message.errors`) and the `{ error, message }` bodies returned
|
|
76
|
+
* by the `react-shopwave-connect/next` routes.
|
|
77
|
+
*/
|
|
78
|
+
function toShopwaveApiError(status, body, fallback) {
|
|
79
|
+
const errors = getApiErrorMap(body);
|
|
80
|
+
let detail = errors ? describeErrors(errors) : "";
|
|
81
|
+
if (!detail && body && typeof body === "object") {
|
|
82
|
+
const b = body;
|
|
83
|
+
if (typeof b.message === "string") detail = b.message;
|
|
84
|
+
else if (typeof b.error === "string") detail = b.error;
|
|
85
|
+
}
|
|
86
|
+
if (!detail) detail = fallback ?? (status >= 400 ? "request failed" : "API returned errors");
|
|
87
|
+
return new ShopwaveApiError(status, `Shopwave API error (${status}): ${detail}`, {
|
|
88
|
+
errors,
|
|
89
|
+
body
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/** Throws a {@link ShopwaveApiError} when the response body carries API errors. */
|
|
93
|
+
function assertNoApiErrors(body, status) {
|
|
94
|
+
if (getApiErrorMap(body)) throw toShopwaveApiError(status, body);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
//#endregion
|
|
27
98
|
//#region src/core/request.ts
|
|
28
99
|
function resolveFetch(options) {
|
|
29
100
|
const f = options.fetch ?? (typeof fetch !== "undefined" ? fetch : void 0);
|
|
30
101
|
if (!f) throw new Error("No fetch implementation available. Pass `options.fetch` (e.g. node-fetch / undici) when running outside the browser.");
|
|
31
102
|
return f;
|
|
32
103
|
}
|
|
33
|
-
|
|
104
|
+
/** Options with `token` filled in from a legacy per-call `params.token`, unless `options.token` is set. */
|
|
105
|
+
function withToken(options, token) {
|
|
106
|
+
return token && !options.token ? {
|
|
107
|
+
...options,
|
|
108
|
+
token
|
|
109
|
+
} : options;
|
|
110
|
+
}
|
|
111
|
+
/** `Authorization` header value for a token (`OAuth <token>` unless it already has a scheme). */
|
|
112
|
+
function authorizationFor(token) {
|
|
113
|
+
const t = token.trim();
|
|
114
|
+
return /^(oauth|bearer)\s+\S/i.test(t) ? t : `OAuth ${t}`;
|
|
115
|
+
}
|
|
116
|
+
/** Headers shared by every request: Accept + the token, when one is given. */
|
|
117
|
+
function baseHeaders(options) {
|
|
118
|
+
const headers = { Accept: "application/json" };
|
|
119
|
+
if (options.token) headers.Authorization = authorizationFor(options.token);
|
|
120
|
+
return headers;
|
|
121
|
+
}
|
|
122
|
+
function buildExtras(extras) {
|
|
34
123
|
const merged = {
|
|
35
124
|
"Content-Type": "application/json",
|
|
36
125
|
...extras
|
|
37
126
|
};
|
|
38
|
-
|
|
127
|
+
delete merged.token;
|
|
39
128
|
return merged;
|
|
40
129
|
}
|
|
130
|
+
/** Parses a response body as JSON; `null` for an empty body, `{ raw }` for non-JSON. */
|
|
131
|
+
async function readBody(response) {
|
|
132
|
+
const text = await response.text();
|
|
133
|
+
if (!text) return null;
|
|
134
|
+
try {
|
|
135
|
+
return JSON.parse(text);
|
|
136
|
+
} catch {
|
|
137
|
+
return { raw: text };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Low-level request: resolves `{ status, body }` for 2xx responses and throws
|
|
142
|
+
* {@link ShopwaveApiError} otherwise. `init.headers` are merged over the
|
|
143
|
+
* standard ones (Accept + Authorization).
|
|
144
|
+
*/
|
|
145
|
+
async function apiRequest(path, init, options = {}) {
|
|
146
|
+
return send(path, {
|
|
147
|
+
...init,
|
|
148
|
+
headers: {
|
|
149
|
+
...baseHeaders(options),
|
|
150
|
+
...init.headers
|
|
151
|
+
}
|
|
152
|
+
}, options);
|
|
153
|
+
}
|
|
154
|
+
async function send(path, init, options) {
|
|
155
|
+
const doFetch = resolveFetch(options);
|
|
156
|
+
const url = (options.baseUrl ?? "") + path;
|
|
157
|
+
let response;
|
|
158
|
+
try {
|
|
159
|
+
response = await doFetch(url, {
|
|
160
|
+
...init,
|
|
161
|
+
signal: options.signal
|
|
162
|
+
});
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (error?.name === "AbortError") throw error;
|
|
165
|
+
throw new ShopwaveApiError(0, `Shopwave API request failed: ${error?.message ?? error}`, { cause: error });
|
|
166
|
+
}
|
|
167
|
+
const body = await readBody(response);
|
|
168
|
+
if (!response.ok) throw toShopwaveApiError(response.status, body, response.statusText || void 0);
|
|
169
|
+
return {
|
|
170
|
+
status: response.status,
|
|
171
|
+
body
|
|
172
|
+
};
|
|
173
|
+
}
|
|
41
174
|
/**
|
|
42
175
|
* Returns a JSON string of API errors if the response carries any, otherwise
|
|
43
|
-
* `null`.
|
|
176
|
+
* `null`. Kept for backwards compatibility; prefer `ShopwaveApiError`.
|
|
44
177
|
*/
|
|
45
178
|
function getApiErrors(api) {
|
|
46
|
-
const errors = api
|
|
47
|
-
|
|
48
|
-
return null;
|
|
179
|
+
const errors = getApiErrorMap({ api });
|
|
180
|
+
return errors ? JSON.stringify(errors) : null;
|
|
49
181
|
}
|
|
50
182
|
/**
|
|
51
|
-
* GET helper. Sends request metadata via the `extras` header
|
|
52
|
-
*
|
|
183
|
+
* GET helper. Sends request metadata via the `extras` header, parses JSON and
|
|
184
|
+
* returns the payload. Throws {@link ShopwaveApiError} for non-2xx responses.
|
|
53
185
|
*/
|
|
54
186
|
async function apiGet(path, extras, options = {}) {
|
|
55
|
-
const
|
|
56
|
-
const url = (options.baseUrl ?? "") + path;
|
|
57
|
-
const headers = { extras: JSON.stringify(buildExtras(extras, options)) };
|
|
58
|
-
console.log("[API GET]", {
|
|
59
|
-
url,
|
|
60
|
-
headers
|
|
61
|
-
});
|
|
62
|
-
const response = await doFetch(url, {
|
|
187
|
+
const { body } = await send(path, {
|
|
63
188
|
method: "GET",
|
|
64
|
-
headers
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
189
|
+
headers: {
|
|
190
|
+
...baseHeaders(options),
|
|
191
|
+
extras: JSON.stringify(buildExtras(extras))
|
|
192
|
+
}
|
|
193
|
+
}, options);
|
|
194
|
+
return body;
|
|
69
195
|
}
|
|
70
196
|
/**
|
|
71
|
-
* JSON body helper for POST/PUT mutations.
|
|
197
|
+
* JSON body helper for POST/PUT mutations. Returns the parsed body (`null` for
|
|
198
|
+
* an empty one). Throws {@link ShopwaveApiError} for non-2xx responses.
|
|
72
199
|
*/
|
|
73
200
|
async function apiSend(path, method, payload, options = {}) {
|
|
74
|
-
const
|
|
75
|
-
const url = (options.baseUrl ?? "") + path;
|
|
76
|
-
const headers = { "Content-Type": "application/json" };
|
|
77
|
-
if (options.token != null) headers.token = options.token;
|
|
78
|
-
console.log(`[API ${method}]`, {
|
|
79
|
-
url,
|
|
80
|
-
headers,
|
|
81
|
-
payload
|
|
82
|
-
});
|
|
83
|
-
const response = await doFetch(url, {
|
|
201
|
+
const { body } = await send(path, {
|
|
84
202
|
method,
|
|
85
|
-
headers
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
203
|
+
headers: {
|
|
204
|
+
...baseHeaders(options),
|
|
205
|
+
"Content-Type": "application/json"
|
|
206
|
+
},
|
|
207
|
+
body: JSON.stringify(payload)
|
|
208
|
+
}, options);
|
|
209
|
+
return body;
|
|
91
210
|
}
|
|
92
211
|
/**
|
|
93
|
-
* DELETE helper.
|
|
212
|
+
* DELETE helper. Shopwave answers deletes with 205 and an empty body; if a body
|
|
213
|
+
* does come back and carries API errors, they are thrown. Returns the parsed
|
|
214
|
+
* body (`null` when empty).
|
|
94
215
|
*/
|
|
95
216
|
async function apiDelete(path, options = {}) {
|
|
96
|
-
const
|
|
97
|
-
const url = (options.baseUrl ?? "") + path;
|
|
98
|
-
const headers = {};
|
|
99
|
-
if (options.token != null) headers.token = options.token;
|
|
100
|
-
console.log("[API DELETE]", {
|
|
101
|
-
url,
|
|
102
|
-
headers
|
|
103
|
-
});
|
|
104
|
-
const response = await doFetch(url, {
|
|
217
|
+
const { status, body } = await send(path, {
|
|
105
218
|
method: "DELETE",
|
|
106
|
-
headers
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
219
|
+
headers: baseHeaders(options)
|
|
220
|
+
}, options);
|
|
221
|
+
if (getApiErrorMap(body)) throw toShopwaveApiError(status, body);
|
|
222
|
+
return body;
|
|
110
223
|
}
|
|
111
224
|
|
|
112
225
|
//#endregion
|
|
@@ -118,11 +231,9 @@ async function apiDelete(path, options = {}) {
|
|
|
118
231
|
async function fetchCategories(params = {}, options = {}) {
|
|
119
232
|
const extras = { deleted: params.deleted ?? false };
|
|
120
233
|
if (params.categoryIds) extras.categoryIds = params.categoryIds;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (error) throw new Error(error);
|
|
125
|
-
return Object.values(json.categories ?? {});
|
|
234
|
+
const json = await apiGet("/api/categories", extras, withToken(options, params.token));
|
|
235
|
+
assertNoApiErrors(json, 200);
|
|
236
|
+
return Object.values(json?.categories ?? {});
|
|
126
237
|
}
|
|
127
238
|
|
|
128
239
|
//#endregion
|
|
@@ -134,9 +245,8 @@ async function fetchCategories(params = {}, options = {}) {
|
|
|
134
245
|
async function fetchConsumers(consumerIds, options = {}) {
|
|
135
246
|
if (!consumerIds || consumerIds.length === 0) return [];
|
|
136
247
|
const json = await apiGet("/api/consumer", { ids: consumerIds.join(",") }, options);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return Object.values(json.consumers ?? {});
|
|
248
|
+
assertNoApiErrors(json, 200);
|
|
249
|
+
return Object.values(json?.consumers ?? {});
|
|
140
250
|
}
|
|
141
251
|
|
|
142
252
|
//#endregion
|
|
@@ -154,10 +264,9 @@ const rolesById = {
|
|
|
154
264
|
async function fetchEmployees(params = {}, options = {}) {
|
|
155
265
|
const extras = { deleted: params.deleted ?? false };
|
|
156
266
|
if (params.employeeIds) extras.employeeIds = params.employeeIds;
|
|
157
|
-
const json = await apiGet("/api/employees", extras, options);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return Object.values(json.employees ?? {});
|
|
267
|
+
const json = await apiGet("/api/employees", extras, withToken(options, params.token));
|
|
268
|
+
assertNoApiErrors(json, 200);
|
|
269
|
+
return Object.values(json?.employees ?? {});
|
|
161
270
|
}
|
|
162
271
|
|
|
163
272
|
//#endregion
|
|
@@ -169,11 +278,9 @@ async function fetchProducts(params = {}, options = {}) {
|
|
|
169
278
|
const extras = { deleted: params.deleted ?? false };
|
|
170
279
|
if (params.productIds) extras.productIds = params.productIds;
|
|
171
280
|
if (params.storeId) extras.storeId = params.storeId;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (error) throw new Error(error);
|
|
176
|
-
return Object.values(json.products ?? {});
|
|
281
|
+
const json = await apiGet("/api/products", extras, withToken(options, params.token));
|
|
282
|
+
assertNoApiErrors(json, 200);
|
|
283
|
+
return Object.values(json?.products ?? {});
|
|
177
284
|
}
|
|
178
285
|
/**
|
|
179
286
|
* Fetches products by id and returns them keyed by id, transparently batching
|
|
@@ -209,11 +316,9 @@ async function fetchPromotions(params = {}, options = {}) {
|
|
|
209
316
|
if (params.imminent !== void 0) extras.imminent = params.imminent;
|
|
210
317
|
if (params.active !== void 0) extras.active = params.active;
|
|
211
318
|
if (params.expired !== void 0) extras.expired = params.expired;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (error) throw new Error(error);
|
|
216
|
-
return Object.values(json.promotions ?? {});
|
|
319
|
+
const json = await apiGet("/api/promotions", extras, withToken(options, params.token));
|
|
320
|
+
assertNoApiErrors(json, 200);
|
|
321
|
+
return Object.values(json?.promotions ?? {});
|
|
217
322
|
}
|
|
218
323
|
|
|
219
324
|
//#endregion
|
|
@@ -222,10 +327,11 @@ async function fetchPromotions(params = {}, options = {}) {
|
|
|
222
327
|
* Fetches stores. Returns the flattened `Store[]`.
|
|
223
328
|
*/
|
|
224
329
|
async function fetchStores(params = {}, options = {}) {
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
330
|
+
const extras = { deleted: params.deleted ?? false };
|
|
331
|
+
if (params.storeIds) extras.storeIds = params.storeIds;
|
|
332
|
+
const json = await apiGet("/api/stores", extras, withToken(options, params.token));
|
|
333
|
+
assertNoApiErrors(json, 200);
|
|
334
|
+
return Object.values(json?.stores ?? {});
|
|
229
335
|
}
|
|
230
336
|
|
|
231
337
|
//#endregion
|
|
@@ -237,20 +343,54 @@ async function fetchStores(params = {}, options = {}) {
|
|
|
237
343
|
*/
|
|
238
344
|
async function fetchReport(query, options = {}) {
|
|
239
345
|
const json = await apiGet("/api/report", { query: JSON.stringify(query) }, options);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
return json.reports ?? {};
|
|
346
|
+
assertNoApiErrors(json, 200);
|
|
347
|
+
return json?.reports ?? {};
|
|
243
348
|
}
|
|
244
349
|
|
|
245
350
|
//#endregion
|
|
246
351
|
//#region src/core/session.ts
|
|
247
352
|
/**
|
|
248
|
-
*
|
|
353
|
+
* Reads the current login status from `GET /api/session`.
|
|
354
|
+
*
|
|
355
|
+
* Works with the new `react-shopwave-connect/next` session route
|
|
356
|
+
* (`{ loggedIn }`) and with older routes that returned the raw session
|
|
357
|
+
* (`{ token: {...} }`) — in that case only a boolean is surfaced.
|
|
358
|
+
*/
|
|
359
|
+
async function fetchSession(options = {}) {
|
|
360
|
+
const doFetch = options.fetch ?? (typeof fetch !== "undefined" ? fetch : void 0);
|
|
361
|
+
if (!doFetch) throw new Error("No fetch implementation available. Pass `options.fetch`.");
|
|
362
|
+
const response = await doFetch(`${options.baseUrl ?? ""}/api/session`, {
|
|
363
|
+
method: "GET",
|
|
364
|
+
headers: { Accept: "application/json" },
|
|
365
|
+
cache: "no-store",
|
|
366
|
+
credentials: "same-origin",
|
|
367
|
+
signal: options.signal
|
|
368
|
+
});
|
|
369
|
+
if (response.status === 401) return { loggedIn: false };
|
|
370
|
+
if (!response.ok) throw new Error(`Session request failed (${response.status})`);
|
|
371
|
+
const json = await response.json();
|
|
372
|
+
if (json && typeof json.loggedIn === "boolean") return {
|
|
373
|
+
loggedIn: json.loggedIn,
|
|
374
|
+
expiresAt: typeof json.expiresAt === "number" ? json.expiresAt : void 0
|
|
375
|
+
};
|
|
376
|
+
return { loggedIn: Boolean(json && json.token) };
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Path that starts a login and returns to `returnTo` afterwards.
|
|
380
|
+
* Navigate the browser to it (`window.location.href = loginPath(...)`).
|
|
381
|
+
*/
|
|
382
|
+
function loginPath(returnTo, authPath = "/auth") {
|
|
383
|
+
return returnTo ? `${authPath}?returnTo=${encodeURIComponent(returnTo)}` : authPath;
|
|
384
|
+
}
|
|
385
|
+
/** Path that logs out of the app and the Shopwave auth server. */
|
|
386
|
+
function logoutPath(authPath = "/auth") {
|
|
387
|
+
return `${authPath}/logout`;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Ends the current server session (no redirect).
|
|
249
391
|
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
* built from server actions) is intentionally NOT included here because it is
|
|
253
|
-
* tied to `next/navigation` and `'use server'` actions — see the README.
|
|
392
|
+
* Prefer navigating to {@link logoutPath} when using
|
|
393
|
+
* `react-shopwave-connect/next`: it also signs the user out of the auth server.
|
|
254
394
|
*/
|
|
255
395
|
async function logout(options = {}) {
|
|
256
396
|
await apiDelete("/api/session?action=logout", options);
|
|
@@ -259,10 +399,12 @@ async function logout(options = {}) {
|
|
|
259
399
|
//#endregion
|
|
260
400
|
//#region src/core/entity.ts
|
|
261
401
|
/**
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
402
|
+
* Low-level, untyped mutations: the caller supplies the route and payload.
|
|
403
|
+
*
|
|
404
|
+
* Prefer the typed functions in `./entities` (`saveProduct`, `deleteCategory`,
|
|
405
|
+
* …): they know each route, build the request envelope, check the response and
|
|
406
|
+
* return the saved entity with its id. Keep these for routes that have no typed
|
|
407
|
+
* function yet (e.g. `/api/merchant`).
|
|
266
408
|
*/
|
|
267
409
|
/**
|
|
268
410
|
* Deletes `/api/{resourcePath}/{entityId}`.
|
|
@@ -272,13 +414,182 @@ async function deleteEntity(resourcePath, entityId, options = {}) {
|
|
|
272
414
|
await apiDelete(`/api/${resourcePath}/${entityId}`, options);
|
|
273
415
|
}
|
|
274
416
|
/**
|
|
275
|
-
* Sends a create/update mutation
|
|
276
|
-
* (
|
|
417
|
+
* Sends a create/update mutation. Returns the response's `result` field when it
|
|
418
|
+
* has one (older app routes wrapped their answer that way), otherwise the whole
|
|
419
|
+
* parsed response body. Throws `ShopwaveApiError` on HTTP or API errors.
|
|
277
420
|
*/
|
|
278
421
|
async function submitEntity({ endpoint, method = "POST", payload }, options = {}) {
|
|
279
|
-
|
|
422
|
+
const json = await apiSend(endpoint, method, payload, options);
|
|
423
|
+
assertNoApiErrors(json, 200);
|
|
424
|
+
if (json && typeof json === "object" && "result" in json) return json.result;
|
|
425
|
+
return json ?? void 0;
|
|
280
426
|
}
|
|
281
427
|
|
|
428
|
+
//#endregion
|
|
429
|
+
//#region src/core/entities.ts
|
|
430
|
+
/**
|
|
431
|
+
* Addressing per entity, following the Shopwave API reference
|
|
432
|
+
* (https://developer.merchantstack.com/api-reference.html) and checked against
|
|
433
|
+
* the live API where noted:
|
|
434
|
+
* - category/product/store: GET filtered by `<x>Ids`, POST upsert, DELETE with `<x>Id` → 205.
|
|
435
|
+
* - promotion: no DELETE; "deleting" a promotion ends it by setting `endDate` to now.
|
|
436
|
+
* - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
|
|
437
|
+
* changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
|
|
438
|
+
* - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
|
|
439
|
+
*/
|
|
440
|
+
const SHOPWAVE_ENTITIES = Object.freeze({
|
|
441
|
+
product: {
|
|
442
|
+
route: "products",
|
|
443
|
+
collection: "products",
|
|
444
|
+
upstream: "product",
|
|
445
|
+
idsHeader: "productIds",
|
|
446
|
+
idHeader: "productId",
|
|
447
|
+
writable: true,
|
|
448
|
+
deleteMode: "delete"
|
|
449
|
+
},
|
|
450
|
+
category: {
|
|
451
|
+
route: "categories",
|
|
452
|
+
collection: "categories",
|
|
453
|
+
upstream: "category",
|
|
454
|
+
idsHeader: "categoryIds",
|
|
455
|
+
idHeader: "categoryId",
|
|
456
|
+
writable: true,
|
|
457
|
+
deleteMode: "delete"
|
|
458
|
+
},
|
|
459
|
+
store: {
|
|
460
|
+
route: "stores",
|
|
461
|
+
collection: "stores",
|
|
462
|
+
upstream: "store",
|
|
463
|
+
idsHeader: "storeIds",
|
|
464
|
+
idHeader: "storeId",
|
|
465
|
+
writable: true,
|
|
466
|
+
deleteMode: "delete"
|
|
467
|
+
},
|
|
468
|
+
promotion: {
|
|
469
|
+
route: "promotions",
|
|
470
|
+
collection: "promotions",
|
|
471
|
+
upstream: "promotion",
|
|
472
|
+
idsHeader: "promotionIds",
|
|
473
|
+
idHeader: "promotionId",
|
|
474
|
+
writable: true,
|
|
475
|
+
deleteMode: "retire",
|
|
476
|
+
retireField: "endDate"
|
|
477
|
+
},
|
|
478
|
+
employee: {
|
|
479
|
+
route: "employees",
|
|
480
|
+
collection: "employees",
|
|
481
|
+
upstream: "employee",
|
|
482
|
+
idsHeader: "employeeIds",
|
|
483
|
+
idHeader: "employeeId",
|
|
484
|
+
writable: true,
|
|
485
|
+
deleteMode: "retire",
|
|
486
|
+
retireField: "exitDate"
|
|
487
|
+
},
|
|
488
|
+
consumer: {
|
|
489
|
+
route: "consumer",
|
|
490
|
+
collection: "consumers",
|
|
491
|
+
upstream: "consumer",
|
|
492
|
+
idsHeader: "ids",
|
|
493
|
+
idHeader: "consumerId",
|
|
494
|
+
writable: false,
|
|
495
|
+
deleteMode: "none"
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
function definition(kind) {
|
|
499
|
+
const def = SHOPWAVE_ENTITIES[kind];
|
|
500
|
+
if (!def) throw new Error(`Unknown Shopwave entity "${kind}"`);
|
|
501
|
+
return def;
|
|
502
|
+
}
|
|
503
|
+
function assertId(kind, id) {
|
|
504
|
+
const s = id == null ? "" : String(id).trim();
|
|
505
|
+
if (!s) throw new Error(`${kind} id is missing`);
|
|
506
|
+
return encodeURIComponent(s);
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Creates or updates several entities of one kind in a single request and
|
|
510
|
+
* returns them as saved (with their ids), in the order given.
|
|
511
|
+
*
|
|
512
|
+
* The request is `{ <collection>: { "0": item0, "1": item1, … } }`. Shopwave
|
|
513
|
+
* answers 201 and echoes every entity under the ref it was sent with, so each
|
|
514
|
+
* result is matched back by ref. Throws {@link ShopwaveApiError} when the
|
|
515
|
+
* response carries `api.message.errors` or any ref is missing from it.
|
|
516
|
+
*
|
|
517
|
+
* The API's partial-success semantics for multi-entity saves haven't been
|
|
518
|
+
* confirmed yet; single saves (`saveProduct` etc.) are the tested path.
|
|
519
|
+
*/
|
|
520
|
+
async function saveEntities(kind, items, options = {}) {
|
|
521
|
+
const def = definition(kind);
|
|
522
|
+
if (items.length === 0) return [];
|
|
523
|
+
const refs = items.map((_, i) => String(i));
|
|
524
|
+
const payload = { [def.collection]: Object.fromEntries(refs.map((ref, i) => [ref, items[i]])) };
|
|
525
|
+
const { status, body: raw } = await apiRequest(`/api/${def.route}`, {
|
|
526
|
+
method: "POST",
|
|
527
|
+
headers: { "Content-Type": "application/json" },
|
|
528
|
+
body: JSON.stringify(payload)
|
|
529
|
+
}, options);
|
|
530
|
+
const body = raw;
|
|
531
|
+
assertNoApiErrors(body, status);
|
|
532
|
+
const echoed = body?.[def.collection] ?? {};
|
|
533
|
+
return refs.map((ref, i) => {
|
|
534
|
+
const saved = echoed[ref];
|
|
535
|
+
if (!saved || saved.id == null) throw new ShopwaveApiError(status, `Shopwave API error (${status}): the saved ${kind} (ref "${ref}") was not returned`, { body });
|
|
536
|
+
return {
|
|
537
|
+
...items[i],
|
|
538
|
+
...saved,
|
|
539
|
+
id: Number(saved.id)
|
|
540
|
+
};
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
/** Creates (no `id`) or updates (with `id`) one entity and returns it with its id. */
|
|
544
|
+
async function saveEntity(kind, item, options = {}) {
|
|
545
|
+
const [saved] = await saveEntities(kind, [item], options);
|
|
546
|
+
return saved;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Deletes one entity (a soft delete: it stays readable with `deleted: true`).
|
|
550
|
+
* Employees and promotions can't be deleted in Shopwave; for them the route
|
|
551
|
+
* sets `exitDate` / `endDate` to now instead (see `SHOPWAVE_ENTITIES`).
|
|
552
|
+
*
|
|
553
|
+
* Shopwave answers 205 with an empty body — also for ids that don't exist — so
|
|
554
|
+
* a resolved promise means "the API accepted the request", not "a record was
|
|
555
|
+
* deleted".
|
|
556
|
+
*/
|
|
557
|
+
async function deleteEntityById(kind, id, options = {}) {
|
|
558
|
+
await apiDelete(`/api/${definition(kind).route}/${assertId(kind, id)}`, options);
|
|
559
|
+
}
|
|
560
|
+
/** Reads one entity by id, or `null` when there's no such (non-deleted) record. */
|
|
561
|
+
async function fetchEntityById(kind, id, params = {}, options = {}) {
|
|
562
|
+
const def = definition(kind);
|
|
563
|
+
const body = await apiGet(`/api/${def.route}/${assertId(kind, id)}`, { deleted: params.deleted ?? false }, options);
|
|
564
|
+
assertNoApiErrors(body, 200);
|
|
565
|
+
const map = body?.[def.collection] ?? {};
|
|
566
|
+
return map[String(id)] ?? Object.values(map).find((e) => String(e?.id) === String(id)) ?? null;
|
|
567
|
+
}
|
|
568
|
+
const saveProduct = (product, options) => saveEntity("product", product, options);
|
|
569
|
+
const deleteProduct = (id, options) => deleteEntityById("product", id, options);
|
|
570
|
+
const fetchProduct = (id, params, options) => fetchEntityById("product", id, params, options);
|
|
571
|
+
const saveCategory = (category, options) => saveEntity("category", category, options);
|
|
572
|
+
const deleteCategory = (id, options) => deleteEntityById("category", id, options);
|
|
573
|
+
const fetchCategory = (id, params, options) => fetchEntityById("category", id, params, options);
|
|
574
|
+
const saveStore = (store, options) => saveEntity("store", store, options);
|
|
575
|
+
const deleteStore = (id, options) => deleteEntityById("store", id, options);
|
|
576
|
+
const fetchStore = (id, params, options) => fetchEntityById("store", id, params, options);
|
|
577
|
+
const savePromotion = (promotion, options) => saveEntity("promotion", promotion, options);
|
|
578
|
+
/** Ends a promotion: Shopwave has no promotion DELETE, so this sets `endDate` to now. */
|
|
579
|
+
const deletePromotion = (id, options) => deleteEntityById("promotion", id, options);
|
|
580
|
+
const fetchPromotion = (id, params, options) => fetchEntityById("promotion", id, params, options);
|
|
581
|
+
/**
|
|
582
|
+
* Creates an employee, or updates one (with `id`). On update Shopwave only
|
|
583
|
+
* applies `roleId`, `joinedDate` and `exitDate` (and store roles); names and
|
|
584
|
+
* email can't be changed, and the echo only carries the updated fields — re-read
|
|
585
|
+
* with `fetchEmployee` if you need the stored record.
|
|
586
|
+
*/
|
|
587
|
+
const saveEmployee = (employee, options) => saveEntity("employee", employee, options);
|
|
588
|
+
/** Retires an employee: Shopwave has no employee DELETE, so this sets `exitDate` to now. */
|
|
589
|
+
const deleteEmployee = (id, options) => deleteEntityById("employee", id, options);
|
|
590
|
+
const fetchEmployee = (id, params, options) => fetchEntityById("employee", id, params, options);
|
|
591
|
+
const fetchConsumer = (id, params, options) => fetchEntityById("consumer", id, params, options);
|
|
592
|
+
|
|
282
593
|
//#endregion
|
|
283
594
|
//#region src/core/basket.ts
|
|
284
595
|
const DEFAULT_BASKET_FILTERS = {
|
|
@@ -429,50 +740,127 @@ function toMessage(e) {
|
|
|
429
740
|
if (e instanceof Error) return e.message;
|
|
430
741
|
return String(e);
|
|
431
742
|
}
|
|
743
|
+
function toStatus(e) {
|
|
744
|
+
return e instanceof ShopwaveApiError ? e.status : null;
|
|
745
|
+
}
|
|
746
|
+
function queryReducer(state, action) {
|
|
747
|
+
switch (action.type) {
|
|
748
|
+
case "start":
|
|
749
|
+
if (action.key !== state.key) return {
|
|
750
|
+
data: null,
|
|
751
|
+
key: action.key,
|
|
752
|
+
fetching: true,
|
|
753
|
+
error: null,
|
|
754
|
+
errorStatus: null
|
|
755
|
+
};
|
|
756
|
+
return {
|
|
757
|
+
...state,
|
|
758
|
+
fetching: true,
|
|
759
|
+
error: null,
|
|
760
|
+
errorStatus: null
|
|
761
|
+
};
|
|
762
|
+
case "success":
|
|
763
|
+
if (action.key !== state.key) return state;
|
|
764
|
+
return {
|
|
765
|
+
data: action.data,
|
|
766
|
+
key: action.key,
|
|
767
|
+
fetching: false,
|
|
768
|
+
error: null,
|
|
769
|
+
errorStatus: null
|
|
770
|
+
};
|
|
771
|
+
case "failure":
|
|
772
|
+
if (action.key !== state.key) return state;
|
|
773
|
+
return {
|
|
774
|
+
...state,
|
|
775
|
+
fetching: false,
|
|
776
|
+
error: action.error,
|
|
777
|
+
errorStatus: action.errorStatus
|
|
778
|
+
};
|
|
779
|
+
case "idle": return state.fetching ? {
|
|
780
|
+
...state,
|
|
781
|
+
fetching: false
|
|
782
|
+
} : state;
|
|
783
|
+
default: return state;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
/** Public view of the internal state for the params currently being rendered. */
|
|
787
|
+
function selectQueryView(state, key, enabled) {
|
|
788
|
+
if (state.key !== key) return {
|
|
789
|
+
data: null,
|
|
790
|
+
loading: enabled,
|
|
791
|
+
fetching: enabled,
|
|
792
|
+
error: null,
|
|
793
|
+
errorStatus: null
|
|
794
|
+
};
|
|
795
|
+
return {
|
|
796
|
+
data: state.data,
|
|
797
|
+
loading: state.fetching && state.data === null,
|
|
798
|
+
fetching: state.fetching,
|
|
799
|
+
error: state.error,
|
|
800
|
+
errorStatus: state.errorStatus
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
function depsKey(deps) {
|
|
804
|
+
try {
|
|
805
|
+
return JSON.stringify(deps);
|
|
806
|
+
} catch {
|
|
807
|
+
return String(deps);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
432
810
|
/**
|
|
433
811
|
* Internal primitive powering the auto-fetch hooks. Runs `fetcher` on mount and
|
|
434
812
|
* whenever `deps` change, tracks loading/error/data, cancels in-flight requests
|
|
435
|
-
* on unmount or dep change, and exposes `refetch
|
|
813
|
+
* on unmount or dep change, and exposes `refetch` (which keeps the current data
|
|
814
|
+
* visible until the new data arrives).
|
|
436
815
|
*
|
|
437
816
|
* When `enabled` is false the fetch is skipped (used for hooks that need
|
|
438
817
|
* arguments before they can run, e.g. consumers-by-id).
|
|
439
818
|
*/
|
|
440
819
|
function useQuery(fetcher, deps, enabled = true) {
|
|
441
|
-
const
|
|
442
|
-
const [
|
|
443
|
-
|
|
820
|
+
const key = depsKey(deps);
|
|
821
|
+
const [state, dispatch] = (0, react.useReducer)(queryReducer, {
|
|
822
|
+
data: null,
|
|
823
|
+
key: enabled ? key : null,
|
|
824
|
+
fetching: enabled,
|
|
825
|
+
error: null,
|
|
826
|
+
errorStatus: null
|
|
827
|
+
});
|
|
444
828
|
const [tick, setTick] = (0, react.useState)(0);
|
|
445
829
|
const refetch = (0, react.useCallback)(() => setTick((t) => t + 1), []);
|
|
446
830
|
(0, react.useEffect)(() => {
|
|
447
831
|
if (!enabled) {
|
|
448
|
-
|
|
832
|
+
dispatch({ type: "idle" });
|
|
449
833
|
return;
|
|
450
834
|
}
|
|
451
835
|
const controller = new AbortController();
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
836
|
+
dispatch({
|
|
837
|
+
type: "start",
|
|
838
|
+
key
|
|
839
|
+
});
|
|
456
840
|
fetcher(controller.signal).then((result) => {
|
|
457
|
-
if (
|
|
841
|
+
if (!controller.signal.aborted) dispatch({
|
|
842
|
+
type: "success",
|
|
843
|
+
key,
|
|
844
|
+
data: result
|
|
845
|
+
});
|
|
458
846
|
}).catch((e) => {
|
|
459
|
-
if (
|
|
460
|
-
|
|
461
|
-
|
|
847
|
+
if (!controller.signal.aborted && e?.name !== "AbortError") dispatch({
|
|
848
|
+
type: "failure",
|
|
849
|
+
key,
|
|
850
|
+
error: toMessage(e),
|
|
851
|
+
errorStatus: toStatus(e)
|
|
852
|
+
});
|
|
462
853
|
});
|
|
463
854
|
return () => {
|
|
464
|
-
active = false;
|
|
465
855
|
controller.abort();
|
|
466
856
|
};
|
|
467
857
|
}, [
|
|
468
|
-
|
|
858
|
+
key,
|
|
469
859
|
tick,
|
|
470
860
|
enabled
|
|
471
861
|
]);
|
|
472
862
|
return {
|
|
473
|
-
|
|
474
|
-
loading,
|
|
475
|
-
error,
|
|
863
|
+
...selectQueryView(state, key, enabled),
|
|
476
864
|
refetch
|
|
477
865
|
};
|
|
478
866
|
}
|
|
@@ -483,16 +871,19 @@ function useMutation(action, deps) {
|
|
|
483
871
|
const [data, setData] = (0, react.useState)(null);
|
|
484
872
|
const [loading, setLoading] = (0, react.useState)(false);
|
|
485
873
|
const [error, setError] = (0, react.useState)(null);
|
|
874
|
+
const [errorStatus, setErrorStatus] = (0, react.useState)(null);
|
|
486
875
|
return {
|
|
487
876
|
mutate: (0, react.useCallback)(async (...args) => {
|
|
488
877
|
setLoading(true);
|
|
489
878
|
setError(null);
|
|
879
|
+
setErrorStatus(null);
|
|
490
880
|
try {
|
|
491
881
|
const result = await action(...args);
|
|
492
882
|
setData(result);
|
|
493
883
|
return result;
|
|
494
884
|
} catch (e) {
|
|
495
885
|
setError(toMessage(e));
|
|
886
|
+
setErrorStatus(toStatus(e));
|
|
496
887
|
return null;
|
|
497
888
|
} finally {
|
|
498
889
|
setLoading(false);
|
|
@@ -500,7 +891,8 @@ function useMutation(action, deps) {
|
|
|
500
891
|
}, deps),
|
|
501
892
|
data,
|
|
502
893
|
loading,
|
|
503
|
-
error
|
|
894
|
+
error,
|
|
895
|
+
errorStatus
|
|
504
896
|
};
|
|
505
897
|
}
|
|
506
898
|
|
|
@@ -682,6 +1074,23 @@ function useLogout(options) {
|
|
|
682
1074
|
return useMutation(() => logout(options), [options?.baseUrl, options?.token]);
|
|
683
1075
|
}
|
|
684
1076
|
|
|
1077
|
+
//#endregion
|
|
1078
|
+
//#region src/hooks/useSession.ts
|
|
1079
|
+
/**
|
|
1080
|
+
* Current login status (`{ loggedIn, expiresAt }`) from `GET /api/session`.
|
|
1081
|
+
* Tokens are never exposed to the browser.
|
|
1082
|
+
*
|
|
1083
|
+
* @example
|
|
1084
|
+
* const { data: session, loading } = useSession();
|
|
1085
|
+
* if (!loading && !session?.loggedIn) window.location.href = loginPath(location.pathname);
|
|
1086
|
+
*/
|
|
1087
|
+
function useSession(options) {
|
|
1088
|
+
return useQuery((signal) => fetchSession({
|
|
1089
|
+
...options,
|
|
1090
|
+
signal
|
|
1091
|
+
}), [options?.baseUrl]);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
685
1094
|
//#endregion
|
|
686
1095
|
//#region src/hooks/useBasketReport.ts
|
|
687
1096
|
/**
|
|
@@ -789,30 +1198,60 @@ function useBasketReport(options) {
|
|
|
789
1198
|
exports.DEFAULT_BASKET_FILTERS = DEFAULT_BASKET_FILTERS;
|
|
790
1199
|
exports.DEFAULT_PAGINATION = DEFAULT_PAGINATION;
|
|
791
1200
|
exports.PromotionRuleSet = PromotionRuleSet;
|
|
1201
|
+
exports.SHOPWAVE_ENTITIES = SHOPWAVE_ENTITIES;
|
|
1202
|
+
exports.ShopwaveApiError = ShopwaveApiError;
|
|
792
1203
|
exports.apiDelete = apiDelete;
|
|
793
1204
|
exports.apiGet = apiGet;
|
|
1205
|
+
exports.apiRequest = apiRequest;
|
|
794
1206
|
exports.apiSend = apiSend;
|
|
1207
|
+
exports.assertNoApiErrors = assertNoApiErrors;
|
|
1208
|
+
exports.authorizationFor = authorizationFor;
|
|
795
1209
|
exports.buildBasketReportQuery = buildBasketReportQuery;
|
|
796
1210
|
exports.buildConsumerMap = buildConsumerMap;
|
|
797
1211
|
exports.collectConsumerIds = collectConsumerIds;
|
|
798
1212
|
exports.collectProductIds = collectProductIds;
|
|
799
1213
|
exports.combineBasketRows = combineBasketRows;
|
|
800
1214
|
exports.computeBasketSummary = computeBasketSummary;
|
|
1215
|
+
exports.deleteCategory = deleteCategory;
|
|
1216
|
+
exports.deleteEmployee = deleteEmployee;
|
|
801
1217
|
exports.deleteEntity = deleteEntity;
|
|
1218
|
+
exports.deleteEntityById = deleteEntityById;
|
|
1219
|
+
exports.deleteProduct = deleteProduct;
|
|
1220
|
+
exports.deletePromotion = deletePromotion;
|
|
1221
|
+
exports.deleteStore = deleteStore;
|
|
802
1222
|
exports.fetchCategories = fetchCategories;
|
|
1223
|
+
exports.fetchCategory = fetchCategory;
|
|
1224
|
+
exports.fetchConsumer = fetchConsumer;
|
|
803
1225
|
exports.fetchConsumers = fetchConsumers;
|
|
1226
|
+
exports.fetchEmployee = fetchEmployee;
|
|
804
1227
|
exports.fetchEmployees = fetchEmployees;
|
|
1228
|
+
exports.fetchEntityById = fetchEntityById;
|
|
1229
|
+
exports.fetchProduct = fetchProduct;
|
|
805
1230
|
exports.fetchProducts = fetchProducts;
|
|
806
1231
|
exports.fetchProductsMap = fetchProductsMap;
|
|
1232
|
+
exports.fetchPromotion = fetchPromotion;
|
|
807
1233
|
exports.fetchPromotions = fetchPromotions;
|
|
808
1234
|
exports.fetchReport = fetchReport;
|
|
1235
|
+
exports.fetchSession = fetchSession;
|
|
1236
|
+
exports.fetchStore = fetchStore;
|
|
809
1237
|
exports.fetchStores = fetchStores;
|
|
810
1238
|
exports.formatDateForQuery = formatDateForQuery;
|
|
1239
|
+
exports.getApiErrorMap = getApiErrorMap;
|
|
811
1240
|
exports.getApiErrors = getApiErrors;
|
|
1241
|
+
exports.loginPath = loginPath;
|
|
812
1242
|
exports.logout = logout;
|
|
1243
|
+
exports.logoutPath = logoutPath;
|
|
813
1244
|
exports.parseBasketReportData = parseBasketReportData;
|
|
814
1245
|
exports.rolesById = rolesById;
|
|
1246
|
+
exports.saveCategory = saveCategory;
|
|
1247
|
+
exports.saveEmployee = saveEmployee;
|
|
1248
|
+
exports.saveEntities = saveEntities;
|
|
1249
|
+
exports.saveEntity = saveEntity;
|
|
1250
|
+
exports.saveProduct = saveProduct;
|
|
1251
|
+
exports.savePromotion = savePromotion;
|
|
1252
|
+
exports.saveStore = saveStore;
|
|
815
1253
|
exports.submitEntity = submitEntity;
|
|
1254
|
+
exports.toShopwaveApiError = toShopwaveApiError;
|
|
816
1255
|
exports.useBasketReport = useBasketReport;
|
|
817
1256
|
exports.useCategory = useCategory;
|
|
818
1257
|
exports.useConsumer = useConsumer;
|
|
@@ -822,6 +1261,8 @@ exports.useLogout = useLogout;
|
|
|
822
1261
|
exports.useProduct = useProduct;
|
|
823
1262
|
exports.usePromotion = usePromotion;
|
|
824
1263
|
exports.useReport = useReport;
|
|
1264
|
+
exports.useSession = useSession;
|
|
825
1265
|
exports.useStore = useStore;
|
|
826
1266
|
exports.useSubmit = useSubmit;
|
|
1267
|
+
exports.withToken = withToken;
|
|
827
1268
|
//# sourceMappingURL=index.cjs.map
|