react-shopwave-connect 0.2.0 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["merged: Extras","headers: Record<string, string>","extras: Record<string, unknown>","extras: Record<string, unknown>","extras: Record<string, unknown>","result: { [id: number]: Product }","extras: Record<string, unknown>","DEFAULT_BASKET_FILTERS: BasketFilters","DEFAULT_PAGINATION: BasketPagination","whereConditions: string[]","keyIndices: { [key: string]: number }","productSubKeys: { [key: string]: number }","products: BasketItem[]","combinedRows: BasketReportRow[]","productsWithNames: BasketItem[]","map: { [id: number]: Consumer }"],"sources":["../../src/core/request.ts","../../src/core/category.ts","../../src/core/consumer.ts","../../src/core/employee.ts","../../src/core/product.ts","../../src/core/promotion.ts","../../src/core/store.ts","../../src/core/report.ts","../../src/core/session.ts","../../src/core/entity.ts","../../src/core/basket.ts"],"sourcesContent":["import type { apiResponse } from \"./types\";\n\n/**\n * Per-call configuration shared by every core API function.\n *\n * Nothing here is React-specific. By accepting these options per call, the\n * core layer works in the browser/Next (relative `/api/...` URLs, the default)\n * AND in Node / CLI / Vue / Angular by passing an absolute `baseUrl`, an auth\n * `token`, and/or a custom `fetch` implementation.\n */\nexport interface RequestOptions {\n /**\n * Prefixed in front of every request path. Leave undefined in the browser to\n * use relative URLs (e.g. `/api/products`). In Node/CLI pass something like\n * `https://api.shopwave.com`.\n */\n baseUrl?: string;\n\n /** OAuth/session token forwarded in the `extras.token` header. */\n token?: string;\n\n /**\n * Custom fetch implementation. Defaults to the global `fetch`. Useful for\n * Node < 18, testing, or injecting interceptors.\n */\n fetch?: typeof fetch;\n\n /** Optional abort signal for cancellation (used by the hooks layer). */\n signal?: AbortSignal;\n}\n\n/** The Shopwave API ships request metadata inside a single `extras` header. */\nexport type Extras = Record<string, unknown>;\n\nfunction resolveFetch(options: RequestOptions): typeof fetch {\n const f = options.fetch ?? (typeof fetch !== \"undefined\" ? fetch : undefined);\n if (!f) {\n throw new Error(\n \"No fetch implementation available. Pass `options.fetch` (e.g. node-fetch / undici) when running outside the browser.\"\n );\n }\n return f;\n}\n\nfunction buildExtras(extras: Extras, options: RequestOptions): Extras {\n const merged: Extras = { \"Content-Type\": \"application/json\", ...extras };\n if (options.token != null && merged.token == null) {\n merged.token = options.token;\n }\n return merged;\n}\n\n/**\n * Returns a JSON string of API errors if the response carries any, otherwise\n * `null`. Mirrors the original `!responseJson.api.message.errors` checks.\n */\nexport function getApiErrors(api?: apiResponse): string | null {\n const errors = api?.message?.errors;\n if (errors && Object.keys(errors).length > 0) {\n return JSON.stringify(errors);\n }\n return null;\n}\n\n/**\n * GET helper. Sends request metadata via the `extras` header exactly like the\n * original hooks did, parses JSON, and returns the typed payload.\n */\nexport async function apiGet<T>(\n path: string,\n extras: Extras,\n options: RequestOptions = {}\n): Promise<T> {\n const doFetch = resolveFetch(options);\n const url = (options.baseUrl ?? \"\") + path;\n\n const headers = { extras: JSON.stringify(buildExtras(extras, options)) };\n console.log('[API GET]', { url, headers });\n\n const response = await doFetch(url, {\n method: \"GET\",\n headers,\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw new Error(`Network response was not ok (${response.status})`);\n }\n\n return (await response.json()) as T;\n}\n\n/**\n * JSON body helper for POST/PUT mutations.\n */\nexport async function apiSend<T>(\n path: string,\n method: \"POST\" | \"PUT\",\n payload: unknown,\n options: RequestOptions = {}\n): Promise<T> {\n const doFetch = resolveFetch(options);\n const url = (options.baseUrl ?? \"\") + path;\n\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (options.token != null) {\n headers.token = options.token;\n }\n\n console.log(`[API ${method}]`, { url, headers, payload });\n\n const response = await doFetch(url, {\n method,\n headers,\n body: JSON.stringify(payload),\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw new Error(`Failed to ${method.toLowerCase()}: ${response.statusText}`);\n }\n\n return (await response.json()) as T;\n}\n\n/**\n * DELETE helper.\n */\nexport async function apiDelete(\n path: string,\n options: RequestOptions = {}\n): Promise<void> {\n const doFetch = resolveFetch(options);\n const url = (options.baseUrl ?? \"\") + path;\n\n const headers: Record<string, string> = {};\n if (options.token != null) {\n headers.token = options.token;\n }\n\n console.log('[API DELETE]', { url, headers });\n\n const response = await doFetch(url, {\n method: \"DELETE\",\n headers,\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw new Error(`Failed to delete: ${response.statusText}`);\n }\n}\n","import { apiGet, getApiErrors, type RequestOptions } from \"./request\";\nimport type { apiResponse } from \"./types\";\n\nexport interface Category {\n id: number;\n title: string;\n parentId: number | null;\n deleteDate: string;\n activeDate: string;\n type: number;\n}\n\nexport interface CategoryResponse {\n categories: {\n [key: number]: Category;\n };\n api: apiResponse;\n}\n\nexport interface FetchCategoriesParams {\n categoryIds?: Array<number>;\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n /** Token override (also accepted via `options.token`). */\n token?: string;\n}\n\n/**\n * Fetches categories. Framework-agnostic: returns the flattened `Category[]`\n * or throws on API errors.\n */\nexport async function fetchCategories(\n params: FetchCategoriesParams = {},\n options: RequestOptions = {}\n): Promise<Category[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n\n if (params.categoryIds) {\n extras.categoryIds = params.categoryIds;\n }\n if (params.token) {\n extras.token = params.token;\n }\n\n const json = await apiGet<CategoryResponse>(\"/api/categories\", extras, options);\n\n const error = getApiErrors(json.api);\n if (error) {\n throw new Error(error);\n }\n\n return Object.values(json.categories ?? {});\n}\n","import { apiGet, getApiErrors, type RequestOptions } from \"./request\";\nimport type { apiResponse } from \"./types\";\n\nexport interface Consumer {\n id: number;\n activeCount: number;\n firstName: string;\n lastName: string;\n email: string;\n}\n\nexport interface ConsumerResponse {\n consumers: {\n [key: number]: Consumer;\n };\n api: apiResponse;\n}\n\n/**\n * Fetches consumers by id. Returns an empty array when no ids are supplied\n * (the original hook short-circuited in that case).\n */\nexport async function fetchConsumers(\n consumerIds: number[] | null | undefined,\n options: RequestOptions = {}\n): Promise<Consumer[]> {\n if (!consumerIds || consumerIds.length === 0) {\n return [];\n }\n\n const extras: Record<string, unknown> = { ids: consumerIds.join(\",\") };\n\n const json = await apiGet<ConsumerResponse>(\"/api/consumer\", extras, options);\n\n const error = getApiErrors(json.api);\n if (error) {\n throw new Error(error);\n }\n\n return Object.values(json.consumers ?? {});\n}\n","import { apiGet, getApiErrors, type RequestOptions } from \"./request\";\nimport type { apiResponse } from \"./types\";\n\nexport const rolesById = {\n \"1\": \"Owner\",\n \"2\": \"Manager\",\n \"3\": \"Assistant\",\n \"4\": \"Guest\",\n \"5\": \"Assistant Manager\",\n} as const;\n\nexport interface Employee {\n id: number;\n firstName: string;\n lastName: string;\n email: string;\n roleId: number;\n joinedDate: string;\n exitDate: string;\n stores: {\n [key: number]: {\n id: number;\n roleId: number;\n };\n };\n}\n\nexport interface EmployeeResponse {\n employees: {\n [key: number]: Employee;\n };\n api: apiResponse;\n}\n\nexport interface FetchEmployeesParams {\n /** Optional id filter. Forwarded when provided. */\n employeeIds?: Array<number>;\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n}\n\n/**\n * Fetches employees. Returns the flattened `Employee[]`.\n */\nexport async function fetchEmployees(\n params: FetchEmployeesParams = {},\n options: RequestOptions = {}\n): Promise<Employee[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n\n if (params.employeeIds) {\n extras.employeeIds = params.employeeIds;\n }\n\n const json = await apiGet<EmployeeResponse>(\"/api/employees\", extras, options);\n\n const error = getApiErrors(json.api);\n if (error) {\n throw new Error(error);\n }\n\n return Object.values(json.employees ?? {});\n}\n","import { apiGet, getApiErrors, type RequestOptions } from \"./request\";\nimport type { apiResponse } from \"./types\";\n\nexport interface ProductInstance {\n id: number;\n price: string;\n taxPercentage: string;\n timestamp: string;\n size: string;\n name: string;\n activeDate: string;\n tags: string;\n}\n\nexport interface Product {\n id: number;\n barcode: string;\n name: string;\n details: string;\n tags: string;\n unit: number;\n timestamp: string;\n activeDate: string;\n deleteDate: string;\n images: Array<string>;\n categories: Array<number>;\n instances: {\n [key: number]: ProductInstance;\n };\n}\n\nexport interface ProductResponse {\n products: {\n [key: number]: Product;\n };\n api: apiResponse;\n}\n\nexport interface FetchProductsParams {\n productIds?: Array<number>;\n storeId?: number;\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n /** Token override (also accepted via `options.token`). */\n token?: string;\n}\n\n/**\n * Fetches products. Returns the flattened `Product[]`.\n */\nexport async function fetchProducts(\n params: FetchProductsParams = {},\n options: RequestOptions = {}\n): Promise<Product[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n\n if (params.productIds) {\n extras.productIds = params.productIds;\n }\n if (params.storeId) {\n extras.storeId = params.storeId;\n }\n if (params.token) {\n extras.token = params.token;\n }\n\n const json = await apiGet<ProductResponse>(\"/api/products\", extras, options);\n\n const error = getApiErrors(json.api);\n if (error) {\n throw new Error(error);\n }\n\n return Object.values(json.products ?? {});\n}\n\n/**\n * Fetches products by id and returns them keyed by id, transparently batching\n * the request (default 200 ids per call) to stay within API limits. Extracted\n * from the original `useBasketReport` inline batching loop so it can be reused\n * outside React.\n */\nexport async function fetchProductsMap(\n productIds: number[] | null | undefined,\n options: RequestOptions = {},\n batchSize = 200\n): Promise<{ [id: number]: Product }> {\n const result: { [id: number]: Product } = {};\n if (!productIds || productIds.length === 0) {\n return result;\n }\n\n for (let i = 0; i < productIds.length; i += batchSize) {\n const batch = productIds.slice(i, i + batchSize);\n const products = await fetchProducts({ productIds: batch }, options);\n for (const product of products) {\n result[product.id] = product;\n }\n }\n\n return result;\n}\n","import { apiGet, getApiErrors, type RequestOptions } from \"./request\";\nimport type { apiResponse } from \"./types\";\n\n/** Promotion rule identifiers as stored by the Shopwave API. */\nexport enum PromotionRuleSet {\n Percentage = \"|%^/\",\n Combo = \"&%</\",\n MealDeal = \"&=^/\",\n}\n\nexport interface Promotion {\n id: number;\n merchantId: number;\n storeIds: number[];\n categoryIds: { [key: string]: number };\n productIds: { [key: string]: any };\n title: string;\n details: string | null;\n terms: string | null;\n startDate: string;\n endDate: string;\n daysAvailable: number[];\n automatic: number;\n rule: PromotionRuleSet;\n x: number;\n y: number | null;\n}\n\nexport interface PromotionResponse {\n promotions: {\n [key: number]: Promotion;\n };\n api: apiResponse;\n}\n\nexport interface FetchPromotionsParams {\n /** Optional id filter. Forwarded when provided. */\n promotionIds?: Array<number>;\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n /** Filter for imminent promotions. */\n imminent?: boolean;\n /** Filter for active promotions. */\n active?: boolean;\n /** Filter for expired promotions. */\n expired?: boolean;\n /** Token override (also accepted via `options.token`). */\n token?: string;\n}\n\n/**\n * Fetches promotions. Returns the flattened `Promotion[]`.\n */\nexport async function fetchPromotions(\n params: FetchPromotionsParams = {},\n options: RequestOptions = {}\n): Promise<Promotion[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n\n if (params.promotionIds) {\n extras.promotionIds = params.promotionIds;\n }\n if (params.imminent !== undefined) {\n extras.imminent = params.imminent;\n }\n if (params.active !== undefined) {\n extras.active = params.active;\n }\n if (params.expired !== undefined) {\n extras.expired = params.expired;\n }\n if (params.token) {\n extras.token = params.token;\n }\n\n const json = await apiGet<PromotionResponse>(\"/api/promotions\", extras, options);\n\n const error = getApiErrors(json.api);\n if (error) {\n throw new Error(error);\n }\n\n return Object.values(json.promotions ?? {});\n}\n","import { apiGet, getApiErrors, type RequestOptions } from \"./request\";\nimport type { apiResponse } from \"./types\";\n\nexport interface Store {\n id: number;\n lat: number;\n lng: number;\n addressLine1: string;\n addressLine2: string | null;\n addressLine3: string;\n phoneNumber: string;\n city: string;\n postcode: string;\n countryId: number | null;\n timezoneId: number | null;\n storeDeleteDate: string | null;\n email: string | null;\n sandbox: boolean | null;\n}\n\nexport interface StoreResponse {\n stores: {\n [key: number]: Store;\n };\n api: apiResponse;\n}\n\nexport interface FetchStoresParams {\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n}\n\n/**\n * Fetches stores. Returns the flattened `Store[]`.\n */\nexport async function fetchStores(\n params: FetchStoresParams = {},\n options: RequestOptions = {}\n): Promise<Store[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n\n const json = await apiGet<StoreResponse>(\"/api/stores\", extras, options);\n\n const error = getApiErrors(json.api);\n if (error) {\n throw new Error(error);\n }\n\n return Object.values(json.stores ?? {});\n}\n","import { apiGet, getApiErrors, type RequestOptions } from \"./request\";\nimport type { apiResponse } from \"./types\";\n\nexport interface ReportKey {\n name: string;\n type: string;\n item?: ReportKey[];\n}\n\nexport interface ReportData {\n keys: ReportKey[];\n data: any[][];\n}\n\nexport interface ReportResponse {\n reports: {\n [key: string]: ReportData;\n };\n api: apiResponse;\n}\n\nexport interface ReportQuery {\n FROM: string;\n WHERE?: {\n AND?: string[];\n OR?: string[];\n };\n SELECT?: string[];\n LIMIT?: number;\n OFFSET?: number;\n}\n\nexport interface ReportQueryMap {\n [key: string]: ReportQuery;\n}\n\n/**\n * Runs one or more report queries and returns the `reports` map keyed by query\n * name. The query is JSON-encoded into the `extras.query` header, matching the\n * original endpoint contract.\n */\nexport async function fetchReport(\n query: ReportQueryMap,\n options: RequestOptions = {}\n): Promise<ReportResponse[\"reports\"]> {\n const extras: Record<string, unknown> = { query: JSON.stringify(query) };\n\n const json = await apiGet<ReportResponse>(\"/api/report\", extras, options);\n\n const error = getApiErrors(json.api);\n if (error) {\n throw new Error(error);\n }\n\n return json.reports ?? {};\n}\n","import { apiDelete, type RequestOptions } from \"./request\";\n\n/**\n * Login status returned by the app's `/api/session` route. It deliberately\n * never contains tokens — those stay in the encrypted httpOnly cookie.\n */\nexport interface SessionStatus {\n loggedIn: boolean;\n /** Access-token expiry (epoch ms), when known. */\n expiresAt?: number;\n}\n\n/**\n * Reads the current login status from `GET /api/session`.\n *\n * Works with the new `react-shopwave-connect/next` session route\n * (`{ loggedIn }`) and with older routes that returned the raw session\n * (`{ token: {...} }`) — in that case only a boolean is surfaced.\n */\nexport async function fetchSession(options: RequestOptions = {}): Promise<SessionStatus> {\n const doFetch = options.fetch ?? (typeof fetch !== \"undefined\" ? fetch : undefined);\n if (!doFetch) {\n throw new Error(\"No fetch implementation available. Pass `options.fetch`.\");\n }\n\n const response = await doFetch(`${options.baseUrl ?? \"\"}/api/session`, {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n cache: \"no-store\",\n credentials: \"same-origin\",\n signal: options.signal,\n } as RequestInit);\n\n if (response.status === 401) return { loggedIn: false };\n if (!response.ok) {\n throw new Error(`Session request failed (${response.status})`);\n }\n\n const json = (await response.json()) as Record<string, unknown> | null;\n if (json && typeof json.loggedIn === \"boolean\") {\n return {\n loggedIn: json.loggedIn,\n expiresAt: typeof json.expiresAt === \"number\" ? json.expiresAt : undefined,\n };\n }\n // Legacy route that returns the whole session: never pass tokens through.\n return { loggedIn: Boolean(json && (json as { token?: unknown }).token) };\n}\n\n/**\n * Path that starts a login and returns to `returnTo` afterwards.\n * Navigate the browser to it (`window.location.href = loginPath(...)`).\n */\nexport function loginPath(returnTo?: string, authPath = \"/auth\"): string {\n return returnTo ? `${authPath}?returnTo=${encodeURIComponent(returnTo)}` : authPath;\n}\n\n/** Path that logs out of the app and the Shopwave auth server. */\nexport function logoutPath(authPath = \"/auth\"): string {\n return `${authPath}/logout`;\n}\n\n/**\n * Ends the current server session (no redirect).\n *\n * Prefer navigating to {@link logoutPath} when using\n * `react-shopwave-connect/next`: it also signs the user out of the auth server.\n */\nexport async function logout(options: RequestOptions = {}): Promise<void> {\n await apiDelete(\"/api/session?action=logout\", options);\n}\n","import { apiDelete, apiSend, type RequestOptions } from \"./request\";\n\n/**\n * Generic CRUD-style mutations extracted from `useHandleDelete` /\n * `useHandleSubmit`. The original hooks also surfaced notistack snackbars; that\n * UI concern is deliberately left to the hooks/host layer so core stays\n * dependency-free.\n */\n\n/**\n * Deletes `/api/{resourcePath}/{entityId}`.\n */\nexport async function deleteEntity(\n resourcePath: string,\n entityId: number | string,\n options: RequestOptions = {}\n): Promise<void> {\n if (entityId == null || entityId === \"\") {\n throw new Error(\"entityId is missing\");\n }\n await apiDelete(`/api/${resourcePath}/${entityId}`, options);\n}\n\nexport interface SubmitEntityArgs {\n /** Fully-resolved endpoint, e.g. `/api/products` or `/api/products/42`. */\n endpoint: string;\n /** Defaults to PUT when an `entityId` exists, otherwise POST. */\n method?: \"POST\" | \"PUT\";\n /** The request payload (already transformed by the caller). */\n payload: unknown;\n}\n\nexport interface SubmitEntityResponse<R = any> {\n result?: R;\n [key: string]: unknown;\n}\n\n/**\n * Sends a create/update mutation and returns the parsed `result` field\n * (matching the original `result.result` access).\n */\nexport async function submitEntity<R = any>(\n { endpoint, method = \"POST\", payload }: SubmitEntityArgs,\n options: RequestOptions = {}\n): Promise<R | undefined> {\n const json = await apiSend<SubmitEntityResponse<R>>(\n endpoint,\n method,\n payload,\n options\n );\n return json.result;\n}\n","import type { Consumer } from \"./consumer\";\nimport type { Product } from \"./product\";\nimport type { ReportQueryMap, ReportResponse } from \"./report\";\n\n// ── Basket Report Types (preserved from the original `types/basket.ts`) ──────\n\nexport interface BasketItem {\n productId: number;\n productName: string;\n quantity: number;\n price: number;\n tax: number;\n}\n\nexport interface Basket {\n id: number;\n name: string;\n consumerId: number;\n products: BasketItem[];\n total: number;\n taxTotal: number;\n status: \"pending\" | \"completed\" | \"cancelled\";\n completeDate: string;\n}\n\nexport interface BasketReportRow {\n id: number;\n basketId: number;\n basketName: string;\n consumerId: number;\n consumerName: string;\n consumerEmail: string;\n products: BasketItem[];\n basketTotal: number;\n tax: number;\n dateTime: string;\n status: string;\n}\n\nexport interface BasketSummary {\n totalBaskets: number;\n totalRevenue: number;\n totalTax: number;\n uniqueConsumers: number;\n}\n\nexport interface BasketFilters {\n search: string;\n dateRange: {\n start: Date | null;\n end: Date | null;\n };\n consumerId: number | null;\n status: \"all\" | \"pending\" | \"completed\" | \"cancelled\";\n}\n\nexport interface BasketPagination {\n page: number;\n pageSize: number;\n total: number;\n}\n\nexport const DEFAULT_BASKET_FILTERS: BasketFilters = {\n search: \"\",\n dateRange: {\n start: null,\n end: null,\n },\n consumerId: null,\n status: \"all\",\n};\n\nexport const DEFAULT_PAGINATION: BasketPagination = {\n page: 0,\n pageSize: 25,\n total: 0,\n};\n\n/** Intermediate shape produced by {@link parseBasketReportData}. */\nexport interface ParsedBasket {\n id: number;\n name: string;\n consumerId: number;\n products: BasketItem[];\n total: number;\n taxTotal: number;\n status: \"completed\";\n completeDate: string;\n}\n\n// ── Pure transform helpers (extracted from the original `useBasketReport`) ────\n\n/** Formats a date as `YYYY-MM-DD HH:mm:ss` for the report query. */\nexport function formatDateForQuery(date: Date, isEndOfDay = false): string {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n const time = isEndOfDay ? \"23:59:59\" : \"00:00:00\";\n return `${year}-${month}-${day} ${time}`;\n}\n\n/** Builds the `Basket` report query from a set of filters. */\nexport function buildBasketReportQuery(filters: BasketFilters): ReportQueryMap {\n const whereConditions: string[] = [];\n\n const today = new Date();\n const startDate = filters.dateRange.start ?? today;\n const endDate = filters.dateRange.end ?? today;\n\n whereConditions.push(\n `Basket.completeDate > '${formatDateForQuery(startDate, false)}'`\n );\n whereConditions.push(\n `Basket.completeDate < '${formatDateForQuery(endDate, true)}'`\n );\n\n if (filters.consumerId) {\n whereConditions.push(`Basket.consumerId = ${filters.consumerId}`);\n }\n\n if (filters.status !== \"all\") {\n whereConditions.push(`Basket.status = '${filters.status}'`);\n }\n\n return {\n baskets: {\n FROM: \"Basket\",\n WHERE: whereConditions.length > 0 ? { AND: whereConditions } : undefined,\n },\n };\n}\n\n/** Flattens the raw report rows/keys into structured baskets. */\nexport function parseBasketReportData(\n reports: ReportResponse[\"reports\"] | null | undefined\n): ParsedBasket[] {\n if (!reports?.baskets?.data || !reports?.baskets?.keys) {\n return [];\n }\n\n const { keys, data } = reports.baskets;\n\n const keyIndices: { [key: string]: number } = {};\n keys.forEach((key, index) => {\n keyIndices[key.name] = index;\n });\n\n const productKey = keys.find((k) => k.name === \"Basket.Product\");\n const productSubKeys: { [key: string]: number } = {};\n if (productKey?.item) {\n productKey.item.forEach((subKey, index) => {\n productSubKeys[subKey.name] = index;\n });\n }\n\n return data.map((row) => {\n const productsArray = (row[keyIndices[\"Basket.Product\"]] as any[][]) || [];\n const products: BasketItem[] = productsArray.map((productRow) => ({\n productId: productRow[productSubKeys[\"Basket.Product.productId\"]] as number,\n productName: `Product #${productRow[productSubKeys[\"Basket.Product.productId\"]]}`,\n quantity:\n (productRow[productSubKeys[\"Basket.Product.quantity\"]] as number) || 1,\n price:\n (productRow[productSubKeys[\"Basket.Product.productPrice\"]] as number) ||\n 0,\n tax:\n (productRow[\n productSubKeys[\"Basket.Product.productTaxPercentage\"]\n ] as number) || 0,\n }));\n\n const total = products.reduce((sum, p) => sum + p.price * p.quantity, 0);\n const taxTotal = products.reduce(\n (sum, p) => sum + p.price * p.quantity * p.tax,\n 0\n );\n\n const completeDateTimestamp = row[keyIndices[\"Basket.completeDate\"]] as number;\n const completeDate = completeDateTimestamp\n ? new Date(completeDateTimestamp).toISOString()\n : \"\";\n\n return {\n id: row[keyIndices[\"Basket.id\"]] as number,\n name: row[keyIndices[\"Basket.name\"]] as string,\n consumerId: row[keyIndices[\"Basket.consumerId\"]] as number,\n products,\n total,\n taxTotal,\n status: \"completed\" as const,\n completeDate,\n };\n });\n}\n\n/** Unique non-null consumer ids referenced by a set of baskets. */\nexport function collectConsumerIds(baskets: ParsedBasket[]): number[] | null {\n if (!baskets.length) return null;\n const ids = baskets\n .map((b) => b.consumerId)\n .filter((id): id is number => id != null);\n return ids.length > 0 ? [...new Set(ids)] : null;\n}\n\n/** Unique product ids referenced by a set of baskets. */\nexport function collectProductIds(baskets: ParsedBasket[]): number[] | null {\n if (!baskets.length) return null;\n const ids = new Set<number>();\n baskets.forEach((basket) => {\n basket.products.forEach((product) => {\n if (product.productId) {\n ids.add(product.productId);\n }\n });\n });\n return ids.size > 0 ? [...ids] : null;\n}\n\nexport interface CombineBasketRowsParams {\n baskets: ParsedBasket[];\n consumerMap: { [id: number]: Consumer };\n productMap: { [id: number]: Product };\n search?: string;\n}\n\n/** Joins baskets with consumer + product lookups, applies search, sorts. */\nexport function combineBasketRows({\n baskets,\n consumerMap,\n productMap,\n search,\n}: CombineBasketRowsParams): BasketReportRow[] {\n if (!baskets.length) return [];\n\n let combinedRows: BasketReportRow[] = baskets.map((basket) => {\n const consumer = basket.consumerId ? consumerMap[basket.consumerId] : null;\n\n const productsWithNames: BasketItem[] = basket.products.map((p) => {\n const product = productMap[p.productId];\n return {\n ...p,\n productName: product?.name || `Product #${p.productId}`,\n };\n });\n\n return {\n id: basket.id,\n basketId: basket.id,\n basketName: basket.name || `Basket #${basket.id}`,\n consumerId: basket.consumerId,\n consumerName: consumer\n ? `${consumer.firstName} ${consumer.lastName}`\n : \"Unknown\",\n consumerEmail: consumer?.email || \"-\",\n products: productsWithNames,\n basketTotal: (basket.total || 0) / 100,\n tax: basket.taxTotal || 0,\n dateTime: basket.completeDate,\n status: basket.status || \"pending\",\n };\n });\n\n if (search) {\n const searchLower = search.toLowerCase();\n combinedRows = combinedRows.filter(\n (row) =>\n row.basketId.toString().includes(searchLower) ||\n row.basketName.toLowerCase().includes(searchLower) ||\n row.consumerName.toLowerCase().includes(searchLower) ||\n row.consumerEmail.toLowerCase().includes(searchLower)\n );\n }\n\n combinedRows.sort((a, b) => {\n const dateA = new Date(a.dateTime).getTime();\n const dateB = new Date(b.dateTime).getTime();\n return dateB - dateA;\n });\n\n return combinedRows;\n}\n\n/** Aggregate totals for a set of report rows. */\nexport function computeBasketSummary(rows: BasketReportRow[]): BasketSummary {\n const uniqueConsumerIds = new Set(rows.map((row) => row.consumerId));\n return {\n totalBaskets: rows.length,\n totalRevenue: rows.reduce((sum, row) => sum + row.basketTotal, 0),\n totalTax: rows.reduce((sum, row) => sum + row.tax, 0),\n uniqueConsumers: uniqueConsumerIds.size,\n };\n}\n\n/** Builds a consumer id → consumer lookup. */\nexport function buildConsumerMap(consumers: Consumer[] | null | undefined): {\n [id: number]: Consumer;\n} {\n const map: { [id: number]: Consumer } = {};\n if (consumers) {\n consumers.forEach((consumer) => {\n map[consumer.id] = consumer;\n });\n }\n return map;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["headers: Record<string, string>","merged: Extras","response: Response","extras: Record<string, unknown>","extras: Record<string, unknown>","extras: Record<string, unknown>","result: { [id: number]: Product }","extras: Record<string, unknown>","extras: Record<string, unknown>","SHOPWAVE_ENTITIES: Readonly<Record<EntityKind, EntityDefinition>>","DEFAULT_BASKET_FILTERS: BasketFilters","DEFAULT_PAGINATION: BasketPagination","whereConditions: string[]","keyIndices: { [key: string]: number }","productSubKeys: { [key: string]: number }","products: BasketItem[]","combinedRows: BasketReportRow[]","productsWithNames: BasketItem[]","map: { [id: number]: Consumer }"],"sources":["../../src/core/errors.ts","../../src/core/request.ts","../../src/core/category.ts","../../src/core/consumer.ts","../../src/core/employee.ts","../../src/core/product.ts","../../src/core/promotion.ts","../../src/core/store.ts","../../src/core/report.ts","../../src/core/session.ts","../../src/core/entity.ts","../../src/core/entities.ts","../../src/core/basket.ts"],"sourcesContent":["import type { ErrorMessage } from \"./types\";\n\n/** Map of Shopwave API errors as found in `api.message.errors`, keyed by error code. */\nexport type ShopwaveErrorMap = Record<string, Partial<ErrorMessage>>;\n\n/**\n * Thrown by every `core` function when a request fails, either at the HTTP\n * level (`status` is the HTTP status) or because the Shopwave response carried\n * `api.message.errors` (then `status` is the HTTP status of that response,\n * usually 200/201, and `errors` holds the API errors).\n *\n * Branch on `status` (e.g. `isUnauthorized` → send the user to log in) rather\n * than parsing `message`.\n */\nexport class ShopwaveApiError extends Error {\n /** HTTP status of the response (0 when the request never got a response). */\n readonly status: number;\n /** Shopwave API errors, when the response carried any. */\n readonly errors: ShopwaveErrorMap | null;\n /** Parsed response body, when there was one. */\n readonly body?: unknown;\n\n constructor(\n status: number,\n message: string,\n details: { errors?: ShopwaveErrorMap | null; body?: unknown; cause?: unknown } = {}\n ) {\n super(message, details.cause !== undefined ? { cause: details.cause } : undefined);\n this.name = \"ShopwaveApiError\";\n this.status = status;\n this.errors = details.errors ?? null;\n this.body = details.body;\n }\n\n /** 401, or the Shopwave \"token expired / invalid\" error 908. */\n get isUnauthorized(): boolean {\n if (this.status === 401) return true;\n return !!this.errors && Object.entries(this.errors).some(([k, v]) => k === \"908\" || Number(v?.id) === 908);\n }\n\n get isNotFound(): boolean {\n return this.status === 404;\n }\n}\n\n/** The `api.message.errors` (or `api.message.error`) map of a Shopwave response, or `null` when there are none. */\nexport function getApiErrorMap(body: unknown): ShopwaveErrorMap | null {\n // The API reference names the map `error`; responses seen so far use `errors`.\n const message = (body as { api?: { message?: { errors?: unknown; error?: unknown } } } | null)?.api?.message;\n const errors = message?.errors ?? message?.error;\n if (errors && typeof errors === \"object\" && Object.keys(errors).length > 0) {\n return errors as ShopwaveErrorMap;\n }\n return null;\n}\n\nfunction describeErrors(errors: ShopwaveErrorMap): string {\n return Object.entries(errors)\n .map(([code, e]) => {\n const text = [e?.title, e?.details].filter(Boolean).join(\": \");\n return text ? `${code} ${text}` : code;\n })\n .join(\"; \");\n}\n\n/**\n * Builds a {@link ShopwaveApiError} for a response. Understands the Shopwave\n * envelope (`api.message.errors`) and the `{ error, message }` bodies returned\n * by the `react-shopwave-connect/next` routes.\n */\nexport function toShopwaveApiError(status: number, body: unknown, fallback?: string): ShopwaveApiError {\n const errors = getApiErrorMap(body);\n let detail = errors ? describeErrors(errors) : \"\";\n if (!detail && body && typeof body === \"object\") {\n const b = body as { message?: unknown; error?: unknown };\n if (typeof b.message === \"string\") detail = b.message;\n else if (typeof b.error === \"string\") detail = b.error;\n }\n if (!detail) detail = fallback ?? (status >= 400 ? \"request failed\" : \"API returned errors\");\n return new ShopwaveApiError(status, `Shopwave API error (${status}): ${detail}`, { errors, body });\n}\n\n/** Throws a {@link ShopwaveApiError} when the response body carries API errors. */\nexport function assertNoApiErrors(body: unknown, status: number): void {\n if (getApiErrorMap(body)) throw toShopwaveApiError(status, body);\n}\n","import type { apiResponse } from \"./types\";\nimport { ShopwaveApiError, getApiErrorMap, toShopwaveApiError } from \"./errors\";\n\n/**\n * Per-call configuration shared by every core API function.\n *\n * Nothing here is React-specific. In the browser/Next leave `baseUrl` empty to\n * call the app's own `/api/...` routes (which hold the session). From Node, a\n * CLI or tests, pass an absolute `baseUrl` pointing at such an app plus a\n * `token`.\n */\nexport interface RequestOptions {\n /**\n * Prefixed in front of every request path. Leave undefined in the browser to\n * use relative URLs (e.g. `/api/products`). From Node pass the app origin,\n * e.g. `http://localhost:3000`.\n */\n baseUrl?: string;\n\n /**\n * Shopwave OAuth access token. Sent as `Authorization: OAuth <token>` on every\n * request (GET, POST, PUT and DELETE alike). A value that already starts with\n * a scheme (`OAuth …` / `Bearer …`) is sent as-is. Leave unset in the browser:\n * the app's routes use the session cookie.\n */\n token?: string;\n\n /**\n * Custom fetch implementation. Defaults to the global `fetch`. Useful for\n * Node < 18, testing, or injecting interceptors.\n */\n fetch?: typeof fetch;\n\n /** Optional abort signal for cancellation (used by the hooks layer). */\n signal?: AbortSignal;\n}\n\n/** The Shopwave API ships request metadata inside a single `extras` header. */\nexport type Extras = Record<string, unknown>;\n\nfunction resolveFetch(options: RequestOptions): typeof fetch {\n const f = options.fetch ?? (typeof fetch !== \"undefined\" ? fetch : undefined);\n if (!f) {\n throw new Error(\n \"No fetch implementation available. Pass `options.fetch` (e.g. node-fetch / undici) when running outside the browser.\"\n );\n }\n return f;\n}\n\n/** Options with `token` filled in from a legacy per-call `params.token`, unless `options.token` is set. */\nexport function withToken(options: RequestOptions, token: string | undefined): RequestOptions {\n return token && !options.token ? { ...options, token } : options;\n}\n\n/** `Authorization` header value for a token (`OAuth <token>` unless it already has a scheme). */\nexport function authorizationFor(token: string): string {\n const t = token.trim();\n return /^(oauth|bearer)\\s+\\S/i.test(t) ? t : `OAuth ${t}`;\n}\n\n/** Headers shared by every request: Accept + the token, when one is given. */\nfunction baseHeaders(options: RequestOptions): Record<string, string> {\n const headers: Record<string, string> = { Accept: \"application/json\" };\n if (options.token) headers.Authorization = authorizationFor(options.token);\n return headers;\n}\n\nfunction buildExtras(extras: Extras): Extras {\n const merged: Extras = { \"Content-Type\": \"application/json\", ...extras };\n // Tokens never travel inside extras any more (see RequestOptions.token).\n delete merged.token;\n return merged;\n}\n\n/** Parses a response body as JSON; `null` for an empty body, `{ raw }` for non-JSON. */\nasync function readBody(response: Response): Promise<unknown> {\n const text = await response.text();\n if (!text) return null;\n try {\n return JSON.parse(text);\n } catch {\n return { raw: text };\n }\n}\n\n/**\n * Low-level request: resolves `{ status, body }` for 2xx responses and throws\n * {@link ShopwaveApiError} otherwise. `init.headers` are merged over the\n * standard ones (Accept + Authorization).\n */\nexport async function apiRequest(\n path: string,\n init: RequestInit,\n options: RequestOptions = {}\n): Promise<{ status: number; body: unknown }> {\n return send(path, { ...init, headers: { ...baseHeaders(options), ...(init.headers as Record<string, string>) } }, options);\n}\n\nasync function send(path: string, init: RequestInit, options: RequestOptions): Promise<{ status: number; body: unknown }> {\n const doFetch = resolveFetch(options);\n const url = (options.baseUrl ?? \"\") + path;\n\n let response: Response;\n try {\n response = await doFetch(url, { ...init, signal: options.signal });\n } catch (error) {\n if ((error as { name?: string })?.name === \"AbortError\") throw error;\n throw new ShopwaveApiError(0, `Shopwave API request failed: ${(error as Error)?.message ?? error}`, { cause: error });\n }\n\n const body = await readBody(response);\n if (!response.ok) {\n throw toShopwaveApiError(response.status, body, response.statusText || undefined);\n }\n return { status: response.status, body };\n}\n\n/**\n * Returns a JSON string of API errors if the response carries any, otherwise\n * `null`. Kept for backwards compatibility; prefer `ShopwaveApiError`.\n */\nexport function getApiErrors(api?: apiResponse): string | null {\n const errors = getApiErrorMap({ api });\n return errors ? JSON.stringify(errors) : null;\n}\n\n/**\n * GET helper. Sends request metadata via the `extras` header, parses JSON and\n * returns the payload. Throws {@link ShopwaveApiError} for non-2xx responses.\n */\nexport async function apiGet<T>(path: string, extras: Extras, options: RequestOptions = {}): Promise<T> {\n const { body } = await send(\n path,\n {\n method: \"GET\",\n headers: { ...baseHeaders(options), extras: JSON.stringify(buildExtras(extras)) },\n },\n options\n );\n return body as T;\n}\n\n/**\n * JSON body helper for POST/PUT mutations. Returns the parsed body (`null` for\n * an empty one). Throws {@link ShopwaveApiError} for non-2xx responses.\n */\nexport async function apiSend<T>(\n path: string,\n method: \"POST\" | \"PUT\",\n payload: unknown,\n options: RequestOptions = {}\n): Promise<T> {\n const { body } = await send(\n path,\n {\n method,\n headers: { ...baseHeaders(options), \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n },\n options\n );\n return body as T;\n}\n\n/**\n * DELETE helper. Shopwave answers deletes with 205 and an empty body; if a body\n * does come back and carries API errors, they are thrown. Returns the parsed\n * body (`null` when empty).\n */\nexport async function apiDelete(path: string, options: RequestOptions = {}): Promise<unknown> {\n const { status, body } = await send(path, { method: \"DELETE\", headers: baseHeaders(options) }, options);\n if (getApiErrorMap(body)) throw toShopwaveApiError(status, body);\n return body;\n}\n","import { apiGet, withToken, type RequestOptions } from \"./request\";\nimport { assertNoApiErrors } from \"./errors\";\nimport type { apiResponse } from \"./types\";\n\nexport interface Category {\n id: number;\n title: string;\n parentId: number | null;\n deleteDate: string;\n activeDate: string;\n type: number;\n}\n\nexport interface CategoryResponse {\n categories: {\n [key: number]: Category;\n };\n api: apiResponse;\n}\n\nexport interface FetchCategoriesParams {\n categoryIds?: Array<number>;\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */\n token?: string;\n}\n\n/**\n * Fetches categories. Framework-agnostic: returns the flattened `Category[]`\n * or throws on API errors.\n */\nexport async function fetchCategories(\n params: FetchCategoriesParams = {},\n options: RequestOptions = {}\n): Promise<Category[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n\n if (params.categoryIds) {\n extras.categoryIds = params.categoryIds;\n }\n\n const json = await apiGet<CategoryResponse | null>(\"/api/categories\", extras, withToken(options, params.token));\n\n assertNoApiErrors(json, 200);\n\n // Shopwave answers an empty body when nothing matches.\n return Object.values(json?.categories ?? {});\n}\n","import { apiGet, type RequestOptions } from \"./request\";\nimport { assertNoApiErrors } from \"./errors\";\nimport type { apiResponse } from \"./types\";\n\nexport interface Consumer {\n id: number;\n activeCount: number;\n firstName: string;\n lastName: string;\n email: string;\n}\n\nexport interface ConsumerResponse {\n consumers: {\n [key: number]: Consumer;\n };\n api: apiResponse;\n}\n\n/**\n * Fetches consumers by id. Returns an empty array when no ids are supplied\n * (the original hook short-circuited in that case).\n */\nexport async function fetchConsumers(\n consumerIds: number[] | null | undefined,\n options: RequestOptions = {}\n): Promise<Consumer[]> {\n if (!consumerIds || consumerIds.length === 0) {\n return [];\n }\n\n const extras: Record<string, unknown> = { ids: consumerIds.join(\",\") };\n\n const json = await apiGet<ConsumerResponse | null>(\"/api/consumer\", extras, options);\n\n assertNoApiErrors(json, 200);\n\n // Shopwave answers an empty body when nothing matches.\n return Object.values(json?.consumers ?? {});\n}\n","import { apiGet, withToken, type RequestOptions } from \"./request\";\nimport { assertNoApiErrors } from \"./errors\";\nimport type { apiResponse } from \"./types\";\n\nexport const rolesById = {\n \"1\": \"Owner\",\n \"2\": \"Manager\",\n \"3\": \"Assistant\",\n \"4\": \"Guest\",\n \"5\": \"Assistant Manager\",\n} as const;\n\nexport interface Employee {\n id: number;\n firstName: string;\n lastName: string;\n email: string;\n roleId: number;\n joinedDate: string;\n exitDate: string;\n stores: {\n [key: number]: {\n id: number;\n roleId: number;\n };\n };\n}\n\nexport interface EmployeeResponse {\n employees: {\n [key: number]: Employee;\n };\n api: apiResponse;\n}\n\nexport interface FetchEmployeesParams {\n /** Optional id filter. Forwarded when provided. */\n employeeIds?: Array<number>;\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */\n token?: string;\n}\n\n/**\n * Fetches employees. Returns the flattened `Employee[]`.\n */\nexport async function fetchEmployees(\n params: FetchEmployeesParams = {},\n options: RequestOptions = {}\n): Promise<Employee[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n\n if (params.employeeIds) {\n extras.employeeIds = params.employeeIds;\n }\n\n const json = await apiGet<EmployeeResponse | null>(\"/api/employees\", extras, withToken(options, params.token));\n\n assertNoApiErrors(json, 200);\n\n // Shopwave answers an empty body when nothing matches.\n return Object.values(json?.employees ?? {});\n}\n","import { apiGet, withToken, type RequestOptions } from \"./request\";\nimport { assertNoApiErrors } from \"./errors\";\nimport type { apiResponse } from \"./types\";\n\nexport interface ProductInstance {\n id: number;\n price: string;\n taxPercentage: string;\n timestamp: string;\n size: string;\n name: string;\n activeDate: string;\n tags: string;\n}\n\nexport interface Product {\n id: number;\n barcode: string;\n name: string;\n details: string;\n tags: string;\n unit: number;\n timestamp: string;\n activeDate: string;\n deleteDate: string;\n images: Array<string>;\n categories: Array<number>;\n instances: {\n [key: number]: ProductInstance;\n };\n}\n\nexport interface ProductResponse {\n products: {\n [key: number]: Product;\n };\n api: apiResponse;\n}\n\nexport interface FetchProductsParams {\n productIds?: Array<number>;\n storeId?: number;\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */\n token?: string;\n}\n\n/**\n * Fetches products. Returns the flattened `Product[]`.\n */\nexport async function fetchProducts(\n params: FetchProductsParams = {},\n options: RequestOptions = {}\n): Promise<Product[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n\n if (params.productIds) {\n extras.productIds = params.productIds;\n }\n if (params.storeId) {\n extras.storeId = params.storeId;\n }\n\n const json = await apiGet<ProductResponse | null>(\"/api/products\", extras, withToken(options, params.token));\n\n assertNoApiErrors(json, 200);\n\n // Shopwave answers an empty body when nothing matches.\n return Object.values(json?.products ?? {});\n}\n\n/**\n * Fetches products by id and returns them keyed by id, transparently batching\n * the request (default 200 ids per call) to stay within API limits. Extracted\n * from the original `useBasketReport` inline batching loop so it can be reused\n * outside React.\n */\nexport async function fetchProductsMap(\n productIds: number[] | null | undefined,\n options: RequestOptions = {},\n batchSize = 200\n): Promise<{ [id: number]: Product }> {\n const result: { [id: number]: Product } = {};\n if (!productIds || productIds.length === 0) {\n return result;\n }\n\n for (let i = 0; i < productIds.length; i += batchSize) {\n const batch = productIds.slice(i, i + batchSize);\n const products = await fetchProducts({ productIds: batch }, options);\n for (const product of products) {\n result[product.id] = product;\n }\n }\n\n return result;\n}\n","import { apiGet, withToken, type RequestOptions } from \"./request\";\nimport { assertNoApiErrors } from \"./errors\";\nimport type { apiResponse } from \"./types\";\n\n/** Promotion rule identifiers as stored by the Shopwave API. */\nexport enum PromotionRuleSet {\n Percentage = \"|%^/\",\n Combo = \"&%</\",\n MealDeal = \"&=^/\",\n}\n\nexport interface Promotion {\n id: number;\n merchantId: number;\n storeIds: number[];\n categoryIds: { [key: string]: number };\n productIds: { [key: string]: any };\n title: string;\n details: string | null;\n terms: string | null;\n startDate: string;\n endDate: string;\n daysAvailable: number[];\n automatic: number;\n rule: PromotionRuleSet;\n x: number;\n y: number | null;\n}\n\nexport interface PromotionResponse {\n promotions: {\n [key: number]: Promotion;\n };\n api: apiResponse;\n}\n\nexport interface FetchPromotionsParams {\n /** Optional id filter. Forwarded when provided. */\n promotionIds?: Array<number>;\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n /** Filter for imminent promotions. */\n imminent?: boolean;\n /** Filter for active promotions. */\n active?: boolean;\n /** Filter for expired promotions. */\n expired?: boolean;\n /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */\n token?: string;\n}\n\n/**\n * Fetches promotions. Returns the flattened `Promotion[]`.\n */\nexport async function fetchPromotions(\n params: FetchPromotionsParams = {},\n options: RequestOptions = {}\n): Promise<Promotion[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n\n if (params.promotionIds) {\n extras.promotionIds = params.promotionIds;\n }\n if (params.imminent !== undefined) {\n extras.imminent = params.imminent;\n }\n if (params.active !== undefined) {\n extras.active = params.active;\n }\n if (params.expired !== undefined) {\n extras.expired = params.expired;\n }\n\n const json = await apiGet<PromotionResponse | null>(\"/api/promotions\", extras, withToken(options, params.token));\n\n assertNoApiErrors(json, 200);\n\n // Shopwave answers an empty body when nothing matches.\n return Object.values(json?.promotions ?? {});\n}\n","import { apiGet, withToken, type RequestOptions } from \"./request\";\nimport { assertNoApiErrors } from \"./errors\";\nimport type { apiResponse } from \"./types\";\n\nexport interface Store {\n id: number;\n lat: number;\n lng: number;\n addressLine1: string;\n addressLine2: string | null;\n addressLine3: string;\n phoneNumber: string;\n city: string;\n postcode: string;\n countryId: number | null;\n timezoneId: number | null;\n storeDeleteDate: string | null;\n email: string | null;\n sandbox: boolean | null;\n}\n\nexport interface StoreResponse {\n stores: {\n [key: number]: Store;\n };\n api: apiResponse;\n}\n\nexport interface FetchStoresParams {\n /** Optional id filter. */\n storeIds?: Array<number>;\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */\n token?: string;\n}\n\n/**\n * Fetches stores. Returns the flattened `Store[]`.\n */\nexport async function fetchStores(\n params: FetchStoresParams = {},\n options: RequestOptions = {}\n): Promise<Store[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n if (params.storeIds) {\n extras.storeIds = params.storeIds;\n }\n\n const json = await apiGet<StoreResponse | null>(\"/api/stores\", extras, withToken(options, params.token));\n\n assertNoApiErrors(json, 200);\n\n // Shopwave answers an empty body when nothing matches.\n return Object.values(json?.stores ?? {});\n}\n","import { apiGet, type RequestOptions } from \"./request\";\nimport { assertNoApiErrors } from \"./errors\";\nimport type { apiResponse } from \"./types\";\n\nexport interface ReportKey {\n name: string;\n type: string;\n item?: ReportKey[];\n}\n\nexport interface ReportData {\n keys: ReportKey[];\n data: any[][];\n}\n\nexport interface ReportResponse {\n reports: {\n [key: string]: ReportData;\n };\n api: apiResponse;\n}\n\nexport interface ReportQuery {\n FROM: string;\n WHERE?: {\n AND?: string[];\n OR?: string[];\n };\n SELECT?: string[];\n LIMIT?: number;\n OFFSET?: number;\n}\n\nexport interface ReportQueryMap {\n [key: string]: ReportQuery;\n}\n\n/**\n * Runs one or more report queries and returns the `reports` map keyed by query\n * name. The query is JSON-encoded into the `extras.query` header, matching the\n * original endpoint contract.\n */\nexport async function fetchReport(\n query: ReportQueryMap,\n options: RequestOptions = {}\n): Promise<ReportResponse[\"reports\"]> {\n const extras: Record<string, unknown> = { query: JSON.stringify(query) };\n\n const json = await apiGet<ReportResponse | null>(\"/api/report\", extras, options);\n\n assertNoApiErrors(json, 200);\n\n return json?.reports ?? {};\n}\n","import { apiDelete, type RequestOptions } from \"./request\";\n\n/**\n * Login status returned by the app's `/api/session` route. It deliberately\n * never contains tokens — those stay in the encrypted httpOnly cookie.\n */\nexport interface SessionStatus {\n loggedIn: boolean;\n /** Access-token expiry (epoch ms), when known. */\n expiresAt?: number;\n}\n\n/**\n * Reads the current login status from `GET /api/session`.\n *\n * Works with the new `react-shopwave-connect/next` session route\n * (`{ loggedIn }`) and with older routes that returned the raw session\n * (`{ token: {...} }`) — in that case only a boolean is surfaced.\n */\nexport async function fetchSession(options: RequestOptions = {}): Promise<SessionStatus> {\n const doFetch = options.fetch ?? (typeof fetch !== \"undefined\" ? fetch : undefined);\n if (!doFetch) {\n throw new Error(\"No fetch implementation available. Pass `options.fetch`.\");\n }\n\n const response = await doFetch(`${options.baseUrl ?? \"\"}/api/session`, {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n cache: \"no-store\",\n credentials: \"same-origin\",\n signal: options.signal,\n } as RequestInit);\n\n if (response.status === 401) return { loggedIn: false };\n if (!response.ok) {\n throw new Error(`Session request failed (${response.status})`);\n }\n\n const json = (await response.json()) as Record<string, unknown> | null;\n if (json && typeof json.loggedIn === \"boolean\") {\n return {\n loggedIn: json.loggedIn,\n expiresAt: typeof json.expiresAt === \"number\" ? json.expiresAt : undefined,\n };\n }\n // Legacy route that returns the whole session: never pass tokens through.\n return { loggedIn: Boolean(json && (json as { token?: unknown }).token) };\n}\n\n/**\n * Path that starts a login and returns to `returnTo` afterwards.\n * Navigate the browser to it (`window.location.href = loginPath(...)`).\n */\nexport function loginPath(returnTo?: string, authPath = \"/auth\"): string {\n return returnTo ? `${authPath}?returnTo=${encodeURIComponent(returnTo)}` : authPath;\n}\n\n/** Path that logs out of the app and the Shopwave auth server. */\nexport function logoutPath(authPath = \"/auth\"): string {\n return `${authPath}/logout`;\n}\n\n/**\n * Ends the current server session (no redirect).\n *\n * Prefer navigating to {@link logoutPath} when using\n * `react-shopwave-connect/next`: it also signs the user out of the auth server.\n */\nexport async function logout(options: RequestOptions = {}): Promise<void> {\n await apiDelete(\"/api/session?action=logout\", options);\n}\n","import { apiDelete, apiSend, type RequestOptions } from \"./request\";\nimport { assertNoApiErrors } from \"./errors\";\n\n/**\n * Low-level, untyped mutations: the caller supplies the route and payload.\n *\n * Prefer the typed functions in `./entities` (`saveProduct`, `deleteCategory`,\n * …): they know each route, build the request envelope, check the response and\n * return the saved entity with its id. Keep these for routes that have no typed\n * function yet (e.g. `/api/merchant`).\n */\n\n/**\n * Deletes `/api/{resourcePath}/{entityId}`.\n */\nexport async function deleteEntity(\n resourcePath: string,\n entityId: number | string,\n options: RequestOptions = {}\n): Promise<void> {\n if (entityId == null || entityId === \"\") {\n throw new Error(\"entityId is missing\");\n }\n await apiDelete(`/api/${resourcePath}/${entityId}`, options);\n}\n\nexport interface SubmitEntityArgs {\n /** Fully-resolved endpoint, e.g. `/api/products` or `/api/products/42`. */\n endpoint: string;\n /** Defaults to PUT when an `entityId` exists, otherwise POST. */\n method?: \"POST\" | \"PUT\";\n /** The request payload (already transformed by the caller). */\n payload: unknown;\n}\n\nexport interface SubmitEntityResponse<R = any> {\n result?: R;\n [key: string]: unknown;\n}\n\n/**\n * Sends a create/update mutation. Returns the response's `result` field when it\n * has one (older app routes wrapped their answer that way), otherwise the whole\n * parsed response body. Throws `ShopwaveApiError` on HTTP or API errors.\n */\nexport async function submitEntity<R = any>(\n { endpoint, method = \"POST\", payload }: SubmitEntityArgs,\n options: RequestOptions = {}\n): Promise<R | undefined> {\n const json = await apiSend<SubmitEntityResponse<R> | null>(\n endpoint,\n method,\n payload,\n options\n );\n assertNoApiErrors(json, 200);\n if (json && typeof json === \"object\" && \"result\" in json) return json.result;\n return (json ?? undefined) as R | undefined;\n}\n","import { apiDelete, apiGet, apiRequest, type RequestOptions } from \"./request\";\nimport { ShopwaveApiError, assertNoApiErrors } from \"./errors\";\nimport type { Product } from \"./product\";\nimport type { Category } from \"./category\";\nimport type { Store } from \"./store\";\nimport type { Promotion } from \"./promotion\";\nimport type { Employee } from \"./employee\";\nimport type { Consumer } from \"./consumer\";\n\n/**\n * How each Shopwave entity is addressed, shared by the client functions below\n * and the server route handlers in `react-shopwave-connect/next`, so the two\n * halves of the contract can't drift apart.\n */\nexport interface EntityDefinition {\n /** App route segment: `/api/<route>` and `/api/<route>/<id>`. */\n route: string;\n /** Key of the entity map in requests and responses (`{ products: { … } }`). */\n collection: string;\n /** Shopwave API path (`GET/POST/DELETE {apiUrl}/<upstream>`). */\n upstream: string;\n /** Header that filters reads by id (`productIds: 1,2`). */\n idsHeader: string;\n /** Header that names the record to delete (`productId: 1`). */\n idHeader: string;\n /** False for read-only entities (the API has no POST for them). */\n writable: boolean;\n /**\n * How a delete is done upstream:\n * - `\"delete\"`: `DELETE {apiUrl}/<upstream>` with the `idHeader` (205, empty body)\n * - `\"retire\"`: the API has no DELETE; the record is retired by POSTing\n * `{ id, [retireField]: <now> }` (employees: `exitDate`, promotions: `endDate`)\n * - `\"none\"`: can't be deleted\n */\n deleteMode: \"delete\" | \"retire\" | \"none\";\n /** Date field set to \"now\" when `deleteMode` is `\"retire\"`. */\n retireField?: string;\n}\n\nexport type EntityKind = \"product\" | \"category\" | \"store\" | \"promotion\" | \"employee\" | \"consumer\";\n\n/** Entities that can be created/updated through the API. */\nexport type WritableEntityKind = Exclude<EntityKind, \"consumer\">;\n\n/** Entities that can be deleted (or retired) through the API. */\nexport type DeletableEntityKind = Exclude<EntityKind, \"consumer\">;\n\n/**\n * Addressing per entity, following the Shopwave API reference\n * (https://developer.merchantstack.com/api-reference.html) and checked against\n * the live API where noted:\n * - category/product/store: GET filtered by `<x>Ids`, POST upsert, DELETE with `<x>Id` → 205.\n * - promotion: no DELETE; \"deleting\" a promotion ends it by setting `endDate` to now.\n * - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be\n * changed); no DELETE (live API: \"Cannot DELETE /employee\") → retired via `exitDate`.\n * - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.\n */\nexport const SHOPWAVE_ENTITIES: Readonly<Record<EntityKind, EntityDefinition>> = Object.freeze({\n product: { route: \"products\", collection: \"products\", upstream: \"product\", idsHeader: \"productIds\", idHeader: \"productId\", writable: true, deleteMode: \"delete\" },\n category: { route: \"categories\", collection: \"categories\", upstream: \"category\", idsHeader: \"categoryIds\", idHeader: \"categoryId\", writable: true, deleteMode: \"delete\" },\n store: { route: \"stores\", collection: \"stores\", upstream: \"store\", idsHeader: \"storeIds\", idHeader: \"storeId\", writable: true, deleteMode: \"delete\" },\n promotion: { route: \"promotions\", collection: \"promotions\", upstream: \"promotion\", idsHeader: \"promotionIds\", idHeader: \"promotionId\", writable: true, deleteMode: \"retire\", retireField: \"endDate\" },\n employee: { route: \"employees\", collection: \"employees\", upstream: \"employee\", idsHeader: \"employeeIds\", idHeader: \"employeeId\", writable: true, deleteMode: \"retire\", retireField: \"exitDate\" },\n consumer: { route: \"consumer\", collection: \"consumers\", upstream: \"consumer\", idsHeader: \"ids\", idHeader: \"consumerId\", writable: false, deleteMode: \"none\" },\n});\n\nexport interface EntityTypes {\n product: Product;\n category: Category;\n store: Store;\n promotion: Promotion;\n employee: Employee;\n consumer: Consumer;\n}\n\n/**\n * What a save accepts: any subset of the entity's fields (plus extra API\n * fields the type doesn't list). Include `id` to update; leave it out to create.\n */\nexport type EntityInput<T> = Partial<T> & { [field: string]: unknown };\n\n/** A saved entity: always has its `id`. */\nexport type Saved<T> = T & { id: number };\n\nexport type EntityId = number | string;\n\nfunction definition(kind: EntityKind): EntityDefinition {\n const def = SHOPWAVE_ENTITIES[kind];\n if (!def) throw new Error(`Unknown Shopwave entity \"${kind}\"`);\n return def;\n}\n\nfunction assertId(kind: EntityKind, id: EntityId): string {\n const s = id == null ? \"\" : String(id).trim();\n if (!s) throw new Error(`${kind} id is missing`);\n return encodeURIComponent(s);\n}\n\n/**\n * Creates or updates several entities of one kind in a single request and\n * returns them as saved (with their ids), in the order given.\n *\n * The request is `{ <collection>: { \"0\": item0, \"1\": item1, … } }`. Shopwave\n * answers 201 and echoes every entity under the ref it was sent with, so each\n * result is matched back by ref. Throws {@link ShopwaveApiError} when the\n * response carries `api.message.errors` or any ref is missing from it.\n *\n * The API's partial-success semantics for multi-entity saves haven't been\n * confirmed yet; single saves (`saveProduct` etc.) are the tested path.\n */\nexport async function saveEntities<K extends WritableEntityKind>(\n kind: K,\n items: ReadonlyArray<EntityInput<EntityTypes[K]>>,\n options: RequestOptions = {}\n): Promise<Array<Saved<EntityTypes[K]>>> {\n const def = definition(kind);\n if (items.length === 0) return [];\n\n const refs = items.map((_, i) => String(i));\n const payload = { [def.collection]: Object.fromEntries(refs.map((ref, i) => [ref, items[i]])) };\n\n const { status, body: raw } = await apiRequest(\n `/api/${def.route}`,\n { method: \"POST\", headers: { \"Content-Type\": \"application/json\" }, body: JSON.stringify(payload) },\n options\n );\n const body = raw as Record<string, unknown> | null;\n assertNoApiErrors(body, status);\n\n const echoed = (body?.[def.collection] ?? {}) as Record<string, Record<string, unknown> | undefined>;\n return refs.map((ref, i) => {\n const saved = echoed[ref];\n if (!saved || saved.id == null) {\n throw new ShopwaveApiError(status, `Shopwave API error (${status}): the saved ${kind} (ref \"${ref}\") was not returned`, {\n body,\n });\n }\n // The write response echoes what was sent plus server fields; keep any\n // field it leaves out.\n return { ...items[i], ...saved, id: Number(saved.id) } as unknown as Saved<EntityTypes[K]>;\n });\n}\n\n/** Creates (no `id`) or updates (with `id`) one entity and returns it with its id. */\nexport async function saveEntity<K extends WritableEntityKind>(\n kind: K,\n item: EntityInput<EntityTypes[K]>,\n options: RequestOptions = {}\n): Promise<Saved<EntityTypes[K]>> {\n const [saved] = await saveEntities(kind, [item], options);\n return saved;\n}\n\n/**\n * Deletes one entity (a soft delete: it stays readable with `deleted: true`).\n * Employees and promotions can't be deleted in Shopwave; for them the route\n * sets `exitDate` / `endDate` to now instead (see `SHOPWAVE_ENTITIES`).\n *\n * Shopwave answers 205 with an empty body — also for ids that don't exist — so\n * a resolved promise means \"the API accepted the request\", not \"a record was\n * deleted\".\n */\nexport async function deleteEntityById(kind: DeletableEntityKind, id: EntityId, options: RequestOptions = {}): Promise<void> {\n await apiDelete(`/api/${definition(kind).route}/${assertId(kind, id)}`, options);\n}\n\nexport interface FetchByIdParams {\n /** Also return the record when it has been deleted. Defaults to `false`. */\n deleted?: boolean;\n}\n\n/** Reads one entity by id, or `null` when there's no such (non-deleted) record. */\nexport async function fetchEntityById<K extends EntityKind>(\n kind: K,\n id: EntityId,\n params: FetchByIdParams = {},\n options: RequestOptions = {}\n): Promise<EntityTypes[K] | null> {\n const def = definition(kind);\n const body = await apiGet<Record<string, unknown> | null>(\n `/api/${def.route}/${assertId(kind, id)}`,\n { deleted: params.deleted ?? false },\n options\n );\n assertNoApiErrors(body, 200);\n const map = (body?.[def.collection] ?? {}) as Record<string, EntityTypes[K]>;\n const match = map[String(id)] ?? Object.values(map).find((e) => String((e as { id?: unknown })?.id) === String(id));\n return match ?? null;\n}\n\n// ---------------------------------------------------------------------------\n// Typed per-entity functions\n// ---------------------------------------------------------------------------\n\nexport const saveProduct = (product: EntityInput<Product>, options?: RequestOptions) =>\n saveEntity(\"product\", product, options);\nexport const deleteProduct = (id: EntityId, options?: RequestOptions) => deleteEntityById(\"product\", id, options);\nexport const fetchProduct = (id: EntityId, params?: FetchByIdParams, options?: RequestOptions) =>\n fetchEntityById(\"product\", id, params, options);\n\nexport const saveCategory = (category: EntityInput<Category>, options?: RequestOptions) =>\n saveEntity(\"category\", category, options);\nexport const deleteCategory = (id: EntityId, options?: RequestOptions) => deleteEntityById(\"category\", id, options);\nexport const fetchCategory = (id: EntityId, params?: FetchByIdParams, options?: RequestOptions) =>\n fetchEntityById(\"category\", id, params, options);\n\nexport const saveStore = (store: EntityInput<Store>, options?: RequestOptions) => saveEntity(\"store\", store, options);\nexport const deleteStore = (id: EntityId, options?: RequestOptions) => deleteEntityById(\"store\", id, options);\nexport const fetchStore = (id: EntityId, params?: FetchByIdParams, options?: RequestOptions) =>\n fetchEntityById(\"store\", id, params, options);\n\nexport const savePromotion = (promotion: EntityInput<Promotion>, options?: RequestOptions) =>\n saveEntity(\"promotion\", promotion, options);\n/** Ends a promotion: Shopwave has no promotion DELETE, so this sets `endDate` to now. */\nexport const deletePromotion = (id: EntityId, options?: RequestOptions) => deleteEntityById(\"promotion\", id, options);\nexport const fetchPromotion = (id: EntityId, params?: FetchByIdParams, options?: RequestOptions) =>\n fetchEntityById(\"promotion\", id, params, options);\n\n/**\n * Creates an employee, or updates one (with `id`). On update Shopwave only\n * applies `roleId`, `joinedDate` and `exitDate` (and store roles); names and\n * email can't be changed, and the echo only carries the updated fields — re-read\n * with `fetchEmployee` if you need the stored record.\n */\nexport const saveEmployee = (employee: EntityInput<Employee>, options?: RequestOptions) =>\n saveEntity(\"employee\", employee, options);\n/** Retires an employee: Shopwave has no employee DELETE, so this sets `exitDate` to now. */\nexport const deleteEmployee = (id: EntityId, options?: RequestOptions) => deleteEntityById(\"employee\", id, options);\nexport const fetchEmployee = (id: EntityId, params?: FetchByIdParams, options?: RequestOptions) =>\n fetchEntityById(\"employee\", id, params, options);\n\n// Consumers are read-only in the Shopwave API: there is no save or delete.\nexport const fetchConsumer = (id: EntityId, params?: FetchByIdParams, options?: RequestOptions) =>\n fetchEntityById(\"consumer\", id, params, options);\n","import type { Consumer } from \"./consumer\";\nimport type { Product } from \"./product\";\nimport type { ReportQueryMap, ReportResponse } from \"./report\";\n\n// ── Basket Report Types (preserved from the original `types/basket.ts`) ──────\n\nexport interface BasketItem {\n productId: number;\n productName: string;\n quantity: number;\n price: number;\n tax: number;\n}\n\nexport interface Basket {\n id: number;\n name: string;\n consumerId: number;\n products: BasketItem[];\n total: number;\n taxTotal: number;\n status: \"pending\" | \"completed\" | \"cancelled\";\n completeDate: string;\n}\n\nexport interface BasketReportRow {\n id: number;\n basketId: number;\n basketName: string;\n consumerId: number;\n consumerName: string;\n consumerEmail: string;\n products: BasketItem[];\n basketTotal: number;\n tax: number;\n dateTime: string;\n status: string;\n}\n\nexport interface BasketSummary {\n totalBaskets: number;\n totalRevenue: number;\n totalTax: number;\n uniqueConsumers: number;\n}\n\nexport interface BasketFilters {\n search: string;\n dateRange: {\n start: Date | null;\n end: Date | null;\n };\n consumerId: number | null;\n status: \"all\" | \"pending\" | \"completed\" | \"cancelled\";\n}\n\nexport interface BasketPagination {\n page: number;\n pageSize: number;\n total: number;\n}\n\nexport const DEFAULT_BASKET_FILTERS: BasketFilters = {\n search: \"\",\n dateRange: {\n start: null,\n end: null,\n },\n consumerId: null,\n status: \"all\",\n};\n\nexport const DEFAULT_PAGINATION: BasketPagination = {\n page: 0,\n pageSize: 25,\n total: 0,\n};\n\n/** Intermediate shape produced by {@link parseBasketReportData}. */\nexport interface ParsedBasket {\n id: number;\n name: string;\n consumerId: number;\n products: BasketItem[];\n total: number;\n taxTotal: number;\n status: \"completed\";\n completeDate: string;\n}\n\n// ── Pure transform helpers (extracted from the original `useBasketReport`) ────\n\n/** Formats a date as `YYYY-MM-DD HH:mm:ss` for the report query. */\nexport function formatDateForQuery(date: Date, isEndOfDay = false): string {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n const time = isEndOfDay ? \"23:59:59\" : \"00:00:00\";\n return `${year}-${month}-${day} ${time}`;\n}\n\n/** Builds the `Basket` report query from a set of filters. */\nexport function buildBasketReportQuery(filters: BasketFilters): ReportQueryMap {\n const whereConditions: string[] = [];\n\n const today = new Date();\n const startDate = filters.dateRange.start ?? today;\n const endDate = filters.dateRange.end ?? today;\n\n whereConditions.push(\n `Basket.completeDate > '${formatDateForQuery(startDate, false)}'`\n );\n whereConditions.push(\n `Basket.completeDate < '${formatDateForQuery(endDate, true)}'`\n );\n\n if (filters.consumerId) {\n whereConditions.push(`Basket.consumerId = ${filters.consumerId}`);\n }\n\n if (filters.status !== \"all\") {\n whereConditions.push(`Basket.status = '${filters.status}'`);\n }\n\n return {\n baskets: {\n FROM: \"Basket\",\n WHERE: whereConditions.length > 0 ? { AND: whereConditions } : undefined,\n },\n };\n}\n\n/** Flattens the raw report rows/keys into structured baskets. */\nexport function parseBasketReportData(\n reports: ReportResponse[\"reports\"] | null | undefined\n): ParsedBasket[] {\n if (!reports?.baskets?.data || !reports?.baskets?.keys) {\n return [];\n }\n\n const { keys, data } = reports.baskets;\n\n const keyIndices: { [key: string]: number } = {};\n keys.forEach((key, index) => {\n keyIndices[key.name] = index;\n });\n\n const productKey = keys.find((k) => k.name === \"Basket.Product\");\n const productSubKeys: { [key: string]: number } = {};\n if (productKey?.item) {\n productKey.item.forEach((subKey, index) => {\n productSubKeys[subKey.name] = index;\n });\n }\n\n return data.map((row) => {\n const productsArray = (row[keyIndices[\"Basket.Product\"]] as any[][]) || [];\n const products: BasketItem[] = productsArray.map((productRow) => ({\n productId: productRow[productSubKeys[\"Basket.Product.productId\"]] as number,\n productName: `Product #${productRow[productSubKeys[\"Basket.Product.productId\"]]}`,\n quantity:\n (productRow[productSubKeys[\"Basket.Product.quantity\"]] as number) || 1,\n price:\n (productRow[productSubKeys[\"Basket.Product.productPrice\"]] as number) ||\n 0,\n tax:\n (productRow[\n productSubKeys[\"Basket.Product.productTaxPercentage\"]\n ] as number) || 0,\n }));\n\n const total = products.reduce((sum, p) => sum + p.price * p.quantity, 0);\n const taxTotal = products.reduce(\n (sum, p) => sum + p.price * p.quantity * p.tax,\n 0\n );\n\n const completeDateTimestamp = row[keyIndices[\"Basket.completeDate\"]] as number;\n const completeDate = completeDateTimestamp\n ? new Date(completeDateTimestamp).toISOString()\n : \"\";\n\n return {\n id: row[keyIndices[\"Basket.id\"]] as number,\n name: row[keyIndices[\"Basket.name\"]] as string,\n consumerId: row[keyIndices[\"Basket.consumerId\"]] as number,\n products,\n total,\n taxTotal,\n status: \"completed\" as const,\n completeDate,\n };\n });\n}\n\n/** Unique non-null consumer ids referenced by a set of baskets. */\nexport function collectConsumerIds(baskets: ParsedBasket[]): number[] | null {\n if (!baskets.length) return null;\n const ids = baskets\n .map((b) => b.consumerId)\n .filter((id): id is number => id != null);\n return ids.length > 0 ? [...new Set(ids)] : null;\n}\n\n/** Unique product ids referenced by a set of baskets. */\nexport function collectProductIds(baskets: ParsedBasket[]): number[] | null {\n if (!baskets.length) return null;\n const ids = new Set<number>();\n baskets.forEach((basket) => {\n basket.products.forEach((product) => {\n if (product.productId) {\n ids.add(product.productId);\n }\n });\n });\n return ids.size > 0 ? [...ids] : null;\n}\n\nexport interface CombineBasketRowsParams {\n baskets: ParsedBasket[];\n consumerMap: { [id: number]: Consumer };\n productMap: { [id: number]: Product };\n search?: string;\n}\n\n/** Joins baskets with consumer + product lookups, applies search, sorts. */\nexport function combineBasketRows({\n baskets,\n consumerMap,\n productMap,\n search,\n}: CombineBasketRowsParams): BasketReportRow[] {\n if (!baskets.length) return [];\n\n let combinedRows: BasketReportRow[] = baskets.map((basket) => {\n const consumer = basket.consumerId ? consumerMap[basket.consumerId] : null;\n\n const productsWithNames: BasketItem[] = basket.products.map((p) => {\n const product = productMap[p.productId];\n return {\n ...p,\n productName: product?.name || `Product #${p.productId}`,\n };\n });\n\n return {\n id: basket.id,\n basketId: basket.id,\n basketName: basket.name || `Basket #${basket.id}`,\n consumerId: basket.consumerId,\n consumerName: consumer\n ? `${consumer.firstName} ${consumer.lastName}`\n : \"Unknown\",\n consumerEmail: consumer?.email || \"-\",\n products: productsWithNames,\n basketTotal: (basket.total || 0) / 100,\n tax: basket.taxTotal || 0,\n dateTime: basket.completeDate,\n status: basket.status || \"pending\",\n };\n });\n\n if (search) {\n const searchLower = search.toLowerCase();\n combinedRows = combinedRows.filter(\n (row) =>\n row.basketId.toString().includes(searchLower) ||\n row.basketName.toLowerCase().includes(searchLower) ||\n row.consumerName.toLowerCase().includes(searchLower) ||\n row.consumerEmail.toLowerCase().includes(searchLower)\n );\n }\n\n combinedRows.sort((a, b) => {\n const dateA = new Date(a.dateTime).getTime();\n const dateB = new Date(b.dateTime).getTime();\n return dateB - dateA;\n });\n\n return combinedRows;\n}\n\n/** Aggregate totals for a set of report rows. */\nexport function computeBasketSummary(rows: BasketReportRow[]): BasketSummary {\n const uniqueConsumerIds = new Set(rows.map((row) => row.consumerId));\n return {\n totalBaskets: rows.length,\n totalRevenue: rows.reduce((sum, row) => sum + row.basketTotal, 0),\n totalTax: rows.reduce((sum, row) => sum + row.tax, 0),\n uniqueConsumers: uniqueConsumerIds.size,\n };\n}\n\n/** Builds a consumer id → consumer lookup. */\nexport function buildConsumerMap(consumers: Consumer[] | null | undefined): {\n [id: number]: Consumer;\n} {\n const map: { [id: number]: Consumer } = {};\n if (consumers) {\n consumers.forEach((consumer) => {\n map[consumer.id] = consumer;\n });\n }\n return map;\n}\n"],"mappings":"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"}
@@ -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).
77
98
  */
78
- declare function apiDelete(path: string, options?: RequestOptions): Promise<void>;
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.
135
+ */
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,5 @@ 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
+ export { Basket, BasketFilters, BasketItem, BasketPagination, BasketReportRow, BasketSummary, Category, CategoryResponse, CombineBasketRowsParams, Consumer, ConsumerResponse, DEFAULT_BASKET_FILTERS, DEFAULT_PAGINATION, DeletableEntityKind, Employee, EmployeeResponse, EntityDefinition, EntityId, EntityInput, EntityKind, EntityTypes, ErrorMessage, Extras, FetchByIdParams, FetchCategoriesParams, FetchEmployeesParams, FetchProductsParams, FetchPromotionsParams, FetchStoresParams, MessageInfo, ParsedBasket, Product, ProductInstance, ProductResponse, Promotion, PromotionResponse, PromotionRuleSet, ReportData, ReportKey, ReportQuery, ReportQueryMap, ReportResponse, RequestOptions, SHOPWAVE_ENTITIES, Saved, SessionStatus, ShopwaveApiError, ShopwaveErrorMap, Store, StoreResponse, SubmitEntityArgs, SubmitEntityResponse, SuccessMessage, WritableEntityKind, apiDelete, apiGet, apiRequest, apiResponse, apiSend, assertNoApiErrors, authorizationFor, buildBasketReportQuery, buildConsumerMap, collectConsumerIds, collectProductIds, combineBasketRows, computeBasketSummary, deleteCategory, deleteEmployee, deleteEntity, deleteEntityById, deleteProduct, deletePromotion, deleteStore, fetchCategories, fetchCategory, fetchConsumer, fetchConsumers, fetchEmployee, fetchEmployees, fetchEntityById, fetchProduct, fetchProducts, fetchProductsMap, fetchPromotion, fetchPromotions, fetchReport, fetchSession, fetchStore, fetchStores, formatDateForQuery, getApiErrorMap, getApiErrors, loginPath, logout, logoutPath, parseBasketReportData, rolesById, saveCategory, saveEmployee, saveEntities, saveEntity, saveProduct, savePromotion, saveStore, submitEntity, toShopwaveApiError, withToken };
509
700
  //# sourceMappingURL=index.d.cts.map